See also: fnkit/validations: Validation & Conversion Utilities · fnkit/datetime: Date/Time Utilities
fnkit is a modern Go utility library inspired by the best of JavaScript (like Lodash, Array methods) and Rust (Result type, functional error handling). It brings expressive, type-safe, and composable utilities to Go, making your code more concise, robust, and fun to write.
go get github.com/kishankumarhs/fnkit
go get github.com/kishankumarhs/fnkit/concurrency
import "github.com/kishankumarhs/fnkit"
import "github.com/kishankumarhs/fnkit/concurrency"
nums := []int{1, 2, 3, 4}
doubled := fnkit.ParallelMap(nums, func(x int) int { return x * 2 })
// doubled == []int{2, 4, 6, 8}
nums := []int{1, 2, 3, 4}
var sum int64
fnkit.ParallelForEach(nums, func(x int) { atomic.AddInt64(&sum, int64(x)) })
// sum == 10
var count int32
debounced := fnkit.Debounce(func() { atomic.AddInt32(&count, 1) }, 50*time.Millisecond)
for i := 0; i < 5; i++ {
debounced()
}
time.Sleep(100 * time.Millisecond)
// count == 1 (function only called once after the last call)
var count int32
throttled := fnkit.Throttle(func() { atomic.AddInt32(&count, 1) }, 50*time.Millisecond)
for i := 0; i < 5; i++ {
throttled()
time.Sleep(10 * time.Millisecond)
}
time.Sleep(100 * time.Millisecond)
// count is 2 or 3 (function called at most once per 50ms)
fnkit provides a comprehensive set of string utility functions inspired by Python, JavaScript, and Rust. All are Unicode-safe and tested.
fnkit.Repeat("ab", 3) // "ababab"
fnkit.ReplaceAll("foo bar foo", "foo", "baz") // "baz bar baz"
fnkit.HasPrefix("hello world", "hello") // true
fnkit.HasSuffix("hello world", "world") // true
fnkit.Contains("banana", "nan") // true
fnkit.Count("banana", "na") // 2
fnkit.ReverseString("a😊b") // "b😊a"
fnkit.IsAlpha("abcXYZ") // true
fnkit.IsAlpha("abc123") // false
fnkit.IsNumeric("12345") // true
fnkit.IsNumeric("12a45") // false
fnkit.Capitalize("hELLO") // "Hello"
fnkit.StripLeft(" hello ") // "hello "
fnkit.StripRight(" hello ") // " hello"
fnkit.Partition("foo-bar-baz", "-") // ("foo", "-", "bar-baz")
fnkit.Rpartition("foo-bar-baz", "-") // ("foo-bar", "-", "baz")
fnkit.Words("Go is awesome") // []string{"Go", "is", "awesome"}
fnkit.CamelCase("hello world_test-case") // "helloWorldTestCase"
fnkit.SnakeCase("hello world_test-case") // "hello_world_test_case"
fnkit.KebabCase("hello world_test-case") // "hello-world-test-case"
fnkit.IsUpper("ABC") // true
fnkit.IsUpper("AbC") // false
fnkit.IsLower("abc") // true
fnkit.IsLower("aBc") // false
fnkit.SwapCase("aBc") // "AbC"
fnkit.Remove("a1b2c3", unicode.IsDigit) // "abc"
fnkit.Keep("a1b2c3", unicode.IsDigit) // "123"
fnkit.PadCenter("hi", 6, '*') // "**hi**"
nums := []int{1, 2, 3, 4, 5, 6}
grouped := fnkit.GroupBy(nums, func(n int) string {
if n%2 == 0 {
return "even"
}
return "odd"
})
// grouped["even"] == []int{2,4,6}
// grouped["odd"] == []int{1,3,5}
nums := []int{1, 2, 3, 4, 5}
chunks := fnkit.Chunk(nums, 2) // [][]int{{1,2},{3,4},{5}}
nums := []int{1,2,2,3,1,4}
uniq := fnkit.Unique(nums) // []int{1,2,3,4}
nested := [][]int{{1,2},{3,4}}
flat := fnkit.Flatten(nested) // []int{1,2,3,4}
Result[T]
is a generic container for a value or an error, inspired by Rust. It allows for functional error handling without repetitive if err != nil
checks.
res := fnkit.Ok("user1")
if res.IsOk() {
fmt.Println("User:", res.Value)
} else {
fmt.Println("Error:", res.Err)
}
// Using ValueOr for fallback
name := res.ValueOr("default")
call := fnkit.Err[int](errors.New("network error"))
code := call.ValueOr(404) // returns 404 if error
db := mockDBQuery(1) // returns Result[string]
if db.IsOk() {
fmt.Println("Found:", db.Value)
} else {
fmt.Println("DB error:", db.Err)
}
net := mockNetworkCall("https://fail")
if !net.IsOk() {
fmt.Println("Network error:", net.Err)
}
Ok(zeroValue)
is valid andIsOk()
is true.Err[T](nil)
is treated as Ok (no error).- Works with any type, including structs.
Option[T]
is a generic container for an optional value, inspired by Rust. It allows for safe, idiomatic handling of values that may or may not be present, without using nil pointers.
opt := fnkit.Some(42)
if opt.IsSome() {
fmt.Println("Value:", opt.Unwrap()) // prints 42
}
none := fnkit.None[int]()
if none.IsNone() {
fmt.Println("No value")
}
opt := fnkit.Some("hello")
val := opt.UnwrapOr("default") // val == "hello"
none := fnkit.None[string]()
val2 := none.UnwrapOr("default") // val2 == "default"
Some(zeroValue)
is valid andIsSome()
is true.None[T]()
is alwaysIsNone()
.- Works with any type, including structs.
nums := []int{1, 2, 3, 4, 5, 6}
grouped := fnkit.GroupBy(nums, func(n int) string {
if n%2 == 0 {
return "even"
}
return "odd"
})
// grouped["even"] == []int{2,4,6}
// grouped["odd"] == []int{1,3,5}
nums := []int{1, 2, 3, 4, 5}
chunks := fnkit.Chunk(nums, 2) // [][]int{{1,2},{3,4},{5}}
nums := []int{1,2,2,3,1,4}
uniq := fnkit.Unique(nums) // []int{1,2,3,4}
nested := [][]int{{1,2},{3,4}}
flat := fnkit.Flatten(nested) // []int{1,2,3,4}
orig := []int{1, 2, 3}
copy := fnkit.CopyWith(orig)
copy[0] = 99 // orig is unchanged
s := []string{"x", "y"}
entries := fnkit.Entries(s)
// entries == []fnkit.Entry[string]{{0, "x"}, {1, "y"}}
allEven := fnkit.Every([]int{2, 4, 6}, func(i int) bool { return i%2 == 0 }) // true
allPositive := fnkit.Every([]int{1, 2, 3}, func(i int) bool { return i > 0 }) // true
empty := fnkit.Every([]int{}, func(i int) bool { return i > 0 }) // true (vacuous truth)
s := []int{1, 2, 3}
fnkit.Fill(s, 9) // s == []int{9, 9, 9}
s := []int{1, 2, 3, 2}
v, ok := fnkit.Find(s, func(i int) bool { return i == 2 }) // v==2, ok==true
idx := fnkit.FindIndex(s, func(i int) bool { return i == 2 }) // idx==1
v, ok = fnkit.FindLast(s, func(i int) bool { return i == 2 }) // v==2, ok==true
idx = fnkit.FindLastIndex(s, func(i int) bool { return i == 2 }) // idx==3
_, ok = fnkit.Find(s, func(i int) bool { return i == 100 }) // ok==false
nested := [][]int{{1, 2}, {3, 4}}
flat := fnkit.Flat(nested) // []int{1, 2, 3, 4}
nums := []int{1, 2}
flatMapped := fnkit.FlatMap(nums, func(i int) []int { return []int{i, i} }) // [1, 1, 2, 2]
sum := 0
fnkit.ForEach([]int{1, 2, 3}, func(i int) { sum += i })
// sum == 6
s := []int{1, 2, 3, 2}
fnkit.Includes(s, 2) // true
fnkit.IndexOf(s, 2) // 1
fnkit.LastIndexOf(s, 2) // 3
fnkit.Includes(s, 99) // false
s := []int{1, 2, 3}
str := fnkit.Join(s, "-") // "1-2-3"
empty := fnkit.Join([]int{}, ",") // ""
s := []string{"a", "b", "c"}
fnkit.Keys(s) // []int{0, 1, 2}
fnkit.Values(s) // []string{"a", "b", "c"} (copy)
s := []int{1, 2, 3}
v, ok := fnkit.Pop(&s) // v==3, ok==true, s==[1,2]
fnkit.Push(&s, 4) // s==[1,2,4]
v, ok = fnkit.Shift(&s) // v==1, ok==true, s==[2,4]
fnkit.Unshift(&s, 0) // s==[0,2,4]
empty := []int{}
_, ok = fnkit.Pop(&empty) // ok==false
_, ok = fnkit.Shift(&empty) // ok==false
s := []int{1, 2, 3}
sum := fnkit.ReduceRight(s, 0, func(acc, v int) int { return acc + v }) // 6
s := []int{1, 2, 3}
fnkit.Reverse(s) // s == [3,2,1]
s := []int{1, 2, 3, 4}
fnkit.Slice(s, 1, 3) // [2,3]
fnkit.Slice(s, -1, 10) // [4]
fnkit.Slice(s, 10, 20) // []
s := []int{1, 2, 3}
fnkit.Any(s, func(i int) bool { return i == 2 }) // true
fnkit.Any(s, func(i int) bool { return i == 100 }) // false
fnkit.Any([]int{}, func(i int) bool { return true }) // false
s := []int{1, 2, 3, 4}
removed := fnkit.Splice(&s, 1, 2, []int{9, 9}) // removed==[2,3], s==[1,9,9,4]
removed = fnkit.Splice(&s, 10, 2, []int{5}) // removed==[], s==[1,9,9,4,5]
removed = fnkit.Splice(&s, -1, 1, nil) // removed==[5], s==[1,9,9,4]
s := []int{1, 2, 3}
str := fnkit.ToLocaleString(s) // "1,2,3"
s := []int{1, 2, 3, 2}
fnkit.Without(s, 2) // [1,3]
s := []int{1, 2, 3, 4}
fnkit.Filter(&s, func(i int) bool { return i%2 == 0 }) // s==[2,4]
s2 := []int{1, 2, 3, 4}
evens := fnkit.ToFilter(s2, func(i int) bool { return i%2 == 0 }) // evens==[2,4], s2 unchanged
- All functions handle empty slices gracefully.
- Functions like
At
,Pop
,Shift
return the zero value and false if out of bounds or empty. - Negative indices are supported in
At
,Slice
, andSplice
(like Python). Filter
modifies the slice in-place;ToFilter
returns a new filtered slice.Every
on an empty slice returns true (vacuous truth);Any
on empty returns false.- All functions are safe for any type (thanks to Go generics).
MIT