Skip to content

Commit

Permalink
Fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
golightlyb committed May 5, 2024
1 parent 1f48f70 commit 4eaa1d6
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 75 deletions.
File renamed without changes.
3 changes: 2 additions & 1 deletion fun/partial/partial.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
// bound.
//
// Functions have the prefix "Left" if they bind the left-most argument first,
// or "Right" if they bind the right-most argument first.
// or "Right" if they bind the right-most argument first. The prefix "All"
// binds all arguments at once.
//
// Each input function must return exactly one value. The "fun/result" and
// "fun/maybe" packages provide useful functions that can wrap functions that
Expand Down
45 changes: 27 additions & 18 deletions fun/promise/promise.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
// represent a computation to be performed at a later stage.
//
// This composes nicely with the idea of futures (see fun/future).
//
// Tip: rarely return a Promise from any function. Instead, return a normal
// (result, error) tuple. The caller can use WrapResultFunc to transform that
// function into one that returns a promise.
package promise

import (
Expand Down Expand Up @@ -30,13 +34,13 @@ type P[X any] interface {
ComputeCtx(ctx context.Context) (X, error)
}

// Func is the type of a function with no arguments that satisfies the promise
// pfunc is the type of a function with no arguments that satisfies the promise
// interface by calling the function, ignoring any context.
type Func[X any] func() (X, error)
func (f Func[X]) Compute() (X, error) {
type pfunc[X any] func() (X, error)
func (f pfunc[X]) Compute() (X, error) {
return f()
}
func (f Func[X]) ComputeCtx(ctx context.Context) (X, error) {
func (f pfunc[X]) ComputeCtx(context.Context) (X, error) {
return f()
}

Expand All @@ -51,7 +55,7 @@ func FromFunc[T any](f func() T) P[T] {
// FromResultFunc creates a promise to call function f, where f returns a
// (result, error) tuple.
func FromResultFunc[X any](f func() (X, error)) P[X] {
return Func[X](f)
return pfunc[X](f)
}

// WrapResultFunc wraps an existing function "f() => (X, error)" so that it
Expand Down Expand Up @@ -98,20 +102,20 @@ func WrapResultFunc4[A, B, C, D, X any](f func(A, B, C, D) (X, error)) func(A, B
// (value, ok) tuple. If the returned ok is false, the promise computes the
// error [NotOk].
func FromOkFunc[X any](f func() (X, bool)) P[X] {
return Func[X](func() (result X, err error) {
return pfunc[X](func() (result X, err error) {
v, ok := f()
if !ok { err = NotOk; return }
return v, nil
})
}

// FuncCtx is the type of a function with a context argument that satisfies the
// pfuncCtx is the type of a function with a context argument that satisfies the
// promise interface by calling the function with a context.
type FuncCtx[X any] func(ctx context.Context) (X, error)
func (f FuncCtx[X]) Compute() (X, error) {
type pfuncCtx[X any] func(ctx context.Context) (X, error)
func (f pfuncCtx[X]) Compute() (X, error) {
return f(context.TODO())
}
func (f FuncCtx[X]) ComputeCtx(ctx context.Context) (X, error) {
func (f pfuncCtx[X]) ComputeCtx(ctx context.Context) (X, error) {
return f(ctx)
}

Expand All @@ -127,32 +131,34 @@ func FromFuncCtx[T any](f func(context.Context) T) P[T] {
// FromResultFuncCtx creates a promise to call function f, where f accepts a
// context and returns a (result, error) tuple.
func FromResultFuncCtx[X any](f func(context.Context) (X, error)) P[X] {
return FuncCtx[X](f)
return pfuncCtx[X](f)
}

// FromOkFuncCtx creates a promise to call function f, where f accepts a
// context and returns a (value, ok) tuple. If the returned ok is false, the
// promise computes the error [NotOk].
func FromOkFuncCtx[X any](f func(ctx context.Context) (X, bool)) P[X] {
return FuncCtx[X](func(ctx context.Context) (result X, err error) {
return pfuncCtx[X](func(ctx context.Context) (result X, err error) {
v, ok := f(ctx)
if !ok { err = NotOk; return }
return v, nil
})
}

type value[X any] struct {x X}
func (v value[X]) Compute() (X, error) {
// pvalue is the type of a single value that implements the promise interface
// by always returning the value.
type pvalue[X any] struct {x X}
func (v pvalue[X]) Compute() (X, error) {
return v.x, nil
}
func (v value[X]) ComputeCtx(ctx context.Context) (X, error) {
func (v pvalue[X]) ComputeCtx(context.Context) (X, error) {
return v.x, nil
}

// FromValue creates a promise that simply returns the provided argument and
// a nil error when computed.
func FromValue[X any](x X) P[X] {
return value[X]{x: x}
return pvalue[X]{x: x}
}

// FromValueErr creates a promise that simply returns the provided argument or,
Expand All @@ -165,12 +171,14 @@ func FromValueErr[X any](value X, err error) P[X] {
}
}

// perror is the type of a single error that implements the promise interface
// by always returning the error.
type perror[X any] struct {err error}
func (e perror[X]) Compute() (X, error) {
var zero X
return zero, e.err
}
func (e perror[X]) ComputeCtx(ctx context.Context) (X, error) {
func (e perror[X]) ComputeCtx(context.Context) (X, error) {
var zero X
return zero, e.err
}
Expand All @@ -182,7 +190,8 @@ func FromError[X any](err error) P[X] {
}

// Chain returns a new promise to compute function f on the result of promise
// p.
// p. If the input promise returns an error, the returned promise will return
// that error without calling f.
func Chain[X any, Y any](p P[X], f func(X) (Y, error)) P[Y] {
return FromResultFuncCtx[Y](func(ctx context.Context) (Y, error) {
v, err := p.ComputeCtx(ctx)
Expand Down
10 changes: 10 additions & 0 deletions fun/slices/slices.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
// Package slices provides generic higher-order functions over slices of values.
package slices

// Copy deeply copies a slice of elements that each, in turn, must be copied
// using their own Copy method.
func Copy[X interface{Copy() X}](xs []X) []X {
results := []X(nil)
for _, x := range xs {
results = append(results, x.Copy())
}
return results
}

// FromArgs returns the slice of the variadic arguments list.
func FromArgs[X any](xs ... X) []X {
return xs
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,23 @@ github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPp
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
golang.org/x/exp v0.0.0-20221208152030-732eee02a75a h1:4iLhBPcpqFmylhnkbY3W0ONLUYYkDAW9xMFLfxgsvCw=
golang.org/x/exp v0.0.0-20221208152030-732eee02a75a/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
5 changes: 5 additions & 0 deletions operator/checked/examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"github.com/tawesoft/golib/v2/operator/checked"
)

// ExampleSimple demonstrates checked addition against the built-in limits
// for 8 bit unsigned integers.
func ExampleSimple() {
{
result, ok := checked.Uint8.Add(250, 5)
Expand All @@ -22,14 +24,17 @@ func ExampleSimple() {
// checked.Uint8.Add(250, 6): 0, ok?=false
}

// ExampleLimits demonstrates checked subtraction against custom limits.
func ExampleLimits() {
// Call checked.Sub directly with min and max...
{
const min = 0
const max = 99
result, ok := checked.Sub(min, max, 10, 9)
fmt.Printf("checked.Sub(min, max, 10, 9): %d, ok?=%t\n", result, ok)
}

// Or define a custom limit and call the Sub() method...
{
limit := checked.Limits[int]{Min: 0, Max: 99}
result, ok := limit.Sub(10, 25)
Expand Down
61 changes: 30 additions & 31 deletions operator/checked/integer.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,58 +6,57 @@ import (
"golang.org/x/exp/constraints"
)

// Add returns (a + b, true) iff the result lies between min and max inclusive,
// otherwise returns (0, false). This calculation is robust in the event of
// integer overflow.
//
// The input arguments must satisfy the inequalities: `min <= a <= max` and
// `min <= b <= max`.
// Add returns (a + b, true) iff a, b, and the result all lie between min and
// max inclusive, otherwise returns (0, false). This calculation is robust in
// the event of integer overflow.
func Add[N constraints.Integer](min N, max N, a N, b N) (N, bool) {
if (a < min) || (a > max) || (b < min) || (b > max) { return 0, false }
if (min > max) || (max < min) { return 0, false }
if (b > 0) && (a > (max - b)) { return 0, false }
if (b < 0) && (a < (min - b)) { return 0, false }
return a + b, true
}

// Sub returns (a + b, true) iff the result lies between min and max inclusive,
// otherwise returns (0, false). This calculation is robust in the event of
// integer overflow.
//
// The input arguments must satisfy the inequalities: `min <= a <= max` and
// `min <= b <= max`.
// Sub returns (a - b, true) iff a, b, and the result all lie between min and
// max inclusive, otherwise returns (0, false). This calculation is robust in
// the event of integer overflow.
func Sub[N constraints.Integer](min N, max N, a N, b N) (N, bool) {
if (a < min) || (a > max) || (b < min) || (b > max) { return 0, false }
if (min > max) || (max < min) { return 0, false }
if (b < 0) && (a > (max + b)) { return 0, false }
if (b > 0) && (a < (min + b)) { return 0, false }
return a - b, true
}

// Mul returns (a * b, true) iff the result lies between min and max inclusive,
// otherwise returns (0, false). This calculation is robust in the event of
// integer overflow.
//
// The input arguments must satisfy the inequalities: `min <= a <= max` and
// `min <= b <= max`.
// Mul returns (a * b, true) iff a, b, and the result all lie between min and
// max inclusive, otherwise returns (0, false). This calculation is robust in
// the event of integer overflow.
func Mul[N constraints.Integer](min N, max N, a N, b N) (N, bool) {
if (a == 0) || (b == 0) { return 0, true }
if (a < min) || (a > max) || (b < min) || (b > max) { return 0, false }
if (min > max) || (max < min) { return 0, false }

x := a * b
if (x < min) || (x > max) { return 0, false }
if (a != x/b) { return 0, false }
if (x != 0) && (a != x/b) { return 0, false }
return x, true
}

// Abs returns (-i, true) for i < 0, or (i, true) for i >= 0 iff the result lies
// between min and max inclusive. Otherwise returns (0, false).
//
// The input arguments must satisfy the inequality `min <= i <= max`.
// Abs returns (positive i, true) iff both i and the result lie between min and
// max inclusive. Otherwise, returns (0, false).
func Abs[N constraints.Integer](min N, max N, i N) (N, bool) {
if (i >= 0) { return i, true }
return Sub(min, max, 0, i)
if (i < min) || (i > max) { return 0, false }
if (min > max) || (max < min) { return 0, false }
if (i < 0) && (0 > (max + i)) { return 0, false }
if (i > 0) && (0 < (min + i)) { return 0, false }
if i < 0 { return -i, true } else { return i, true }
}

// Inv returns (-i) iff the result lies between min and max inclusive.
// Otherwise returns (0, false).
//
// The input arguments must satisfy the inequality `min <= i <= max`.
// Inv returns (-i, true) iff both i and the result lie between min and max
// inclusive. Otherwise, returns (0, false).
func Inv[N constraints.Integer](min N, max N, i N) (N, bool) {
return Sub(min, max, 0, i)
if (i < min) || (i > max) { return 0, false }
if (min > max) || (max < min) { return 0, false }
if (i < 0) && (0 > (max + i)) { return 0, false }
if (i > 0) && (0 < (min + i)) { return 0, false }
return -i, true
}
34 changes: 21 additions & 13 deletions operator/checked/limits.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import (
"golang.org/x/exp/constraints"
)

// Limits provides a convenient way to fill the min and max arguments to the
// checked operator functions. The inequality Min <= Max must be satisfied.
// Limits are a pair of integer values defining a range between an (inclusive)
// minimum value and an (inclusive) maximum value.
type Limits[I constraints.Integer] struct {
Min I
Max I
}

// GetLimits returns a filled-in [Limit] for the given integer type.
// GetLimits returns a filled-in [Limit] representing the widest possible
// minimum and maximum values for a generic integer type.
func GetLimits[I constraints.Integer]() Limits[I] {
var n Limits[I]
switch x := any(&n).(type) {
Expand All @@ -36,6 +37,10 @@ func GetLimits[I constraints.Integer]() Limits[I] {

// Filled-in [Limits] about different integer types with minimum and maximum
// set to the largest range supported by the limit.
//
// For signed integers, these are the appropriately sized math.MinInt and
// math.MaxInt constants. For unsigned integers, these are zero and the
// appropriately sized math.MaxUint constants.
var (
Int = Limits[int] {math.MinInt, math.MaxInt}
Int8 = Limits[int8] {math.MinInt8, math.MaxInt8}
Expand All @@ -50,32 +55,35 @@ var (
Uint64 = Limits[uint64]{0, math.MaxUint64}
)

// Add calls [checked.Add] with min and max filled in with the associated
// [Limits] values.
// Add returns (a + b, true) iff a, b, and the result all lie between the Limit
// min and max inclusive, otherwise returns (0, false). This calculation is
// robust in the event of integer overflow.
func (l Limits[I]) Add(a I, b I) (I, bool) {
return Add(l.Min, l.Max, a, b)
}

// Sub calls [checked.Sub] with min and max filled in with the associated
// [Limits] values.
// Sub returns (a - b, true) iff a, b, and the result all lie between the Limit
// min and max inclusive, otherwise returns (0, false). This calculation is
// robust in the event of integer overflow.
func (l Limits[I]) Sub(a I, b I) (I, bool) {
return Sub(l.Min, l.Max, a, b)
}

// Mul calls [checked.Mul] with min and max filled in with the associated
// [Limits] values.
// Mul returns (a * b, true) iff a, b, and the result all lie between the Limit
// min and max inclusive, otherwise returns (0, false). This calculation is
// robust in the event of integer overflow.
func (l Limits[I]) Mul(a I, b I) (I, bool) {
return Mul(l.Min, l.Max, a, b)
}

// Abs calls [checked.Abs] with min and max filled in with the associated
// [Limits] values.
// Abs returns (positive i, true) iff both i and the result lie between the
// Limit min and max inclusive. Otherwise, returns (0, false).
func (l Limits[I]) Abs(i I) (I, bool) {
return Abs(l.Min, l.Max, i)
}

// Inv calls [checked.Inv] with min and max filled in with the associated
// [Limits] values.
// Inv returns (-i, true) iff both i and the result lie between the Limit min
// and max inclusive. Otherwise, returns (0, false).
func (l Limits[I]) Inv(i I) (I, bool) {
return Inv(l.Min, l.Max, i)
}
Loading

0 comments on commit 4eaa1d6

Please sign in to comment.