hoff

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 17, 2022 License: MIT Imports: 6 Imported by: 2

README

hoff: Higher Order Functions (and Friends) Go Reference

Golang 1.18+ implementations of common methods/data structures using Go Generics

Requirements

  • Go 1.18 or newer (must support Generics)

In Development

Please note: this package is still under development and may change in the future. We will attempt to maintain as much backwards compatibility as possible for future changes, but this is still a v0.x release and things might change.

Mash that Star button and OBLITERATE the Watch button to follow our changes.

Running tests/benchmarks

Run the tests and benchmarks for the project using this command:

go test -v -bench=. -race ./...

CI/CD and Github Actions

This project is configured to use GH Actions to automatically test/benchmark the project whenever pushes occur. See the .github/workflows folder for all the details.

Contributing

Contributors must sign the Shopify CLA before your PR can be accepted/merged.

Authors

License

hoff is released under the MIT License.

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Chunk

func Chunk[T any](actions []T, batchSize int) [][]T

Chunk takes an input array of T elements, and "chunks" into groups of chunkSize elements. If the input array is empty, or the batchSize is 0, return an empty slice of slices. Adapted to generics from https://github.com/golang/go/wiki/SliceTricks#batching-with-minimal-allocation. Examples: Chunk([]int{1, 2, 3, 4, 5}, 2) = [[1, 2] [3, 4], [5]] Chunk([]int{}, 2) = [] Chunk([]int{1, 2, 3}, 0) = [].

func Fill

func Fill[T any](t T, num int) []T

func Filter

func Filter[T any](arr []T, fn func(T) bool) (out []T)

Filter takes an array of T's and applies the callback fn to each element. If fn returns true, the element is included in the returned collection, if false it is excluded. Example: [1, 2, 3].Filter(<number is odd?>) = [1, 3].

func FilterContext

func FilterContext[T any](
	ctx context.Context,
	arr []T,
	fn func(ctx context.Context, elem T) bool,
) (out []T)

FilterContext is "context aware" and will pass the parent func's ctx param along to the callback fn.

func FilterContextError

func FilterContextError[T any](
	ctx context.Context,
	arr []T,
	fn func(ctx context.Context, elem T) (bool, error),
) (out []T, err error)

FilterContextError combines both "FilterContext" and "FilterError" approaches, passing through the ctx, and stopping and returning the first error encountered.

func FilterError

func FilterError[T any](
	arr []T,
	fn func(elem T) (bool, error),
) (out []T, err error)

FilterError will return early with the first error encountered in the callback fn, if any.

func FlatMap

func FlatMap[In, Out any](arr []In, fn func(In) []Out) (out []Out)

FlatMap applies a transformation to an array of elements and returns another array with the transformed result.

Example
fmt.Println(FlatMap([]string{"abcd", "efg"}, func(s string) []int { return []int{len(s)} }))
Output:

[4 3]

func FlatMapContext

func FlatMapContext[In, Out any](ctx context.Context, arr []In, fn func(context.Context, In) []Out) (out []Out)

FlatMapContext applies the FlatMap transformation while, at the same time, shares a context with the transforming function.

func FlatMapContextError

func FlatMapContextError[In, Out any](ctx context.Context, arr []In, fn func(context.Context, In) ([]Out, error)) (out []Out, err error)

FlatMapContextError applies the FlatMap transformation while, at the same time, shares a context with the transforming function. If one of the transformations fails, it will return early.

func FlatMapError

func FlatMapError[In, Out any](arr []In, fn func(In) ([]Out, error)) (out []Out, err error)

FlatMapError applies a transformation to an array of elements and returns another array with the transformed result. If one of the transformations fails, it will return early.

Example
properPrefixes := func(s string) ([]string, error) {
	if len(s) < 2 {
		return nil, fmt.Errorf("string '%s' has no proper prefixes", s)
	}

	res := make([]string, len(s)-1)
	for i := 1; i < len(s); i++ {
		res[i-1] = s[0:i]
	}

	return res, nil
}

var err error
prefixes, _ := FlatMapError([]string{"abcd", "efg"}, properPrefixes)
fmt.Println(prefixes)
prefixes, err = FlatMapError([]string{"abcd", "x", "efg"}, properPrefixes)
fmt.Println(err)
Output:

[a ab abc e ef]
string 'x' has no proper prefixes

func ForEach

func ForEach[T any](arr []T, fn func(T))

func ForEachContext

func ForEachContext[T any](ctx context.Context, arr []T, fn func(context.Context, T))

func ForEachContextError

func ForEachContextError[T any](ctx context.Context, arr []T, fn func(context.Context, T) error) error

func Map

func Map[In, Out any](arr []In, fn func(In) Out) []Out

Map implements the basic map function.

Example
arr := []int{0, 1, 2, 3}
fn := func(n int) int { return n * 2 }
results := Map(arr, fn)
fmt.Println(results)
Output:

[0 2 4 6]

func MapConcurrentError

func MapConcurrentError[In, Out any](
	ctx context.Context,
	arr []In,
	fn func(context.Context, In, int) (Out, error),
) ([]Out, error)

MapConcurrentError is the same as `MapConcurrentError` but returns only the values and, if it happens, the first error.

Example (Failure)
fn := func(ctx context.Context, n, _ int) (int, error) {
	diff := ctx.Value(ctxKey).(int) - n
	if diff == 0 {
		return 0, fmt.Errorf("%d is already the answer", n)
	}
	return n, nil
}

_, err := MapConcurrentError(ctx, []int{1, 2, 42}, fn)

fmt.Println(err.Error())
Output:

MapConcurrentError got an error in index 2, value 42: 42 is already the answer
Example (Panic)
fn := func(ctx context.Context, n, _ int) (int, error) {
	diff := ctx.Value(ctxKey).(int) - n
	if diff == 0 {
		panic(fmt.Sprintf("%d is already the answer", n))
	}
	return n, nil
}

_, err := MapConcurrentError(ctx, []int{1, 2, 42}, fn)

fmt.Println(err.Error())
Output:

MapConcurrentError recovered from panic while processing index 2, value 42: 42 is already the answer
Example (Success)
fn := func(ctx context.Context, n, _ int) (int, error) {
	diff := ctx.Value(ctxKey).(int) - n
	if diff == 0 {
		return 0, fmt.Errorf("%d is already the answer", n)
	}
	return n, nil
}

results, _ := MapConcurrentError(ctx, []int{1, 2, 3}, fn)

fmt.Println(results)
Output:

[1 2 3]

func MapContextError

func MapContextError[In, Out any](
	ctx context.Context,
	arr []In,
	fn func(context.Context, In, int) (Out, error),
) ([]Out, error)

MapContextError is the same as `MapError` but with a context.

Example (Failure)
fn := func(ctx context.Context, n, _ int) (int, error) {
	diff := ctx.Value(ctxKey).(int) - n
	if diff == 0 {
		return 0, fmt.Errorf("%d is already the answer", n)
	}
	return n, nil
}

_, err := MapContextError(ctx, []int{1, 2, 42}, fn)

fmt.Println(err.Error())
Output:

MapContextError got an error in index 2, value 42: 42 is already the answer
Example (Success)
fn := func(ctx context.Context, n, _ int) (int, error) {
	diff := ctx.Value(ctxKey).(int) - n
	if diff == 0 {
		return 0, fmt.Errorf("%d is already the answer", n)
	}
	return n, nil
}

results, _ := MapContextError(ctx, []int{1, 2, 3}, fn)

fmt.Println(results)
Output:

[1 2 3]

func MapError

func MapError[In, Out any](arr []In, fn func(In, int) (Out, error)) ([]Out, error)

MapError is the same as `Map` but for functions that might return an error.

Example (Failure)
fn := func(n, _ int) (int, error) {
	if n > 3 {
		return 0, fmt.Errorf("%d is greater than 3", n)
	}
	return n, nil
}

_, err := MapError([]int{2, 3, 4}, fn)

fmt.Println(err.Error())
Output:

MapError got an error in index 2, value 4: 4 is greater than 3
Example (Success)
fn := func(n, _ int) (int, error) {
	if n > 3 {
		return 0, fmt.Errorf("%d is greater than 3", n)
	}
	return n, nil
}

results, _ := MapError([]int{1, 2, 3}, fn)

fmt.Println(results)
Output:

[1 2 3]

func Pluck

func Pluck[M ~map[K]V, K comparable, V any](maps []M, keys ...K) [][]V

Pluck takes an input array of maps with K keys and V values and "Plucks" the selected keys into an array of arrays of V values. If the input array is empty, or the keys is 0, return an empty slice of slices.

Example
fmt.Println(Pluck([]map[string]any{{"foo": 1}, {"bar": 4}}, "bar"))
Output:

[[<nil>] [4]]

func Reduce

func Reduce[T, Acc any](
	arr []T,
	fn func(acc Acc, elem T, index int) Acc,
	acc Acc,
) Acc

Reduce takes an array of input items, runs the callback on each one and accumulates the result.

Example
a := []int{1, 2, 3, 4, 5}
total := Reduce(a, sumInts, 0)
fmt.Println(total)
Output:

15

func ReduceContext

func ReduceContext[T, Acc any](
	ctx context.Context,
	arr []T,
	fn func(ctx context.Context, acc Acc, elem T, index int) Acc,
	acc Acc,
) Acc

ReduceContext passes the context arg through to the reducer fn.

Example
ctx := context.WithValue(context.Background(), multiplierKey, 2)
a := []int{1, 2, 3, 4, 5}
fmt.Println(ReduceContext(ctx, a, sumIntsCtxMultiplier, 0))
Output:

30

func ReduceContextError

func ReduceContextError[T, Acc any](
	ctx context.Context,
	arr []T,
	fn func(ctx context.Context, acc Acc, elem T, index int) (Acc, error),
	acc Acc,
) (Acc, error)

ReduceContextError combines both ReduceContext and ReduceError.

Example
ctx := context.WithValue(context.Background(), divisorKey, 2)
a := []int{2, 4, 6}
fmt.Println(ReduceContextError(ctx, a, sumIntsCtxDividerError, 0))
Output:

6 <nil>
Example (Error)
ctx := context.WithValue(context.Background(), divisorKey, 0)
a := []int{2, 4, 6}
fmt.Println(ReduceContextError(ctx, a, sumIntsCtxDividerError, 0))
Output:

0 division by zero

func ReduceError

func ReduceError[T, Acc any](
	arr []T,
	fn func(acc Acc, elem T, index int) (Acc, error),
	acc Acc,
) (Acc, error)

ReduceError will stop the reducer when an error is encountered and return the Acc and the error encountered.

Example
a := []int{1, 2, 3}
fmt.Println(ReduceError(a, sumIntsError, 0))
Output:

6 <nil>
Example (Error)
a := []int{1, 2, 3, 4, 5}
fmt.Println(ReduceError(a, sumIntsError, 0))
Output:

6 invalid element (4) encountered

Types

type Result

type Result[T any] struct {
	Value T
	Error error
}

Result holds a result of a concurrent operation.

type Results

type Results[T any] []Result[T]

Results hold a sequence concurrent operation results in order.

func MapConcurrentToResults

func MapConcurrentToResults[In, Out any](
	ctx context.Context,
	arr []In,
	fn func(context.Context, In, int) (Out, error),
) Results[Out]

MapConcurrentToResults is the same as `MapContextError` but applied with concurrency. It returns an slice of results, each one containing the value and the error if any. In spite of concurrency, order is guaranteed.

Example (Failure)
fn := func(ctx context.Context, n, _ int) (int, error) {
	diff := ctx.Value(ctxKey).(int) - n
	if diff == 0 {
		return 0, fmt.Errorf("%d is already the answer", n)
	}
	return n, nil
}

results := MapConcurrentToResults(ctx, []int{1, 2, 42}, fn)

fmt.Println(results.Errors())
Output:

[MapConcurrentToResults got an error in index 2, value 42: 42 is already the answer]
Example (Panic)
fn := func(ctx context.Context, n, _ int) (int, error) {
	diff := ctx.Value(ctxKey).(int) - n
	if diff == 0 {
		panic(fmt.Sprintf("%d is already the answer", n))
	}
	return n, nil
}

results := MapConcurrentToResults(ctx, []int{1, 2, 42}, fn)

fmt.Println(results.Errors())
Output:

[MapConcurrentToResults recovered from panic while processing index 2, value 42: 42 is already the answer]
Example (Success)
fn := func(ctx context.Context, n, _ int) (int, error) {
	diff := ctx.Value(ctxKey).(int) - n
	if diff == 0 {
		return 0, fmt.Errorf("%d is already the answer", n)
	}
	return n, nil
}

results := MapConcurrentToResults(ctx, []int{1, 2, 3}, fn)

fmt.Println(results.Values())
Output:

[1 2 3]

func (Results[T]) Error added in v0.2.0

func (rs Results[T]) Error() error

Error returns nil if the results contain no errors, or an error with the message a comma-separated string of all the errors in the collection.

Example
errStrings := Results[int]{
	{0, nil},
	{1, errors.New("first")},
	{2, errors.New("second")},
}
fmt.Println(errStrings.Error().Error())

noErrors := Results[int]{
	{0, nil},
	{1, nil},
}
fmt.Println(noErrors.Error() == nil, noErrors.Error())
Output:

first, second
true <nil>

func (Results[T]) Errors

func (rs Results[T]) Errors() (errors []error)

Errors returns an array with the errors of all non-valid Results.

Example
errStrings := Results[int]{
	{0, nil},
	{1, errors.New("first")},
	{2, errors.New("second")},
}
fmt.Println(errStrings.Errors())

noErrors := Results[int]{
	{0, nil},
	{1, nil},
}
fmt.Println(noErrors.Errors())
Output:

[first second]
[]

func (Results[T]) HasError

func (rs Results[T]) HasError() bool

HasError returns whether the sequence of results has any errors.

Example
errStrings := Results[int]{
	{0, nil},
	{1, errors.New("first")},
	{2, errors.New("second")},
}
fmt.Println(errStrings.HasError())

noErrors := Results[int]{
	{0, nil},
	{1, nil},
}
fmt.Println(noErrors.HasError())
Output:

true
false

func (Results[T]) Values

func (rs Results[T]) Values() (values []T)

Values returns an array with the values of all non-error Results.

Example
errStrings := Results[int]{
	{0, nil},
	{1, errors.New("first")},
	{2, errors.New("second")},
}
fmt.Println(errStrings.Values())

noErrors := Results[int]{
	{0, nil},
	{1, nil},
}
fmt.Println(noErrors.Values())
Output:

[0]
[0 1]

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL