Documentation
¶
Overview ¶
Package gox contains functions and types which could have been builtin, which could have been part of Go itself.
Most of the functions are eligible for inlining.
Reasonable to "dot-import" the package so identifiers will be directly available, see the package example.
Example ¶
// Pass multiple return values to variadic functions: now := time.Date(2020, 3, 4, 0, 0, 0, 0, time.UTC) fmt.Printf("Year: %d, month: %d, day: %d\n", Wrap(now.Date())...) // Quick "handling" of error: n, err := strconv.Atoi("3") Pie(err) fmt.Println("Parsed:", n)
Output: Year: 2020, month: 3, day: 4 Parsed: 3
Index ¶
- Constants
- func Coalesce[T comparable](values ...T) (v T)
- func Deref[T any](p *T, def ...T) (result T)
- func First[T any](first T, _ ...any) T
- func If[T any](cond bool, vtrue, vfalse T) T
- func IfFunc[T any](cond bool, ftrue func() T, ffalse func() T) T
- func Must[T any](v T, err error) T
- func Pie(err error)
- func Ptr[T any](v T) *T
- func RunEvictor(ctx context.Context, evictorPeriod time.Duration, opCaches ...Evictable)
- func Second[T any](_ any, second T, _ ...any) T
- func Third[T any](_, _ any, third T, _ ...any) T
- func Wrap(vs ...interface{}) []interface{}
- type Evictable
- type OpCache
- type OpCacheConfig
- type Struct2
- type Struct3
- type Struct4
- type Struct5
- type Struct6
Examples ¶
Constants ¶
const DefaultEvictPeriodMinutes = 15
Variables ¶
This section is empty.
Functions ¶
func Coalesce ¶ added in v0.2.0
func Coalesce[T comparable](values ...T) (v T)
Coalesce returns the first non-zero value from listed arguments. Returns the zero value of the type parameter if no arguments are given or all are the zero value. Useful when you want to initialize a variable to the first non-zero value from a list of fallback values.
For example:
hostVal := Coalesce(hostName, os.Getenv("HOST"), "localhost")
Note: the same functionality has been added in Go 1.22 as cmp.Or()
func Deref ¶ added in v0.2.0
func Deref[T any](p *T, def ...T) (result T)
Deref "safely" dereferences a pointer, returns the pointed value. If the pointer is nil, the (first) def is returned. If def is not specified, the zero value of T is returned.
func First ¶ added in v0.2.0
First returns the first argument. Useful when you want to use the first result of a function call that has more than one return values (e.g. in a composite literal or in a condition).
For example:
func f() (i, j, k int, s string, f float64) { return } p := image.Point{ X: First(f()), }
func If ¶
If returns vtrue if cond is true, vfalse otherwise.
Useful to avoid an if statement when initializing variables, for example:
min := If(i > 0, i, 0)
func IfFunc ¶ added in v0.2.0
IfFunc returns the return value of ftrue if cond is true, the return value of ffalse otherwise.
In contrast to If, this can be used to deferred, on-demand evaluation of values depending on the condition.
func Must ¶ added in v0.2.0
Must takes 2 arguments, the second being an error. If err is not nil, Must panics. Else the first argument is returned.
Useful when inputs to some function are provided in the source code, and you are sure they are valid (if not, it's OK to panic). For example:
t := Must(time.Parse("2006-01-02", "2022-04-20"))
func Pie ¶
func Pie(err error)
Pie is a "panic-if-error" utility: panics if the passed error is not nil. Should not be over-used, but may come handy to write code quickly.
func Ptr ¶ added in v0.2.0
func Ptr[T any](v T) *T
Ptr returns a pointer to the passed value.
Useful when you have a value and need a pointer, e.g.:
func f() string { return "foo" } foo := struct{ Bar *string }{ Bar: Ptr(f()), }
func RunEvictor ¶ added in v0.2.0
RunEvictor should be run as a goroutine, it evicts expired cache entries from the listed OpCaches. Returns only if ctx is cancelled.
OpCache has Evict() method, so any OpCache can be listed (does not depend on the type parameter).
func Second ¶ added in v0.2.0
Second returns the second argument. Useful when you want to use the second result of a function call that has more than one return values (e.g. in a composite literal or in a condition).
For example:
func f() (i, j, k int, s string, f float64) { return } p := image.Point{ X: Second(f()), }
func Third ¶ added in v0.2.0
Third returns the third argument. Useful when you want to use the third result of a function call that has more than one return values (e.g. in a composite literal or in a condition).
For example:
func f() (i, j, k int, s string, f float64) { return } p := image.Point{ X: Third(f()), }
func Wrap ¶
func Wrap(vs ...interface{}) []interface{}
Wrap returns its arguments as a slice.
General use of Wrap is to wrap function calls, so the return values of the "wrapped" function will be available as a slice. Which then can be passed to variadic functions that have other parameters too.
Most notable example is fmt.Printf(). This code doesn't compile:
// Compile-time error! fmt.Printf("Year: %d, month: %d, day: %d", time.Now().Date())
But with the help of this Wrap:
// This is OK! fmt.Printf("Year: %d, month: %d, day: %d", Wrap(time.Now().Date())...)
For details, see https://stackoverflow.com/a/52654950/1705598
Example ¶
ExampleWrap shows how to use the Wrap() function.
now := time.Date(2020, 3, 4, 0, 0, 0, 0, time.UTC) // Note that without Wrap it's a compile-time error. fmt.Printf("Year: %d, month: %d, day: %d\n", Wrap(now.Date())...)
Output: Year: 2020, month: 3, day: 4
Types ¶
type Evictable ¶ added in v0.2.0
type Evictable interface {
Evict()
}
Evictable defines a single Evict() method. OpCache has Evict().
type OpCache ¶ added in v0.2.0
type OpCache[K comparable, T any] struct { // contains filtered or unexported fields }
OpCache implements a general value cache. It can be used to cache results of arbitrary operations.
Cached values are tied to a key that should be derived from the operation's arguments. If the operation has multiple arguments, a wrapper struct is ideal (such as Struct2, Struct3 etc.), or fmt.Sprint() will also do as an alternative (with string being the key type).
Cached values have an expiration time and also a grace period during which the cached value is considered usable, but getting a cached value during the grace period triggers a reload that will happen in the background (the cached value is returned immediately, without waiting).
Operations are captured by a function that returns a value of a certain type (T) and an error. If an operation has multiple results beside the error, they must be wrapped in a composite type (like a struct or slice).
If multiple input arguments are available (for multiple operation execution), operations can often be executed more efficiently if all inputs are handed as a batch than executing the operation for each input argument individually. A tipical example is loading records by ID from a database: running a query with a condition like "id=?" for each ID individually can be significantly slower than running a single query with a condition like "id in ?". These operations can take advantage of the OpCache.MultiGet method. MultiGet will ensure that only the minimal required subset of the arguments is passed in the multi-operation execution if some of them are already cached, and OpCache.Get methods will also take advantage of entries cached by MultiGet.
Example ¶
This example demonstrates how to use OpCache to cache the results of an existing function.
package main import ( "fmt" "time" "github.com/icza/gox/gox" ) func main() { type Point struct { X, Y int Counter int // To track invocations } counter := 0 // Existing GetPoint() function we want to add caching for: GetPoint := func(x, y int) (*Point, error) { counter++ return &Point{X: x, Y: y, Counter: counter}, nil } var getPointCache = gox.NewOpCache[gox.Struct2[int, int], *Point](gox.OpCacheConfig{ResultExpiration: 100 * time.Millisecond}) // Function to use which utilizes getPointCache (has identical signature to that of GetPoint): GetPointFast := func(x, y int) (*Point, error) { return getPointCache.Get( gox.Struct2Of(x, y), // Key constructed from all arguments func() (*Point, error) { return GetPoint(x, y) }, ) } p, err := GetPointFast(1, 2) // This will call GetPoint() fmt.Printf("%+v %v\n", p, err) p, err = GetPointFast(1, 2) // This will come from the cache fmt.Printf("%+v %v\n", p, err) time.Sleep(110 * time.Millisecond) p, err = GetPointFast(1, 2) // Cache expired, will call GetPoint() again fmt.Printf("%+v %v\n", p, err) }
Output: &{X:1 Y:2 Counter:1} <nil> &{X:1 Y:2 Counter:1} <nil> &{X:1 Y:2 Counter:2} <nil>
Example (Multi_return) ¶
This example demonstrates how to use OpCache to cache the results of an existing function that has multiple result types (besides the error).
package main import ( "fmt" "time" "github.com/icza/gox/gox" ) func main() { type Point struct { X, Y int Counter int // To track invocations } counter := 0 // Existing GetPoint() function we want to add caching for: GetPoint := func(x, y int) (*Point, int, error) { counter++ return &Point{X: x, Y: 2 * x, Counter: counter}, counter * 10, fmt.Errorf("test_error_%d", counter) } var getPointCache = gox.NewOpCache[gox.Struct2[int, int], gox.Struct2[*Point, int]](gox.OpCacheConfig{ResultExpiration: 100 * time.Millisecond}) // Function to use which utilizes getPointCache (has identical signature to that of GetPoint): GetPointFast := func(x, y int) (*Point, int, error) { mr, err := getPointCache.Get( gox.Struct2Of(x, y), // Key constructed from all arguments func() (gox.Struct2[*Point, int], error) { p, n, err := GetPoint(x, y) return gox.Struct2Of(p, n), err // packing multiple results }, ) return mr.V1, mr.V2, err // Unpacking multiple results } p, n, err := GetPointFast(1, 2) // This will call GetPoint() fmt.Printf("%+v %d %v\n", p, n, err) p, n, err = GetPointFast(1, 2) // This will come from the cache fmt.Printf("%+v %d %v\n", p, n, err) time.Sleep(110 * time.Millisecond) p, n, err = GetPointFast(1, 2) // Cache expired, will call GetPoint() again fmt.Printf("%+v %d %v\n", p, n, err) }
Output: &{X:1 Y:2 Counter:1} 10 test_error_1 &{X:1 Y:2 Counter:1} 10 test_error_1 &{X:1 Y:2 Counter:2} 20 test_error_2
func NewOpCache ¶ added in v0.2.0
func NewOpCache[K comparable, T any](cfg OpCacheConfig) *OpCache[K, T]
NewOpCache creates a new OpCache.
func (*OpCache[K, T]) Evict ¶ added in v0.2.0
func (oc *OpCache[K, T]) Evict()
Evict checks all cached entries, and removes invalid ones.
func (*OpCache[K, T]) Get ¶ added in v0.2.0
func (oc *OpCache[K, T]) Get( key K, execOp func() (result T, err error), ) (result T, resultErr error)
Get gets the result of an operation.
If the result is cached and valid, it is returned immediately.
If the result is cached but not valid, but we're within the grace period, execOp() is called in the background to refresh the cache, and the cached result is returned immediately. Care is taken to only launch a single background worker to refresh the cache even if Get() or OpCache.MultiGet is called multiple times with the same key before the cache can be refreshed.
Else result is either not cached or we're past even the grace period: execOp() is executed, the function waits for its return values, the result is cached, and then the fresh result is returned.
func (*OpCache[K, T]) MultiGet ¶ added in v0.2.0
func (oc *OpCache[K, T]) MultiGet( keys []K, execMultiOp func(keyIndices []int) (results []T, errs []error), ) (results []T, resultErrs []error)
MultiGet gets the results of a multi-operation. A multi-operation is an operation that accepts a slice of keys, and can produce results for multiple input parameters more efficiently than calling the operation for each input separately.
results and resultErrs will be slices with identical size and elements matching to that of keys.
Each result is taken from the cache if present and valid, or we're within its grace period. If there are entries that are either not cached or we're past their grace period, execMultiOp() is executed for those keys, the function waits for its return values, the results are cached, and the fresh results are returned.
If there are results that are returned because they are cached but not valid but we're within the grace period, execMultiOp() is called in the background to refresh them. Care is taken to only launch a single background worker to refresh each such entry even if OpCache.Get or MultiGet() is called multiple times with the same key(s) before the cache can be refreshed.
execMultiOp must return results and errs slices with identical size to that of its keyIndices argument, and elements matching to keys designated by keyIndices! Failure to do so is undefined behavior, may even result in runtime panic!
Tip: github.com/icza/gog/slicesx.SelectByIndices may come handy when implementing execMultiOp.
Example ¶
This example demonstrates how to use OpCache.MultiGet().
package main import ( "fmt" "time" "github.com/icza/gox/gox" "github.com/icza/gox/slicesx" ) func main() { type CalcResult struct { Y int Counter int // To track invocations } counter := 0 // Existing Calc() function we want to add caching for: Calc := func(x int) (CalcResult, error) { counter++ return CalcResult{Y: 2 * x, Counter: counter}, nil } // Existing MultiCalc() that can do the same for multiple inputs: MultiCalc := func(xs []int) (cs []CalcResult, errs []error) { for _, x := range xs { counter++ cs = append(cs, CalcResult{Y: 2 * x, Counter: counter}) errs = append(errs, nil) } return } var calcCache = gox.NewOpCache[int, CalcResult](gox.OpCacheConfig{ ResultExpiration: 100 * time.Millisecond, ResultGraceExpiration: 50 * time.Millisecond, }) // Function to use which utilizes calcCache (has identical signature to that of Calc): CalcFast := func(x int) (CalcResult, error) { return calcCache.Get( x, // Key constructed from all arguments func() (CalcResult, error) { return Calc(x) }, ) } // Function to use which utilizes calcCache (has identical signature to that of MultiCalc): MultiCalcFast := func(xs []int) ([]CalcResult, []error) { return calcCache.MultiGet( xs, func(keyIndices []int) ([]CalcResult, []error) { return MultiCalc(slicesx.SelectByIndices(xs, keyIndices)) }, ) } c, err := CalcFast(1) // This will call Calc() fmt.Printf("%+v %v\n", c, err) cs, errs := MultiCalcFast([]int{1, 2, 3}) // First from cache, other 2 will be passed to MultiCalc() fmt.Printf("%+v %v\n", cs, errs) time.Sleep(110 * time.Millisecond) // First 2 from cache, third will be passed to MultiCalc() // Also background MultiCalc() will be called for first 2. cs, errs = MultiCalcFast([]int{1, 2, 4}) fmt.Printf("%+v %v\n", cs, errs) time.Sleep(10 * time.Millisecond) // All from cache, first 2 with updated counter from the background refresh cs, errs = MultiCalcFast([]int{1, 2, 4}) fmt.Printf("%+v %v\n", cs, errs) }
Output: {Y:2 Counter:1} <nil> [{Y:2 Counter:1} {Y:4 Counter:2} {Y:6 Counter:3}] [<nil> <nil> <nil>] [{Y:2 Counter:1} {Y:4 Counter:2} {Y:8 Counter:4}] [<nil> <nil> <nil>] [{Y:2 Counter:5} {Y:4 Counter:6} {Y:8 Counter:4}] [<nil> <nil> <nil>]
Example (Multi_inputargs) ¶
This example demonstrates how to use OpCache.MultiGet() when the operaiton has multiple input arguments.
package main import ( "fmt" "time" "github.com/icza/gox/gox" "github.com/icza/gox/slicesx" ) func main() { type CalcResult struct { Y int Counter int // To track invocations } counter := 0 // Existing Calc() function we want to add caching for: Calc := func(x, y int) (CalcResult, error) { counter++ return CalcResult{Y: 2*x + y, Counter: counter}, nil } // Existing MultiCalc() that can do the same for multiple inputs: MultiCalc := func(xs, ys []int) (cs []CalcResult, errs []error) { for i, x := range xs { counter++ cs = append(cs, CalcResult{Y: 2*x + ys[i], Counter: counter}) errs = append(errs, nil) } return } var calcCache = gox.NewOpCache[gox.Struct2[int, int], CalcResult](gox.OpCacheConfig{ ResultExpiration: 100 * time.Millisecond, ResultGraceExpiration: 50 * time.Millisecond, }) // Function to use which utilizes calcCache (has identical signature to that of Calc): CalcFast := func(x, y int) (CalcResult, error) { return calcCache.Get( gox.Struct2Of(x, y), // Key constructed from all arguments func() (CalcResult, error) { return Calc(x, y) }, ) } // Function to use which utilizes calcCache (has identical signature to that of MultiCalc): MultiCalcFast := func(xs, ys []int) ([]CalcResult, []error) { keys := make([]gox.Struct2[int, int], len(xs)) for i, x := range xs { keys[i] = gox.Struct2Of(x, ys[i]) // Key constructed from all arguments } return calcCache.MultiGet( keys, func(keyIndices []int) ([]CalcResult, []error) { return MultiCalc(slicesx.SelectByIndices(xs, keyIndices), slicesx.SelectByIndices(ys, keyIndices)) }, ) } c, err := CalcFast(1, 100) // This will call Calc() fmt.Printf("%+v %v\n", c, err) cs, errs := MultiCalcFast([]int{1, 2, 3}, []int{100, 200, 300}) // First from cache, other 2 will be passed to MultiCalc() fmt.Printf("%+v %v\n", cs, errs) time.Sleep(110 * time.Millisecond) // First 2 from cache, third will be passed to MultiCalc() // Also background MultiCalc() will be called for first 2. cs, errs = MultiCalcFast([]int{1, 2, 4}, []int{100, 200, 400}) fmt.Printf("%+v %v\n", cs, errs) time.Sleep(10 * time.Millisecond) // All from cache, first 2 with updated counter from the background refresh cs, errs = MultiCalcFast([]int{1, 2, 4}, []int{100, 200, 400}) fmt.Printf("%+v %v\n", cs, errs) }
Output: {Y:102 Counter:1} <nil> [{Y:102 Counter:1} {Y:204 Counter:2} {Y:306 Counter:3}] [<nil> <nil> <nil>] [{Y:102 Counter:1} {Y:204 Counter:2} {Y:408 Counter:4}] [<nil> <nil> <nil>] [{Y:102 Counter:5} {Y:204 Counter:6} {Y:408 Counter:4}] [<nil> <nil> <nil>]
type OpCacheConfig ¶ added in v0.2.0
type OpCacheConfig struct { // Operation results are valid for this long after creation. ResultExpiration time.Duration // Expired results are still usable for this long after expiration. // Tip: if this field is 0, grace period and thus background // op execution will be disabled. ResultGraceExpiration time.Duration // ErrorExpiration is an optional function. // If provided, it will be called for non-nil operation errors. // Return discard=true if you do not want to cache an error result. // If expiration or graceExpiration is provided (non-nil), they will // override the cache expiration for the given error result // // If provided, this function is only called once for the result error of a single operation execution // (regardless of how many times it is accessed from the OpCache). ErrorExpiration func(err error) (discard bool, expiration, graceExpiration *time.Duration) // AutoEvictPeriodMinutes tells how frequently should expired entries be checked and evicted from the cache. // If 0, DefaultEvictPeriodMinutes will be used. Removal is currently not supported. // // If a negative value is given, the op cache is not added to the internal auto-evictor, and manual eviction // should be taken care of with e.g. using the RunEvictor() function. AutoEvictPeriodMinutes int }
OpCacheConfig holds configuration options for an OpCache.
type Struct2 ¶ added in v0.2.0
type Struct2[T1, T2 any] struct { V1 T1 V2 T2 }
Struct2 is a struct of 2 generic fields. May come handy as a "quick" wrapper for key fields or multiple results for OpCache.
type Struct3 ¶ added in v0.2.0
type Struct3[T1, T2, T3 any] struct { V1 T1 V2 T2 V3 T3 }
Struct3 is a struct of 3 generic fields. May come handy as a "quick" wrapper for key fields or multiple results for OpCache.
type Struct4 ¶ added in v0.2.0
type Struct4[T1, T2, T3, T4 any] struct { V1 T1 V2 T2 V3 T3 V4 T4 }
Struct4 is a struct of 4 generic fields. May come handy as a "quick" wrapper for key fields or multiple results for OpCache.
type Struct5 ¶ added in v0.2.0
type Struct5[T1, T2, T3, T4, T5 any] struct { V1 T1 V2 T2 V3 T3 V4 T4 V5 T5 }
Struct5 is a struct of 5 generic fields. May come handy as a "quick" wrapper for key fields or multiple results for OpCache.