generics

package module
v0.0.0-...-e4e1d86 Latest Latest
Warning

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

Go to latest
Published: Apr 9, 2024 License: MIT Imports: 9 Imported by: 5

README

generics

Generic code helpers for Go 1.18+ onwards

GoDoc GitHub issues GitHub commit activity


Overview

The generics package is a series of helpers for writing generic Go code that provides a series of functions that don't exist in the mainline package set.

About ZeroFlucs

ZeroFlucs is a B2B provider of pricing technology for Sportsbooks/wagering service providers globally. We use Open-Source software through our platform stack. This, along with other projects is made available through our zeroflucs-given Github profile on MIT licensing. To learn more you can visit:

Why Does this Exist?

When writing Go code for Go 1.17 or below, we've all written more than our fair share of methods to check "does this slice contain a thing", or "give me the first item matching a predicate". This package contains a roll-up of helpers and methods, as well as generic collection types that enable a lot of this boilerplate code to be removed.

Key attributes:

  • Context support for filters/mappers (Optional)
  • Does not mutate input during operation.

All code is covered 100% by tests for expected behaviour. Where filters or mappers are used methods are provided with and without context support.

Slice Queries

package generics_test

import (
	"fmt"

	"github.com/zeroflucs-given/generics/query"
)

func main() {
	inputs := []int{
		1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
	}
	query := query.Slice(inputs)

	result := query.
		Skip(5).
		TakeUntil(func(index int, v int) bool {
			return v >= 15
		}).
		Filter(func(index int, v int) bool {
			return v%2 == 0 // Evens only
		}).
		Reverse().
        ToSlice() // Back to a clean slice type

	fmt.Println(result) // [14, 12, 10, 8, 6]
}

But what about Contexts?

Don't worry, we've got you. Use .WithContext(ctx) on the query chain and the entire subtree is now context aware. Any materialization functions that emit an actual value will now return an extra error argument. Operations are lazy, and will skip over the remainder of any query chain once the first error has occured.

package generics_test

import (
	"context"
	"fmt"

	"github.com/zeroflucs-given/generics/query"
)

func main() {
	inputs := []int{
		1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
	}
	query := query.Slice(inputs)
	ctx := context.Todo()

	result, err := query.
		WithContext(ctx).
		Skip(5).
		TakeUntil(func(ctx context.Context, index int, v int) (bool, error) {
			return v >= 15, nil
		}).
		Filter(func(ctx context.Context, index int, v int) (bool, error)  {
			return v%2 == 0, nil // Evens only
		}).
		Reverse().
        ToSlice() // Back to a clean slice type

	fmt.Println(result) // [14, 12, 10, 8, 6]
	fmt.Println(err) // nil
}

When dealing with slice operations, the following design rules apply:

  • Operations that return a single value will return the type default if no item matches (i.e. 0 for numeric types, empty string or nil for objects)
  • Operations that accept multiple filters combine these filters into a logical AND by default.
  • If no filters are applied, every item is assumed to pass.
  • Context aware operations only exist where it makes sense (i.e. Take first 5 doesn't need a context, whereas take with filtering does)

Operation Detail

All / AllContext

Returns true if all items in the slice match the filter. Will return true for an empty slice as no items fail the predicate.

Any / AnyContext

Returns true if any item in the slice passes.

Combinations

Returns all combinations (note: not permutations) of items of length N over the slice.

Chunk

Uniformly distributes (or "chunks") a slice over n buckets.

ChunkMap

Uniformly distributes (or "chunks") a map over n buckets.

CombinationsFiltered

Returns all combinations of items of length N over the slice, where the members of the slice can be filtered. The return type contains references back to the original input list indicies.

Concatenate

Joins N slices of items together in the given order. Allocates a new slice.

Contains

Returns true if the slice contains the specified value T, false otherwise. Uses standard equality operator to compare types.

Count / CountContext

Returns the count of items matching the input filters.

Cut

Takes a slice and returns the head of the slice, plus a second value for the remainder of the slice.

DefaultIfEmpty

Given a slice, if the slice is empty or nil will create a slice of a single default item.

Distinct

Sorts and removes any duplicate elements from a slice, returning a new copy. The input slice is unchanged.

DistinctFunc

Similar to Distinct but takes a custom comparison function.

DistinctStable

Similar to Distinct but keeps the original order of the elements.

DistinctStableFunc

Similar to DistinctStable but takes a custom hash function. Hash collisions are ignored.

ExecuteOnce

Takes a function to be run at a later time and caches its result for retrieval many times. Subsequent retrievals will block until either their context is cancelled, or the task completes.

Filter / FilterContext

Creates a filtered set of the values in the slice, using a filter function.

First / FirstWithContext

Returns the first item of the slice that matches the filters. If no value matches, returns the type default.

FirstIndexOf

Returns the first index of a value in a typed slice, or -1 if not present.

Group / GroupWithContext

Uses a mapper function to assign input values to buckets.

If

If returns the equivalent value based on the predicate. This is an eager evaluation of both sides and not a true ternary operator.

Last / LastWithContext

Returns the last item of the slice that matches the filter.

LastIndexOf

Returns the index of the last occurrence of a value in the slice, or -1 if not present.

Map / MapWithContext

Runs the specified mapper over each element of the input slice, creating an output slice of a different type.

Mutate

Allows mutation of the slice elements, but the output must be of the same type as the original elements.

PointerTo

Returns a pointer reference to the input. Useful for lots of APIs that use string, integer pointers to differentiate between empty and absent.

PointerOrNil

Returns a pointer to the input, unless the input is the default value for its type (i.e. 0, empty string etc). In that scenario will return nil.

Reverse

Creates a reverse-sorted version of the input slice.

Skip

Skip the first N elements of the slice.

SkipUntil / SkipUntilWithContext

Skip items from the front of the slice until the predicate returns true.

SkipWhile / SkipWhileWithContext

Skips items from the front of the slice until the predicate returns false.

Take

Take the next N elements of the slice.

TakeUntil / TakeUntil

Take items from the slice until the filter function returns true.

TakeWhile / TakeWhileWithContext

Take items from the slice until the filter function returns false.

ToMap / ToMapWithContext

Converts a slice of values to Go map, using mappers for the key and values respectively.

ValueOrDefault

If a pointer is set, will return the dereferenced value. Otherwise returns the default value of the target type.

Slice Aggregations


Min

Returns the minimum value from the input slice. Returns 0 if no values.

Max

Returns the maximum value from the input slice. Returns 0 if no values.

Sum

Returns the total sum of the values. Note that when handling large values, you may overflow your input type.

Map Operations

The following map operation helpers exist in the generics package:

KeyValuesToMap

Assembles a map from a slice of key-value pairs.

Keys

Returns a slice of the key members of the map.

MapValues / MapValuesWithContext

Allows translation of a maps items to other values.

ToKeyValues

Converts a map into a slice of key-value pairs. As per Go handling of maps, the order of output here is not in a guaranteed order.

Values

Return a slice of the value members of the map.

Error Checking

Must

Returns the first value in a pair of (T, error) if there is no error. Otherwise will panic.

Filter Building

The filtering sub-package contains various constructs used to filter values.

True / TrueWithContext

A constant value filter that always returns true. Useful for testing.

False / FalseWithContext

A constant value filter that always returns false. Useful for testing.

And(...filters) / AndWithContext

Creates a composite AND filter for multiple conditions. An empty set of filters is a true.

Or(...filters) / OrWithContext

Creates a composite OR filter for multiple conditions. An empty set of filters is a false.

Not(filter) / NotWithContext

Creates a NOT/inverted version of the specified filter to allow for negative checking.

Wrap

Takes a non-context aware filter, but makes it compatible with code that expects contexts.

Documentation

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrEmptySlice = errors.New("operator cannot be applied to empty slice")

ErrEmptySlice indicates an error applying an operator to an empty slice without a suitable default or other fallback.

View Source
var ErrTupleHasError = errors.New("the tail member of the input tuple contained an error")

ErrTupleHasError is our

Functions

func All

func All[T any](items []T, filters ...filtering.Expression[T]) bool

All returns true if no item in the list fails to meet the predicates. If multiple predicates are passed, they are treated as logical AND operations.

func AllWithContext

func AllWithContext[T any](ctx context.Context, items []T, filters ...filtering.ExpressionWithContext[T]) (bool, error)

AllWithContext is a context aware function that returns true if no item in the list fails to meet the predicate. If multiple predicate passed, they are treated as logical AND operations.

func Any

func Any[T any](items []T, filters ...filtering.Expression[T]) bool

Any returns true if any item in the slice matches the filter. If multiple predicates are passed, they are treated as logical AND operations.

func AnyWithContext

func AnyWithContext[T any](ctx context.Context, items []T, filters ...filtering.ExpressionWithContext[T]) (bool, error)

AnyWithContext is a context aware function that returns true if any item in the slice matches the filter.

func Chunk

func Chunk[T any](items []T, n int) [][]T

Chunk will uniformly distribute the given over n bins/buckets. Note that buckets are slices referencing the same memory.

func ChunkMap

func ChunkMap[K comparable, V any](items map[K]V, n int) []map[K]V

ChunkMap will uniformly distribute the given map over n bins/buckets.

func Combinations

func Combinations[T any](items []T, size int) [][]T

Combinations generates all combinations of the input objects at the specified size.

func CombinationsFiltered

func CombinationsFiltered[T any](items []T, size int, filter filtering.Expression[T]) [][]IndexedItem[T]

CombinationsFiltered returns the combinations of items, but applies a filter to the items. The returned indexed items represent the original positions in the raw list.

func Compact

func Compact[T any, K comparable](input []T, keyMapper SliceMapperExpression[T, K]) []T

Compact takes a slice and compacts it, by reducing to only the last occurrence of a given key. This is akin to Kafka topic compaction, and used for scenarios where you have a slice of mixed updates but want to take only the final update for a given predicate. The result order is determined by the final position(s) of the surviving elements relative to each other.

func Concatenate

func Concatenate[T any](lists ...[]T) []T

Concatenate all lists together

func Contains

func Contains[T comparable](items []T, value T) bool

Contains returns true if the set contains the specified value

func Count

func Count[T any](items []T, filters ...filtering.Expression[T]) int

Count returns how many items pass the filters

func CountWithContext

func CountWithContext[T any](ctx context.Context, items []T, filters ...filtering.ExpressionWithContext[T]) (int, error)

CountWithContext counts how many items pass the filter

func Cut

func Cut[T any](items []T) (T, []T)

Cut removes the head of a list, returning it and the remainder of the list. If the input list is empty, cut returns the type-default.

func DefaultIfEmpty

func DefaultIfEmpty[T any](items []T, def T) []T

DefaultIfEmpty checks to see if the specified slice is empty and if so, creates a slice with a specified default value.

func Distinct

func Distinct[S ~[]E, E cmp.Ordered](in S) S

Distinct sorts and removes any duplicate elements from a slice, returning a new copy. The input slice is unchanged.

func DistinctFunc

func DistinctFunc[S ~[]E, E any](x S, cmp func(a, b E) int) S

DistinctFunc is like Distinct but uses a custom comparison function on each pair of elements.

DistinctFunc requires that cmp is a strict weak ordering. See https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings.

func DistinctStable

func DistinctStable[S ~[]E, E comparable](in S) S

DistinctStable removes any duplicates from a slice, keeping iteration order and returning a new copy. The input slice is unchanged.

func DistinctStableFunc

func DistinctStableFunc[S ~[]E, E any](in S, hash func(val E) uint64) S

DistinctStableFunc is like DistinctStable but uses a custom hash function to deduplicate the elements.

Elements with the same hash are considered to be equal, collisions are NOT considered.

func Except

func Except[T comparable](items []T, exclusions ...T) []T

Except

func Filter

func Filter[T any](items []T, filters ...filtering.Expression[T]) []T

Filter filters item in a list

func FilterWithContext

func FilterWithContext[T any](ctx context.Context, items []T, filters ...filtering.ExpressionWithContext[T]) ([]T, error)

FilterWithContext filters item in a list

func First

func First[T any](items []T, filters ...filtering.Expression[T]) T

First item in a slice that passes the filters. If multiple filters are set, they are treated as a logical AND. If no filters are set, will return the first item in the slice. If no items match, the type default is returned.

func FirstIndexOf

func FirstIndexOf[T comparable](v T, items []T) int

FirstIndexOf returns the first index of an item in the slice

func FirstWithContext

func FirstWithContext[T any](ctx context.Context, items []T, filters ...filtering.ExpressionWithContext[T]) (T, error)

FirstWithContext gets the first item in a slice that passes the filters. If multiple filters are set, they are treated as a logical AND. If no filters are set, will return the first item in the slice. If no items match, the type default is returned.

func Group

func Group[T any, K comparable](items []T, keyMapper SliceMapperExpression[T, K]) map[K][]T

Group rolls up items into groups based on a mapper function that provides a key per item

func GroupWithContext

func GroupWithContext[T any, K comparable](ctx context.Context, items []T, keyMapper SliceMapperExpressionWithContext[T, K]) (map[K][]T, error)

GroupWithContext rolls up items into groups based on a mapper function that provides a key per item

func If

func If[T any](condition bool, ifTrue, ifFalse T) T

If returns the equivalent value based on the predicate

func Intersect

func Intersect[T comparable](items []T, others ...T) []T

Intersect

func KeyValuesToMap

func KeyValuesToMap[K comparable, V any](input []KeyValuePair[K, V]) map[K]V

KeyValuesToMap converts a slice of key-value pairs to a map

func Keys

func Keys[K comparable, V any](input map[K]V) []K

Keys gets the set of keys in a map

func Last

func Last[T any](items []T, filters ...filtering.Expression[T]) T

Last item in a slice that matches the specified filters. Returns the type default if none found.

func LastIndexOf

func LastIndexOf[T comparable](v T, items []T) int

LastIndexOf returns the last index of an item in the slice

func LastWithContext

func LastWithContext[T any](ctx context.Context, items []T, filters ...filtering.ExpressionWithContext[T]) (T, error)

LastWithContext item in a slice that matches the specified filters. Returns the type default if none found.

func Map

func Map[T any, V any](items []T, mapper SliceMapperExpression[T, V]) []V

Map converts values in a slice from one type to another

func MapValues

func MapValues[K comparable, V any, NV any](input map[K]V, mapper func(k K, v V) NV) map[K]NV

MapValues translates all values in a map to new values

func MapValuesWithContext

func MapValuesWithContext[K comparable, V any, NV any](ctx context.Context, input map[K]V, mapper func(ctx context.Context, k K, v V) (NV, error)) (map[K]NV, error)

MapValuesWithContext translates all values in a map to new values

func MapWithContext

func MapWithContext[T any, V any](ctx context.Context, items []T, mapper SliceMapperExpressionWithContext[T, V]) ([]V, error)

MapWithContext executes a mapper over the members of a slice using the specified context

func Max

func Max[T Numeric](items []T) T

Max gets the maximum of numeric values. If the slice is empty then a value of 0 is returned.

func MaxConcurrent

func MaxConcurrent[I any, O any](n int, execute func(ctx context.Context, in I) (O, error)) func(context.Context, I) (O, error)

MaxConcurrent limits the maximum number of executions in flight for a function by using a pool of tokens. Each time a request starts executing, a token is obtained - then subsequently released. The function is panic safe, and will release tokens cleanly through the use of a defer.

func Min

func Min[T Numeric](items []T) T

Min gets the minimum of numeric values. If the slice is empty then a value of 0 is returned.

func Must

func Must[T any](v T, err error) T

Must enforces that a value/error pair contains no error, and returns the value. If an error is present, the code will panic. If you require a default value instead use MustDefault instead.

Example
package main

import (
	"errors"
	"fmt"

	"github.com/zeroflucs-given/generics"
)

func main() {
	// Any function that returns a value/error tuple
	fn := func(fail bool) (string, error) {
		if fail {
			return "", errors.New("boom")
		}
		return "Hello", nil
	}

	// Instead of assigning to two variables, can step straight to value
	v := generics.Must(fn(false))
	fmt.Println(v)
}
Output:

func PointerOrNil

func PointerOrNil[T comparable](v T) *T

PointerOrNil returns a nil if the value is its type default, or a pointer to the value if it's set.

func PointerTo

func PointerTo[T any](v T) *T

PointerTo returns the pointer to a value

func Reverse

func Reverse[T any](items []T) []T

Reverse creates a reversed copy of the slice

func Skip

func Skip[T any](items []T, n int) []T

Skip the first N items of the slice.

func Sort

func Sort[T Comparable](in []T) []T

Sort creates a sorted version of the list, whilst leaving the original list intact.

func SortDescending

func SortDescending[T Comparable](in []T) []T

SortDescending creates a reverse sorted version of the list, whilst leaving the original list intact.

func Sum

func Sum[T Numeric](items []T) T

Sum numeric values

func Take

func Take[T any](items []T, n int) []T

Take up to N items from the slice

func TakeUntil

func TakeUntil[T any](items []T, filters ...filtering.Expression[T]) []T

TakeUntil takes items from the slice until the first item that passes the predicate.

func TakeUntilWithContext

func TakeUntilWithContext[T any](ctx context.Context, items []T, filters ...filtering.ExpressionWithContext[T]) ([]T, error)

TakeUntilWithContext takes items from the slice until the first item that passes the predicate.

func TakeWhile

func TakeWhile[T any](items []T, filters ...filtering.Expression[T]) []T

TakeWhile takes items from the slice until the first item that fails the predicate.

func TakeWhileWithContext

func TakeWhileWithContext[T any](ctx context.Context, items []T, filters ...filtering.ExpressionWithContext[T]) ([]T, error)

TakeWhileWithContext takes items from the slice until the first item that fails the predicate.

func ToMap

func ToMap[T any, K comparable, V any](items []T, keyMapper SliceMapperExpression[T, K], valueMapper SliceMapperExpression[T, V]) map[K]V

ToMap converts a slice of items into a dictionary using mappers for the key and value pairs. If multiple items yield the same key, the last key in the set will be the one kept.

func ToMapWithContext

func ToMapWithContext[T any, K comparable, V any](ctx context.Context, items []T, keyMapper SliceMapperExpressionWithContext[T, K], valueMapper SliceMapperExpressionWithContext[T, V]) (map[K]V, error)

ToMapWithContext converts a slice of items into a dictionary using mappers for the key and value pairs. If multiple items yield the same key, the last key in the set will be the one kept. If any mapper fails, the operation as a whole fails.

func ValueOrDefault

func ValueOrDefault[T any](v *T) T

ValueOrDefault safely resolves a pointer to a type

func ValueOrError

func ValueOrError[T any](value T, err error) (T, error)

ValueOrError returns an error only if the error is set, otherwise returns the value and nil. This replaces the value with the default/nil for its type.

func Values

func Values[K comparable, V any](input map[K]V) []V

Values gets the set of keys in a map

Types

type CachedTask

type CachedTask[T any] struct {
	// contains filtered or unexported fields
}

CachedTask holds a packet of work that is executed once, and the result retrieved many times.

func ExecuteOnce

func ExecuteOnce[T any](fn func(ctx context.Context) (*T, error)) CachedTask[T]

ExecuteOnce creates a new CachedTask for which the work is provided by fn.

func (*CachedTask[T]) Get

func (t *CachedTask[T]) Get(ctx context.Context) (value *T, err error)

Get executes the task using the current context, or returns the value if it has already been calculated.

type Comparable

type Comparable interface {
	Numeric | ~string
}

Comparable is the set of comparable types for our operators

type IndexedItem

type IndexedItem[T any] struct {
	Index int `json:"index"`
	Item  T   `json:"item"`
}

IndexedItem is an item that is indexed

type KeyValuePair

type KeyValuePair[K comparable, V any] struct {
	Key   K
	Value V
}

KeyValuePair is a pairing of key/values, for when we have to represent map sets as a list/slice.

func SortedByKey

func SortedByKey[K Comparable, V any](input map[K]V) []KeyValuePair[K, V]

SortedByKey sorts a map by key and returns it as a slice of key-value pairs enabling working with the map in a reliable/repeatable order

func ToKeyValues

func ToKeyValues[K comparable, V any](input map[K]V) []KeyValuePair[K, V]

ToKeyValues converts a map into a set of key/value pairs

type Numeric

type Numeric interface {
	~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~float32 | ~float64
}

Numeric is a type that contains all the available numeric types known to go

type Resolver

type Resolver[K comparable, V any] struct {
	WorkFn ResolverWorkFn[K, V] // Work function
	// contains filtered or unexported fields
}

Resolver is a type that lets us do hard work once, and share the results across multiple callers. Also handles parallelism.

func (*Resolver[K, V]) Get

func (r *Resolver[K, V]) Get(ctx context.Context, key K) (V, error)

Get fetches the resolved state of the object

type ResolverWorkFn

type ResolverWorkFn[K comparable, V any] func(ctx context.Context, key K) (V, error)

ResolverWorkFn is a function that resolvers use to do the underlying work

type SliceMapperExpression

type SliceMapperExpression[T any, V any] func(index int, input T) V

SliceMapperExpression is a type that represents a generic mapper

type SliceMapperExpressionWithContext

type SliceMapperExpressionWithContext[T any, V any] func(ctx context.Context, index int, v T) (V, error)

SliceMapperExpressionWithContext is a type used to represent a context aware generic mapper

Jump to

Keyboard shortcuts

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