lo README

The project's README from github.com/samber/lo. As one markdown file: README.md.

lo - Iterate over slices, maps, channels...

tag Go Version GoDoc Build Status Go report Coverage Contributors License

✨ samber/lo is a Lodash-style Go library based on Go 1.18+ Generics.

A utility library based on Go 1.18+ generics that makes it easier to work with slices, maps, strings, channels, and functions. It provides dozens of handy methods to simplify common coding tasks and improve code readability. It may look like Lodash in some aspects.

5 to 10 helpers may overlap with those from the Go standard library, in packages slices and maps. I feel this library is legitimate and offers many more valuable abstractions.

See also:

What makes it different from samber/ro?

Why this name?

I wanted a short name, similar to "Lodash", and no Go package uses this name.

lo

🚀 Install

go get github.com/samber/lo@v1

# AI Agent Skill
npx skills add https://github.com/samber/cc-skills-golang --skill golang-samber-lo

This library is v1 and follows SemVer strictly.

No breaking changes will be made to exported APIs before v2.0.0, except for experimental packages under exp/.

This library has no dependencies outside the Go standard library.

💡 Usage

You can import lo using:

import (
    "github.com/samber/lo"
    lop "github.com/samber/lo/parallel"
    lom "github.com/samber/lo/mutable"
    loi "github.com/samber/lo/it"
)

Then use one of the helpers below:

names := lo.Uniq([]string{"Samuel", "John", "Samuel"})
// []string{"Samuel", "John"}

Tips for lazy developers

I cannot recommend it, but in case you are too lazy for repeating lo. everywhere, you can import the entire library into the namespace.

import (
    . "github.com/samber/lo"
)

I take no responsibility for this junk. 😁 💩

🤠 Spec

GoDoc: godoc.org/github.com/samber/lo

Documentation: lo.samber.dev

Supported helpers for slices:

Supported helpers for maps:

Supported math helpers:

Supported helpers for strings:

Supported helpers for tuples:

Supported helpers for time and duration:

Supported helpers for channels:

Supported intersection helpers:

Supported search helpers:

Conditional helpers:

Type manipulation helpers:

Function helpers:

Concurrency helpers:

Error handling:

Constraints:

Filter

Iterates over a collection and returns a slice of all the elements the predicate function returns true for.

result := lo.Filter([]int{1, 2, 3, 4}, func(x int, index int) bool {
    return x%2 == 0
})
// []int{2, 4}

[play]

// Use FilterErr when the predicate can return an error
result, err := lo.FilterErr([]int{1, 2, 3, 4}, func(x int, _ int) (bool, error) {
    if x == 3 {
        return false, fmt.Errorf("number 3 is not allowed")
    }
    return x%2 == 0, nil
})
// []int(nil), error("number 3 is not allowed")

[play]

Mutable: like lo.Filter(), but the slice is updated in place.

import lom "github.com/samber/lo/mutable"

list := []int{1, 2, 3, 4}
newList := lom.Filter(list, func(x int) bool {
    return x%2 == 0
})

list
// []int{2, 4, 3, 4}

newList
// []int{2, 4}

[play]

Map

Manipulates a slice of one type and transforms it into a slice of another type:

import "github.com/samber/lo"

result1:= lo.Map([]int64{1, 2, 3, 4}, func(x int64, index int) string {
    return strconv.FormatInt(x, 10)
})
// []string{"1", "2", "3", "4"}
// Use MapErr when the transform function can return an error
result2, err := lo.MapErr([]int{1, 2, 3, 4}, func(x int, _ int) (string, error) {
    if x == 3 {
        return "", fmt.Errorf("number 3 is not allowed")
    }
    return strconv.Itoa(x), nil
})
// []string(nil), error("number 3 is not allowed")

[play]

Parallel processing: like lo.Map(), but the transform function is called in a goroutine. Results are returned in the same order.

import lop "github.com/samber/lo/parallel"

partitionedByParity := lop.Map([]int64{1, 2, 3, 4}, func(x int64, _ int) string {
    return strconv.FormatInt(x, 10)
})
// []string{"1", "2", "3", "4"}

[play]

Mutable: like lo.Map(), but the slice is updated in place.

import lom "github.com/samber/lo/mutable"

list := []int{1, 2, 3, 4}
lom.Map(list, func(x int) int {
    return x*2
})
// []int{2, 4, 6, 8}

[play]

UniqMap

Manipulates a slice and transforms it to a slice of another type with unique values.

type User struct {
    Name string
    Age  int
}
users := []User{{Name: "Alex", Age: 10}, {Name: "Alex", Age: 12}, {Name: "Bob", Age: 11}, {Name: "Alice", Age: 20}}

names := lo.UniqMap(users, func(u User, index int) string {
    return u.Name
})
// []string{"Alex", "Bob", "Alice"}

[play]

FilterMap

Returns a slice obtained after both filtering and mapping using the given callback function.

The callback function should return two values: the result of the mapping operation and whether the result element should be included or not.

matching := lo.FilterMap([]string{"cpu", "gpu", "mouse", "keyboard"}, func(x string, _ int) (string, bool) {
    if strings.HasSuffix(x, "pu") {
        return "xpu", true
    }
    return "", false
})
// []string{"xpu", "xpu"}

[play]

FlatMap

Manipulates a slice and transforms and flattens it to a slice of another type. The transform function can either return a slice or a nil, and in the nil case no value is added to the final slice.

lo.FlatMap([]int64{0, 1, 2}, func(x int64, _ int) []string {
    return []string{
        strconv.FormatInt(x, 10),
        strconv.FormatInt(x, 10),
    }
})
// []string{"0", "0", "1", "1", "2", "2"}
// Use FlatMapErr when the transform function can return an error
result, err := lo.FlatMapErr([]int64{0, 1, 2, 3}, func(x int64, _ int) ([]string, error) {
    if x == 2 {
        return nil, fmt.Errorf("number 2 is not allowed")
    }
    return []string{strconv.FormatInt(x, 10), strconv.FormatInt(x, 10)}, nil
})
// []string(nil), error("number 2 is not allowed")

[play]

Reduce

Reduces a collection to a single value. The value is calculated by accumulating the result of running each element in the collection through an accumulator function. Each successive invocation is supplied with the return value returned by the previous call.

sum := lo.Reduce([]int{1, 2, 3, 4}, func(agg int, item int, _ int) int {
    return agg + item
}, 0)
// 10
// Use ReduceErr when the accumulator function can return an error
result, err := lo.ReduceErr([]int{1, 2, 3, 4}, func(agg int, item int, _ int) (int, error) {
    if item == 3 {
        return 0, fmt.Errorf("number 3 is not allowed")
    }
    return agg + item, nil
}, 0)
// 0, error("number 3 is not allowed")

[play]

ReduceRight

Like lo.Reduce except that it iterates over elements of collection from right to left.

result := lo.ReduceRight([][]int{{0, 1}, {2, 3}, {4, 5}}, func(agg []int, item []int, _ int) []int {
    return append(agg, item...)
}, []int{})
// []int{4, 5, 2, 3, 0, 1}
// Use ReduceRightErr when the accumulator function can return an error
result, err := lo.ReduceRightErr([]int{1, 2, 3, 4}, func(agg int, item int, _ int) (int, error) {
    if item == 2 {
        return 0, fmt.Errorf("number 2 is not allowed")
    }
    return agg + item, nil
}, 0)
// 0, error("number 2 is not allowed")

[play]

ForEach

Iterates over elements of a collection and invokes the function over each element.

import "github.com/samber/lo"

lo.ForEach([]string{"hello", "world"}, func(x string, _ int) {
    println(x)
})
// prints "hello\nworld\n"

[play]

Parallel processing: like lo.ForEach(), but the callback is called as a goroutine.

import lop "github.com/samber/lo/parallel"

lop.ForEach([]string{"hello", "world"}, func(x string, _ int) {
    println(x)
})
// prints "hello\nworld\n" or "world\nhello\n"

ForEachWhile

Iterates over collection elements and invokes iteratee for each element collection return value decide to continue or break, like do while().

list := []int64{1, 2, -42, 4}

lo.ForEachWhile(list, func(x int64, _ int) bool {
	if x < 0 {
		return false
	}
	fmt.Println(x)
	return true
})
// 1
// 2

[play]

ForEachErr

Iterates over elements of a collection, invokes the iteratee for each element, and returns the first error returned by the iteratee. Iteration stops on the first error.

import "github.com/samber/lo"

list := []int64{1, 2, -42, 4}

err := lo.ForEachErr(list, func(x int64, _ int) error {
	if x < 0 {
		return fmt.Errorf("%d is not allowed", x)
	}
	fmt.Println(x)
	return nil
})
// 1
// 2
// err: "-42 is not allowed"

Times

Times invokes the iteratee n times, returning a slice of the results of each invocation. The iteratee is invoked with index as argument.

import "github.com/samber/lo"

lo.Times(3, func(i int) string {
    return strconv.FormatInt(int64(i), 10)
})
// []string{"0", "1", "2"}

[play]

Parallel processing: like lo.Times(), but callback is called in goroutine.

import lop "github.com/samber/lo/parallel"

lop.Times(3, func(i int) string {
    return strconv.FormatInt(int64(i), 10)
})
// []string{"0", "1", "2"}

Uniq

Returns a duplicate-free version of a slice, in which only the first occurrence of each element is kept. The order of result values is determined by the order they occur in the slice.

uniqValues := lo.Uniq([]int{1, 2, 2, 1})
// []int{1, 2}

[play]

UniqBy

Returns a duplicate-free version of a slice, in which only the first occurrence of each element is kept. The order of result values is determined by the order they occur in the slice. It accepts iteratee which is invoked for each element in the slice to generate the criterion by which uniqueness is computed.

uniqValues := lo.UniqBy([]int{0, 1, 2, 3, 4, 5}, func(i int) int {
    return i%3
})
// []int{0, 1, 2}
// Use UniqByErr when the iteratee function can return an error
result, err := lo.UniqByErr([]int{0, 1, 2, 3, 4, 5}, func(i int) (int, error) {
    if i == 3 {
        return 0, fmt.Errorf("number 3 is not allowed")
    }
    return i % 3, nil
})
// []int(nil), error("number 3 is not allowed")

[play]

IsUniq

Returns true if all elements in the slice are unique, false otherwise. Returns true for nil and empty slices.

lo.IsUniq([]int{1, 2, 3})
// true

lo.IsUniq([]int{1, 2, 1})
// false

IsUniqBy

Returns true if all elements in the slice are unique based on a custom criterion. It accepts iteratee which is invoked for each element in the slice to generate the criterion by which uniqueness is computed. Returns true for nil and empty slices.

type User struct {
    ID   int
    Name string
}

users := []User{{ID: 1, Name: "Alice"}, {ID: 2, Name: "Bob"}, {ID: 1, Name: "Charlie"}}

lo.IsUniqBy(users, func(u User) int { return u.ID })
// false

lo.IsUniqBy(users, func(u User) string { return u.Name })
// true

GroupBy

Returns an object composed of keys generated from the results of running each element of collection through iteratee.

import lo "github.com/samber/lo"

groups := lo.GroupBy([]int{0, 1, 2, 3, 4, 5}, func(i int) int {
    return i%3
})
// map[int][]int{0: []int{0, 3}, 1: []int{1, 4}, 2: []int{2, 5}}
// Use GroupByErr when the iteratee function can return an error
result, err := lo.GroupByErr([]int{0, 1, 2, 3, 4, 5}, func(i 

lo (samber/lo)

Searching Database…

21.4K ·GitHub
What is this?samber/lo is a Lodash-style utility library for Go built on 1.18+ generics: dozens of helpers for filtering, mapping, grouping and reducing slices, maps, strings, channels and functions.
Is it a library I import into my Go code, or a standalone tool?A pure import-and-call library: there is no binary, server or config. You bring it into your Go code and use helpers like lo.Filter or lo.Map directly.
How do I add it to a project?Run 'go get github.com/samber/lo', import it, and start calling helpers. The API is v1 under strict SemVer: no breaking changes to exported APIs before v2.0.0, except experimental packages under exp/.
Which Go version do I need, and will it pull in other packages?Go 1.18 or newer, since every helper is built on generics. Beyond that it is fully self-contained: no dependencies outside the Go standard library.
Is there a trick to avoid typing lo. before every helper?Yes: a dot-import pulls the whole package into your namespace so helpers need no prefix. The author cannot recommend it and jokingly takes no responsibility for the result.
Does it matter which OS I build for?Not really: it is pure Go with standard-library-only code, so it compiles anywhere Go itself runs, from Linux servers to Windows and macOS machines.
What kinds of helpers does it include?Slice workhorses (Filter, Map, Reduce, GroupBy, Uniq, Shuffle, Chunk), map helpers (Keys, Invert, Assign), string casing (SnakeCase, KebabCase), tuples T2-T9 with Zip and Unzip, channel plumbing (Buffer, FanIn, FanOut), conditionals (Ternary, Switch), concurrency (Debounce, Throttle, Synchronize, Async) and error helpers (Must, Try, Validate).
Can it process a slice in parallel?Yes: Map, ForEach and Times have parallel variants that run the callback in goroutines, and the parallel Map returns results in the same order as the input slice.
Doesn't the Go standard library already cover some of this?A bit: 5 to 10 helpers overlap with the standard slices and maps packages, but the author kept them because the library offers many more valuable abstractions on top.
Are there related libraries by the same author?Yes: samber/ro handles reactive infinite event streams while lo targets finite synchronous sequences, samber/do is a generics-based dependency injection toolkit, and samber/mo provides monads like Option, Result and Either.
What are similar projects?conc, ramda, effect, bubbletea, fantasy-land, gjson, gqlgen, es-toolkit
Creator
samber
Size
7.5 MB
Stars
21.4K
Age
4y
Commits · 90 Days