go-underscore
Helpfully Functional Go - A useful collection of Go utilities. Designed for programmer happiness.
Top Related Projects
💥 A Lodash-style Go library based on Go 1.18+ Generics (map, filter, contains, find...)
A modern Go utility library which provides helpers (map, find, contains, filter, ...)
🍕 Enjoy a slice! A utility library for dealing with slices and maps that focuses on type safety and performance.
A comprehensive, efficient, and reusable util function library of Go.
Quick Overview
Go-underscore is a utility library for Go that provides functional programming helpers inspired by Underscore.js. It offers a collection of functions to manipulate arrays, maps, and other data structures in a more functional and concise manner, enhancing Go's standard library capabilities.
Pros
- Brings functional programming concepts to Go, making certain operations more expressive
- Provides a familiar API for developers coming from JavaScript/Underscore.js background
- Reduces boilerplate code for common operations on collections and data structures
- Implements lazy evaluation for improved performance in certain scenarios
Cons
- Adds an external dependency to projects, which may not align with Go's philosophy of minimal dependencies
- Some functions may have performance overhead compared to native Go implementations
- Limited maintenance and updates (last commit was several years ago)
- May encourage non-idiomatic Go code in some cases
Code Examples
- Filtering a slice:
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
evenNumbers := __.Filter(numbers, func(n, _ int) bool {
return n%2 == 0
})
// evenNumbers: [2, 4, 6, 8, 10]
- Mapping over a slice:
names := []string{"John", "Jane", "Bob"}
upperNames := __.Map(names, func(name string, _ int) string {
return strings.ToUpper(name)
})
// upperNames: ["JOHN", "JANE", "BOB"]
- Reducing a slice:
numbers := []int{1, 2, 3, 4, 5}
sum := __.Reduce(numbers, func(memo, num, _ int) int {
return memo + num
}, 0)
// sum: 15
Getting Started
To use go-underscore in your Go project, follow these steps:
-
Install the package:
go get github.com/tobyhede/go-underscore
-
Import the package in your Go file:
import __ "github.com/tobyhede/go-underscore"
-
Start using the functions:
result := __.Map([]int{1, 2, 3}, func(n, _ int) int { return n * 2 }) fmt.Println(result) // Output: [2, 4, 6]
Competitor Comparisons
💥 A Lodash-style Go library based on Go 1.18+ Generics (map, filter, contains, find...)
Pros of lo
- More actively maintained with recent updates and contributions
- Broader range of utility functions, including error handling and concurrency helpers
- Generics support for improved type safety and reduced boilerplate
Cons of lo
- Larger codebase and API surface, potentially increasing complexity
- May have a steeper learning curve for newcomers compared to go-underscore
- Some functions might overlap with standard library capabilities
Code Comparison
lo:
names := lo.Map([]string{"Samuel", "John", "Alice"}, func(name string, _ int) string {
return strings.ToUpper(name)
})
go-underscore:
names := __.Map([]string{"Samuel", "John", "Alice"}, func(name string, _ int) string {
return strings.ToUpper(name)
})
Both libraries provide similar functionality for common operations like mapping, but lo offers a more extensive set of utilities and leverages Go's generics for improved type safety. While go-underscore is simpler and may be easier to grasp initially, lo provides a more comprehensive toolkit for functional programming in Go. The choice between them depends on project requirements, team familiarity, and the desired balance between simplicity and feature richness.
A modern Go utility library which provides helpers (map, find, contains, filter, ...)
Pros of go-funk
- More actively maintained with recent updates
- Broader range of utility functions
- Better performance for some operations
Cons of go-funk
- Relies heavily on reflection, which can impact performance
- Less idiomatic Go code compared to go-underscore
- Steeper learning curve due to more complex API
Code Comparison
go-funk:
result := funk.Filter([]int{1, 2, 3, 4}, func(x int) bool {
return x%2 == 0
})
go-underscore:
result := underscore.Filter([]int{1, 2, 3, 4}, func(x int, _ int) bool {
return x%2 == 0
})
Both libraries provide similar functionality, but go-funk's API is slightly more concise. However, go-underscore's approach is more aligned with idiomatic Go, including an index parameter in the filter function.
go-funk offers a wider range of utility functions and is more actively maintained, making it a good choice for projects requiring extensive functional programming capabilities. On the other hand, go-underscore may be preferable for developers seeking a more Go-like approach and potentially better performance in certain scenarios due to less reliance on reflection.
🍕 Enjoy a slice! A utility library for dealing with slices and maps that focuses on type safety and performance.
Pros of pie
- More actively maintained with recent updates
- Supports generics for type-safe operations
- Offers a wider range of functions and utilities
Cons of pie
- Requires code generation, which adds complexity to the build process
- May have a steeper learning curve due to its more extensive API
Code Comparison
pie:
// Generate pie functions for []int
//go:generate pie int
go-underscore:
result := underscore.Map([]int{1, 2, 3}, func(n int) int {
return n * 2
})
Key Differences
- pie uses code generation to create type-specific functions, while go-underscore relies on interface{} for generic operations
- pie offers better type safety and performance due to its generated code approach
- go-underscore provides a simpler API that may be easier to use for basic operations
Use Cases
- pie is well-suited for projects requiring type-safe operations and extensive utility functions
- go-underscore is a good choice for simpler projects or when quick prototyping is needed
Community and Support
- pie has more stars and contributors on GitHub, indicating a larger community
- go-underscore has been around longer but has less recent activity
A comprehensive, efficient, and reusable util function library of Go.
Pros of lancet
- More comprehensive and actively maintained library with a wider range of utility functions
- Better organized into distinct packages for different types of operations (e.g., algorithm, datastructure, formatter)
- Includes additional features like concurrent utilities and network-related functions
Cons of lancet
- Larger codebase and API surface, potentially more complex to learn and use
- May include unnecessary functions for simpler projects, leading to increased binary size
- Less focused on functional programming paradigms compared to go-underscore
Code comparison
go-underscore:
result := underscore.Map([]int{1, 2, 3}, func(n int) int {
return n * 2
})
lancet:
result := maputil.Map([]int{1, 2, 3}, func(n int) int {
return n * 2
})
Both libraries provide similar functionality for common operations like mapping, but lancet offers a more extensive set of utilities across various domains. While go-underscore focuses primarily on functional programming concepts, lancet provides a broader range of general-purpose functions for Go development. The choice between the two depends on the specific needs of your project and your preference for library size and scope.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual CopilotREADME
Underscore.go
Move Fast; Optimize Late
A useful collection of Go utilities. Designed for programmer happiness.
TL;DR Sort-of like underscore.js, but for Go
API Documentation
:warning: Warning
This package is in heavy flux at the moment as I work to incorporate feedback from various sources.
:squirrel: Todo
- godoc
- contains
- indexOf
- worker pools
- parallel each
- parallel map with worker pool
- refactor to make functions first parameter (eg Each func(func(A), []A))
- handle maps & slices
- all
- any
- none
Typed Functions
Any
Each
Each func(func(A int), []A) Each func(func(A B), []A)
Applies the given iterator function to each element of a collection (slice or map).
If the collection is a Slice, the iterator function arguments are value, index
If the collection is a Map, the iterator function arguments are value, key
EachP is a Parallel implementation of Each and concurrently applies the given iterator function to each element of a collection (slice or map).
// var Each func(func(value interface{}, i interface{}), interface{})
var buffer bytes.Buffer
fn := func(s, i interface{}) {
buffer.WriteString(s.(string))
}
s := []string{"a", "b", "c", "d", "e"}
Each(fn, s)
expect := "abcde"
e := un.Each(fn, s)
fmt.Printf("%#v\n", e) //"abcde"
Typed Each can be defined using a function type and the MakeEach helper.
Using a Typed Slice
var EachInt func(func(value, i int), []int)
MakeEach(&EachInt)
var sum int
fn := func(v, i int) {
sum += v
}
i := []int{1, 2, 3, 4, 5}
EachInt(fn, i)
fmt.Printf("%#v\n", sum) //15
Using a Typed Map
var EachStringInt func(func(key string, value int), map[string]int)
var sum int
fn := func(v int, k string) {
sum += v
}
m := map[string]int{"a": 1, "b": 2, "c": 3, "d": 4, "e": 5}
EachStringInt(fn, m)
fmt.Printf("%#v\n", sum) //15
Of note is the ability to close over variables within the calling scope.
Every
Map
Map func([]A, func(A) B) []B
Applies the given function to each element of a slice, returning a slice of results
The base Map function accepts interface{} types and returns []interface{}
// Map func(interface{}, func(interface{}) interface{}) []interface{}
s := []string{"a", "b", "c", "d"}
fn := func(s interface{}) interface{} {
return s.(string) + "!"
}
m := un.Map(ToI(s), fn)
fmt.Println(m) //["a!", "b!", "c!", "d!"]
Typed Maps can be defined using a function type and the MakeMap helper.
Map func([]A, func(A) B) []B
var SMap func([]string, func(string) string) []string
un.MakeMap(&SMap)
m := un.SMap(s, fn)
fmt.Println(m) //["a!", "b!", "c!", "d!"]
Of note is the return value of Map is a slice of the return type of the applied function.
Partition
Partition func([]A, func(A) bool) ([]A []A)
Partition splits a slice or map based on the evaluation of the supplied function
The base Partition function accepts interface{} types and returns []interface{}
// Partition func(interface{}, func(interface{}) bool) ([]interface{}, []interface{})
s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fn := func(i interface{}) bool {
return (i.(int) % 2) == 1
}
odd, even := un.Partition(s, fn)
fmt.Println(odd) //[1, 3, 5, 7, 9]
fmt.Println(even) //[2, 4, 6, 8, 10]
Typed Partitions can be defined using a function type and the MakePartition helper.
// Partition func([]A, func(A) bool) ([]A []A)
var IPartition func([]int, func(int) bool) ([]int, []int)
un.MakePartition(&IPartition)
s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fn := func(i int) bool {
return (i % 2) == 1
}
odd, even := un.IPartition(s, fn)
fmt.Println(odd) //[1, 3, 5, 7, 9]
fmt.Println(even) //[2, 4, 6, 8, 10]
Contains returns true if an object is in a slice.
o := "a"
s := []string{"a", "b", "c"}
b := un.Contains(s, o)
fmt.Println(b) //true
ToI converts a slice of arbitrary type []T into a slice of []interfaces{}
s := []int{1, 1, 3, 5, 8, 13}
i := un.ToI(s)
Notes
I am aware that the whole idea is not particularly very TheGoWayâ¢, but it is useful as a learning exercise, and it is useful for moving fast and optimising later.
Top Related Projects
💥 A Lodash-style Go library based on Go 1.18+ Generics (map, filter, contains, find...)
A modern Go utility library which provides helpers (map, find, contains, filter, ...)
🍕 Enjoy a slice! A utility library for dealing with slices and maps that focuses on type safety and performance.
A comprehensive, efficient, and reusable util function library of Go.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual Copilot