Enjoy a slice! pie
is a code generator for dealing with slices that
focuses on type safety, performance and immutability.
go get -u github.com/elliotchance/pie
pie
ships with some slice types ready to go (pun intended). These include:
These can be used without needing go generate
. For example:
package main
import (
"fmt"
"strings"
"github.com/elliotchance/pie/pie"
)
func main() {
name := pie.Strings{"Bob", "Sally", "John", "Jane"}.
FilterNot(func (name string) bool {
return strings.HasPrefix(name, "J")
}).
Map(strings.ToUpper).
Last()
fmt.Println(name) // "SALLY"
}
Annotate the slice type in your source code:
type Car struct {
Name, Color string
}
//go:generate pie Cars.*
type Cars []Car
Run go generate
. This will create a file called cars_pie.go
. You should
commit this with the rest of your code. Run go generate
any time you need to
add more types.
Now you can use the slices:
cars := Cars{
{"Bob", "blue"},
{"Sally", "green"},
{"John", "red"},
{"Jane", "red"},
}
redCars := cars.Filter(func(car Car) bool {
return car.Color == "red"
})
// redCars = Cars{{"John", "red"}, {"Jane", "red"}}
Or, more complex operations can be chained:
cars.FilterNot(func (car Car) {
return strings.HasPrefix(car.Name, "J")
}).
Map(func (car Car) Car {
car.Name = strings.ToUpper(car.Name)
return car
}).
Last()
// Car{"SALLY", "green"}
Some functions that compare elements, such as Contains will use the following method if it is available on the element type:
func (a ElementType) Equals(b ElementType) bool
The ElementType
must be the same for the receiver and argument and it must
return a bool. Be careful to create the function on the pointer or non-pointer
type that is used by the slice.
Here is a minimal example:
type Car struct {
Name, Color string
}
type Cars []*Car // ElementType is *Car
func (c *Car) Equals(c2 *Car) bool {
return c.Name == c2.Name
}
Some functions that need elements to be represented as strings, such as
Strings()
will try to use the fmt.Stringer
interface. If it's not available
then it will fallback to:
fmt.Sprintf("%v", element)
The .*
can be used to generate all functions. This is easy to get going but
creates a lot of unused code. You can limit the functions generated by chaining
the function names with a dot syntax, like:
//go:generate pie myInts.Average.Sum myStrings.Filter
This will only generate myInts.Average
, myInts.Sum
and myStrings.Filter
.
Below is a summary of the available functions.
The letters in brackets indicate:
-
E: The function will use the
Equals
method if it is available. See Custom Equality. -
S: The function will use the
String
method if it is available. See Custom Stringer.
Function | String | Number | Struct | Maps | Big-O | Description |
---|---|---|---|---|---|---|
Abs |
✓ | n | Abs will return the absolute value of all values in the slice. | |||
All |
✓ | ✓ | ✓ | n | All will return true if all callbacks return true. If the list is empty then true is always returned. | |
Any |
✓ | ✓ | ✓ | n | Any will return true if any callbacks return true. If the list is empty then false is always returned. | |
Append |
✓ | ✓ | ✓ | n | A new slice with the elements appended to the end. | |
AreSorted |
✓ | ✓ | n | Check if the slice is already sorted. | ||
AreUnique |
✓ | ✓ | n | Check if the slice contains only unique elements. | ||
Average |
✓ | n | The average (mean) value, or a zeroed value. | |||
Bottom |
✓ | ✓ | ✓ | n | Gets n elements from bottom. | |
Contains (E) |
✓ | ✓ | ✓ | n | Check if the value exists in the slice. | |
Diff (E) |
✓ | ✓ | ✓ | n² | Diff returns the elements that needs to be added or removed from the first slice to have the same elements in the second slice. | |
DropTop |
✓ | ✓ | ✓ | n | A new slice after dropping the top n elements. | |
Each |
✓ | ✓ | ✓ | n | Perform an action on each element. | |
Extend |
✓ | ✓ | ✓ | n | A new slice with the elements from each slice appended to the end. | |
Filter |
✓ | ✓ | ✓ | n | A new slice containing only the elements that returned true from the condition. | |
FilterNot |
✓ | ✓ | ✓ | n | A new slice containing only the elements that returned false from the condition. | |
FindFirstUsing |
✓ | ✓ | ✓ | n | The index of the first element when the callback returns true or -1 if the callback does not return true, meaning that it did not match any element in the slice. It also can return -1 if the slice is empty or if it is nil. | |
First |
✓ | ✓ | ✓ | 1 | The first element, or a zeroed value. | |
FirstOr |
✓ | ✓ | ✓ | 1 | The first element, or a default value. | |
Float64s (S) |
✓ | ✓ | ✓ | n | Transforms each element into a float64. | |
Intersect |
✓ | ✓ | n | Intersect returns elements which exists in all slices. | ||
Ints (S) |
✓ | ✓ | ✓ | n | Transforms each element into an int. | |
Join |
✓ | n | A string from joining each of the elements. | |||
JSONBytes |
✓ | ✓ | ✓ | n | The JSON encoded bytes. | |
JSONString |
✓ | ✓ | ✓ | n | The JSON encoded string. | |
Keys |
✓ | n | Returns all keys in the map (in random order). | |||
Last |
✓ | ✓ | ✓ | 1 | The last element, or a zeroed value. | |
LastOr |
✓ | ✓ | ✓ | 1 | The last element, or a default value. | |
Len |
✓ | ✓ | ✓ | 1 | Number of elements. | |
Map |
✓ | ✓ | ✓ | n | A new slice where each element has been mapped (transformed). | |
Max |
✓ | ✓ | n | The maximum value, or a zeroes value. | ||
Median |
✓ | n | Median returns the value separating the higher half from the lower half of a data sample. | |||
Min |
✓ | ✓ | n | The minimum value, or a zeroed value. | ||
Product |
✓ | n | Product of all elements. | |||
Random |
✓ | ✓ | ✓ | 1 | Select a random element, or a zeroed value if empty. | |
Reduce |
✓ | ✓ | n | Continously apply a function to the slice (left to right), reducing it to a single value. | ||
Reverse |
✓ | ✓ | ✓ | n | Reverse elements. | |
Send |
✓ | ✓ | ✓ | n | Send all element to channel. | |
Sequence |
✓ | n | Generates sequence of numbers. | |||
SequenceUsing |
✓ | ✓ | ✓ | n | Generates new sequence of elements in range using function which will be creator. | |
Shuffle |
✓ | ✓ | ✓ | n | Returns a new shuffled slice. | |
Sort |
✓ | ✓ | n⋅log(n) | Return a new sorted slice. | ||
SortStableUsing |
✓ | ✓ | n⋅log(n) | Return a new sorted slice, using custom comparator, keeping the original order of equal elements. | ||
SortUsing |
✓ | ✓ | n⋅log(n) | Return a new sorted slice, using custom comparator. | ||
Strings (S) |
✓ | ✓ | ✓ | n | Transforms each element into a string. | |
SubSlice |
✓ | ✓ | ✓ | n | Return the subSlice from start to end(excluded). | |
Sum |
✓ | n | Sum (total) of all elements. | |||
Top |
✓ | ✓ | ✓ | n | Gets several elements from top(head of slice). | |
ToStrings |
✓ | ✓ | ✓ | n | Transforms each element to a string. | |
Unique |
✓ | ✓ | n | Return a new slice with only unique elements. | ||
Values |
✓ | n | Returns all values in the map (in random order). |
pie
supports many Go versions, all the way back to Go 1.8.
-
Type safety. I never want to hit runtime bugs because I could pass in the wrong type, or perform an invalid type case out the other end.
-
Performance. The functions need to be as fast as native Go implementations otherwise there's no point in this library existing.
-
Nil-safe. All of the functions will happily accept nil and treat them as empty slices. Apart from less possible panics, it makes it easier to chain.
-
Immutable. Functions never modify inputs, unlike some built-ins such as
sort.Strings
.
Pull requests are always welcome.
Here is a comprehensive list of steps to follow to add a new function:
-
Create a new file in the
functions/
directory. The file should be named the same as the function. You must include documentation for your function. -
Update
functions/main.go
to register the new function by adding an entry toFunctions
. Make sure you choose the correctFor
value that is appropriate for your function. -
Run
go generate ./... && go install && go generate ./...
. The firstgenerate
is to create the pie templates,install
will update your binary for the annotations and the secondgenerate
will use the newly created templates to update the generated code for the internal types. If you encounter errors with your code you can safely rerun the command above. -
If you chose
ForAll
orForStructs
, then you must add unit tests topie/carpointers_test.go
andpie/cars_test.go
. -
If you chose
ForAll
,ForNumbersAndStrings
orForNumbers
, then you must add unit tests topie/float64s_test.go
andpie/ints_test.go
. -
If you chose
ForAll
orForStrings
, then you must add unit tests topie/strings_test.go
. -
If you chose
ForMaps
, then you must add unit tests topie/currencies.go
. -
Update the README to list the new functions.
I wanted to pick a name for the project that was short and had an associated emoji. I liked pie, but then I found out that the pie emoji is not fully supported everywhere. I didn't want to change the name of the project to cake, but pizza pie still made sense. I'm not sure if I will change it back to a pie later.