gog

package module
v0.0.0-...-5da24f1 Latest Latest
Warning

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

Go to latest
Published: Oct 10, 2024 License: Apache-2.0 Imports: 3 Imported by: 19

README

gog

Build Status Go Reference

WARNING! This package is now deprecated and archived! It has been merged into github.com/icza/gox. Import and use gox!


General, generic extensions to the Go language, requiring generics (introduced in Go 1.18).

For now, the most simple, most obvious and most useful generics utilities.

Documentation

Overview

Package gog contains general, generic extensions to the Go language, requiring generics (introduced in Go 1.18).

Index

Examples

Constants

View Source
const DefaultEvictPeriodMinutes = 15

Variables

This section is empty.

Functions

func Coalesce

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

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

func First[T any](first T, _ ...any) T

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

func If[T any](cond bool, vtrue, vfalse T) T

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

func IfFunc[T any](cond bool, ftrue func() T, ffalse func() T) T

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

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

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 Ptr

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

func RunEvictor(ctx context.Context, evictorPeriod time.Duration, opCaches ...Evictable)

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

func Second[T any](_ any, second T, _ ...any) T

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

func Third[T any](_, _ any, third T, _ ...any) T

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()),
}

Types

type Evictable

type Evictable interface {
	Evict()
}

Evictable defines a single Evict() method. OpCache has Evict().

type OpCache

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/gog"
)

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 = gog.NewOpCache[gog.Struct2[int, int], *Point](gog.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(
			gog.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/gog"
)

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 = gog.NewOpCache[gog.Struct2[int, int], gog.Struct2[*Point, int]](gog.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(
			gog.Struct2Of(x, y), // Key constructed from all arguments
			func() (gog.Struct2[*Point, int], error) {
				p, n, err := GetPoint(x, y)
				return gog.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

func NewOpCache[K comparable, T any](cfg OpCacheConfig) *OpCache[K, T]

NewOpCache creates a new OpCache.

func (*OpCache[K, T]) Evict

func (oc *OpCache[K, T]) Evict()

Evict checks all cached entries, and removes invalid ones.

func (*OpCache[K, T]) Get

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

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/gog"
	"github.com/icza/gog/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 = gog.NewOpCache[int, CalcResult](gog.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/gog"
	"github.com/icza/gog/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 = gog.NewOpCache[gog.Struct2[int, int], CalcResult](gog.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(
			gog.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([]gog.Struct2[int, int], len(xs))
		for i, x := range xs {
			keys[i] = gog.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

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

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.

func Struct2Of

func Struct2Of[T1, T2 any](v1 T1, v2 T2) Struct2[T1, T2]

type Struct3

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.

func Struct3Of

func Struct3Of[T1, T2, T3 any](v1 T1, v2 T2, v3 T3) Struct3[T1, T2, T3]

type Struct4

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.

func Struct4Of

func Struct4Of[T1, T2, T3, T4 any](v1 T1, v2 T2, v3 T3, v4 T4) Struct4[T1, T2, T3, T4]

type Struct5

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.

func Struct5Of

func Struct5Of[T1, T2, T3, T4, T5 any](v1 T1, v2 T2, v3 T3, v4 T4, v5 T5) Struct5[T1, T2, T3, T4, T5]

type Struct6

type Struct6[T1, T2, T3, T4, T5, T6 any] struct {
	V1 T1
	V2 T2
	V3 T3
	V4 T4
	V5 T5
	V6 T6
}

Struct6 is a struct of 6 generic fields. May come handy as a "quick" wrapper for key fields or multiple results for OpCache.

func Struct6Of

func Struct6Of[T1, T2, T3, T4, T5, T6 any](v1 T1, v2 T2, v3 T3, v4 T4, v5 T5, v6 T6) Struct6[T1, T2, T3, T4, T5, T6]

Directories

Path Synopsis
Package slicesx provides generic slice utility functions.
Package slicesx provides generic slice utility functions.

Jump to

Keyboard shortcuts

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