mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 01:43:16 +01:00
Merge pull request #8985 from ProofOfKeags/fn/collect-results
fn: more fn goodies
This commit is contained in:
commit
f42636fec9
10
fn/either.go
10
fn/either.go
@ -20,7 +20,7 @@ func NewRight[L any, R any](r R) Either[L, R] {
|
||||
// ElimEither is the universal Either eliminator. It can be used to safely
|
||||
// handle all possible values inside the Either by supplying two continuations,
|
||||
// one for each side of the Either.
|
||||
func ElimEither[L, R, O any](f func(L) O, g func(R) O, e Either[L, R]) O {
|
||||
func ElimEither[L, R, O any](e Either[L, R], f func(L) O, g func(R) O) O {
|
||||
if !e.isRight {
|
||||
return f(e.left)
|
||||
}
|
||||
@ -52,9 +52,9 @@ func (e Either[L, R]) IsRight() bool {
|
||||
return e.isRight
|
||||
}
|
||||
|
||||
// LeftToOption converts a Left value to an Option, returning None if the inner
|
||||
// LeftToSome converts a Left value to an Option, returning None if the inner
|
||||
// Either value is a Right value.
|
||||
func (e Either[L, R]) LeftToOption() Option[L] {
|
||||
func (e Either[L, R]) LeftToSome() Option[L] {
|
||||
if e.isRight {
|
||||
return None[L]()
|
||||
}
|
||||
@ -62,9 +62,9 @@ func (e Either[L, R]) LeftToOption() Option[L] {
|
||||
return Some(e.left)
|
||||
}
|
||||
|
||||
// RightToOption converts a Right value to an Option, returning None if the
|
||||
// RightToSome converts a Right value to an Option, returning None if the
|
||||
// inner Either value is a Left value.
|
||||
func (e Either[L, R]) RightToOption() Option[R] {
|
||||
func (e Either[L, R]) RightToSome() Option[R] {
|
||||
if !e.isRight {
|
||||
return None[R]()
|
||||
}
|
||||
|
@ -10,17 +10,17 @@ func TestPropConstructorEliminatorDuality(t *testing.T) {
|
||||
Len := func(s string) int { return len(s) } // smh
|
||||
if isRight {
|
||||
v := ElimEither(
|
||||
NewRight[int, string](s),
|
||||
Iden[int],
|
||||
Len,
|
||||
NewRight[int, string](s),
|
||||
)
|
||||
return v == Len(s)
|
||||
}
|
||||
|
||||
v := ElimEither(
|
||||
NewLeft[int, string](i),
|
||||
Iden[int],
|
||||
Len,
|
||||
NewLeft[int, string](i),
|
||||
)
|
||||
return v == i
|
||||
}
|
||||
@ -99,18 +99,16 @@ func TestPropToOptionIdentities(t *testing.T) {
|
||||
if isRight {
|
||||
e = NewRight[int, string](s)
|
||||
|
||||
r2O := e.RightToOption() == Some(s)
|
||||
o2R := e == OptionToRight[string, int, string](
|
||||
Some(s), i,
|
||||
)
|
||||
l2O := e.LeftToOption() == None[int]()
|
||||
r2O := e.RightToSome() == Some(s)
|
||||
o2R := e == SomeToRight(Some(s), i)
|
||||
l2O := e.LeftToSome() == None[int]()
|
||||
|
||||
return r2O && o2R && l2O
|
||||
} else {
|
||||
e = NewLeft[int, string](i)
|
||||
l2O := e.LeftToOption() == Some(i)
|
||||
o2L := e == OptionToLeft[int, int](Some(i), s)
|
||||
r2O := e.RightToOption() == None[string]()
|
||||
l2O := e.LeftToSome() == Some(i)
|
||||
o2L := e == SomeToLeft(Some(i), s)
|
||||
r2O := e.RightToSome() == None[string]()
|
||||
|
||||
return l2O && o2L && r2O
|
||||
}
|
||||
|
2
fn/fn.go
2
fn/fn.go
@ -23,7 +23,7 @@ func Iden[A any](a A) A {
|
||||
// Const is a function that accepts an argument and returns a function that
|
||||
// always returns that value irrespective of the returned function's argument.
|
||||
// This is also quite useful in conjunction with higher order functions.
|
||||
func Const[A, B any](a A) func(B) A {
|
||||
func Const[B, A any](a A) func(B) A {
|
||||
return func(_ B) A {
|
||||
return a
|
||||
}
|
||||
|
@ -743,7 +743,7 @@ func TestFilterIdempotence(t *testing.T) {
|
||||
|
||||
filtered := l.Filter(pred)
|
||||
|
||||
filteredAgain := Filter(pred, filtered)
|
||||
filteredAgain := Filter(filtered, pred)
|
||||
|
||||
return slices.Equal(filtered, filteredAgain)
|
||||
},
|
||||
|
60
fn/option.go
60
fn/option.go
@ -1,6 +1,11 @@
|
||||
package fn
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Option represents a value which may or may not be there. This is very often
|
||||
// preferable to nil-able pointers.
|
||||
@ -72,16 +77,11 @@ func (o Option[A]) UnwrapOrFunc(f func() A) A {
|
||||
func (o Option[A]) UnwrapOrFail(t *testing.T) A {
|
||||
t.Helper()
|
||||
|
||||
if o.isSome {
|
||||
require.True(t, o.isSome, "Option[%T] was None()", o.some)
|
||||
|
||||
return o.some
|
||||
}
|
||||
|
||||
t.Fatalf("Option[%T] was None()", o.some)
|
||||
|
||||
var zero A
|
||||
return zero
|
||||
}
|
||||
|
||||
// UnwrapOrErr is used to extract a value from an option, if the option is
|
||||
// empty, then the specified error is returned directly.
|
||||
func (o Option[A]) UnwrapOrErr(err error) (A, error) {
|
||||
@ -144,11 +144,11 @@ func FlattenOption[A any](oo Option[Option[A]]) Option[A] {
|
||||
return oo.some
|
||||
}
|
||||
|
||||
// ChainOption transforms a function A -> Option[B] into one that accepts an
|
||||
// FlatMapOption transforms a function A -> Option[B] into one that accepts an
|
||||
// Option[A] as an argument.
|
||||
//
|
||||
// ChainOption : (A -> Option[B]) -> Option[A] -> Option[B].
|
||||
func ChainOption[A, B any](f func(A) Option[B]) func(Option[A]) Option[B] {
|
||||
// FlatMapOption : (A -> Option[B]) -> Option[A] -> Option[B].
|
||||
func FlatMapOption[A, B any](f func(A) Option[B]) func(Option[A]) Option[B] {
|
||||
return func(o Option[A]) Option[B] {
|
||||
if o.isSome {
|
||||
return f(o.some)
|
||||
@ -225,9 +225,9 @@ func (o Option[A]) UnsafeFromSome() A {
|
||||
panic("Option was None()")
|
||||
}
|
||||
|
||||
// OptionToLeft can be used to convert an Option value into an Either, by
|
||||
// SomeToLeft can be used to convert an Option value into an Either, by
|
||||
// providing the Right value that should be used if the Option value is None.
|
||||
func OptionToLeft[O, L, R any](o Option[O], r R) Either[O, R] {
|
||||
func SomeToLeft[O, R any](o Option[O], r R) Either[O, R] {
|
||||
if o.IsSome() {
|
||||
return NewLeft[O, R](o.some)
|
||||
}
|
||||
@ -235,12 +235,42 @@ func OptionToLeft[O, L, R any](o Option[O], r R) Either[O, R] {
|
||||
return NewRight[O, R](r)
|
||||
}
|
||||
|
||||
// OptionToRight can be used to convert an Option value into an Either, by
|
||||
// SomeToRight can be used to convert an Option value into an Either, by
|
||||
// providing the Left value that should be used if the Option value is None.
|
||||
func OptionToRight[O, L, R any](o Option[O], l L) Either[L, O] {
|
||||
func SomeToRight[O, L any](o Option[O], l L) Either[L, O] {
|
||||
if o.IsSome() {
|
||||
return NewRight[L, O](o.some)
|
||||
}
|
||||
|
||||
return NewLeft[L, O](l)
|
||||
}
|
||||
|
||||
// SomeToOk allows you to convert an Option value to a Result with your own
|
||||
// error. If the Option contained a Some, then the supplied error is ignored
|
||||
// and Some is converted to Ok.
|
||||
func (o Option[A]) SomeToOk(err error) Result[A] {
|
||||
return Result[A]{
|
||||
SomeToLeft(o, err),
|
||||
}
|
||||
}
|
||||
|
||||
// SomeToOkf allows you to convert an Option value to a Result with your own
|
||||
// error message. If the Option contains a Some, then the supplied message is
|
||||
// ignored and Some is converted to Ok.
|
||||
func (o Option[A]) SomeToOkf(errString string, args ...interface{}) Result[A] {
|
||||
return Result[A]{
|
||||
SomeToLeft(o, fmt.Errorf(errString, args...)),
|
||||
}
|
||||
}
|
||||
|
||||
// TransposeOptRes transposes the Option[Result[A]] into a Result[Option[A]].
|
||||
// This has the effect of leaving an A value alone while inverting the Option
|
||||
// and Result layers. If there is no internal A value, it will convert the
|
||||
// non-success value to the proper one in the transposition.
|
||||
func TransposeOptRes[A any](o Option[Result[A]]) Result[Option[A]] {
|
||||
if o.IsNone() {
|
||||
return Ok(None[A]())
|
||||
}
|
||||
|
||||
return Result[Option[A]]{MapLeft[A, error](Some[A])(o.some.Either)}
|
||||
}
|
||||
|
53
fn/option_test.go
Normal file
53
fn/option_test.go
Normal file
@ -0,0 +1,53 @@
|
||||
package fn
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestOptionUnwrapOrFail(t *testing.T) {
|
||||
require.Equal(t, Some(1).UnwrapOrFail(t), 1)
|
||||
}
|
||||
|
||||
func TestSomeToOk(t *testing.T) {
|
||||
err := errors.New("err")
|
||||
require.Equal(t, Some(1).SomeToOk(err), Ok(1))
|
||||
require.Equal(t, None[uint8]().SomeToOk(err), Err[uint8](err))
|
||||
}
|
||||
|
||||
func TestSomeToOkf(t *testing.T) {
|
||||
errStr := "err"
|
||||
require.Equal(t, Some(1).SomeToOkf(errStr), Ok(1))
|
||||
require.Equal(
|
||||
t, None[uint8]().SomeToOkf(errStr),
|
||||
Err[uint8](fmt.Errorf(errStr)),
|
||||
)
|
||||
}
|
||||
|
||||
func TestPropTransposeOptResInverts(t *testing.T) {
|
||||
f := func(i uint) bool {
|
||||
var o Option[Result[uint]]
|
||||
switch i % 3 {
|
||||
case 0:
|
||||
o = Some(Ok(i))
|
||||
case 1:
|
||||
o = Some(Errf[uint]("error"))
|
||||
case 2:
|
||||
o = None[Result[uint]]()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
odd := TransposeOptRes(o) ==
|
||||
TransposeOptRes(TransposeResOpt(TransposeOptRes(o)))
|
||||
even := TransposeResOpt(TransposeOptRes(o)) == o
|
||||
|
||||
return odd && even
|
||||
}
|
||||
|
||||
require.NoError(t, quick.Check(f, nil))
|
||||
}
|
112
fn/result.go
112
fn/result.go
@ -3,6 +3,8 @@ package fn
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Result represents a value that can either be a success (T) or an error.
|
||||
@ -62,27 +64,35 @@ func (r Result[T]) IsErr() bool {
|
||||
return r.IsRight()
|
||||
}
|
||||
|
||||
// Map applies a function to the success value if it exists.
|
||||
func (r Result[T]) Map(f func(T) T) Result[T] {
|
||||
// MapOk applies an endomorphic function to the success value if it exists.
|
||||
func (r Result[T]) MapOk(f func(T) T) Result[T] {
|
||||
return Result[T]{
|
||||
MapLeft[T, error](f)(r.Either),
|
||||
}
|
||||
}
|
||||
|
||||
// MapErr applies a function to the error value if it exists.
|
||||
// MapErr applies an endomorphic function to the error value if it exists.
|
||||
func (r Result[T]) MapErr(f func(error) error) Result[T] {
|
||||
return Result[T]{
|
||||
MapRight[T](f)(r.Either),
|
||||
}
|
||||
}
|
||||
|
||||
// Option returns the success value as an Option.
|
||||
func (r Result[T]) Option() Option[T] {
|
||||
return r.Either.LeftToOption()
|
||||
// MapOk applies a non-endomorphic function to the success value if it exists
|
||||
// and returns a Result of the new type.
|
||||
func MapOk[A, B any](f func(A) B) func(Result[A]) Result[B] {
|
||||
return func(r Result[A]) Result[B] {
|
||||
return Result[B]{MapLeft[A, error](f)(r.Either)}
|
||||
}
|
||||
}
|
||||
|
||||
// WhenResult executes the given function if the Result is a success.
|
||||
func (r Result[T]) WhenResult(f func(T)) {
|
||||
// OkToSome mutes the error value of the result.
|
||||
func (r Result[T]) OkToSome() Option[T] {
|
||||
return r.Either.LeftToSome()
|
||||
}
|
||||
|
||||
// WhenOk executes the given function if the Result is a success.
|
||||
func (r Result[T]) WhenOk(f func(T)) {
|
||||
r.WhenLeft(f)
|
||||
}
|
||||
|
||||
@ -102,9 +112,9 @@ func (r Result[T]) UnwrapOr(defaultValue T) T {
|
||||
|
||||
// UnwrapOrElse returns the success value or computes a value from a function
|
||||
// if it's an error.
|
||||
func (r Result[T]) UnwrapOrElse(f func() T) T {
|
||||
func (r Result[T]) UnwrapOrElse(f func(error) T) T {
|
||||
if r.IsErr() {
|
||||
return f()
|
||||
return f(r.right)
|
||||
}
|
||||
|
||||
return r.left
|
||||
@ -114,17 +124,29 @@ func (r Result[T]) UnwrapOrElse(f func() T) T {
|
||||
func (r Result[T]) UnwrapOrFail(t *testing.T) T {
|
||||
t.Helper()
|
||||
|
||||
require.True(
|
||||
t, r.IsOk(), "Result[%T] contained error: %v", r.left, r.right,
|
||||
)
|
||||
|
||||
return r.left
|
||||
}
|
||||
|
||||
// FlattenResult takes a nested Result and joins the two functor layers into
|
||||
// one.
|
||||
func FlattenResult[A any](r Result[Result[A]]) Result[A] {
|
||||
if r.IsErr() {
|
||||
t.Fatalf("Result[%T] contained error: %v", r.left, r.right)
|
||||
return Err[A](r.right)
|
||||
}
|
||||
|
||||
var zero T
|
||||
|
||||
return zero
|
||||
if r.left.IsErr() {
|
||||
return Err[A](r.left.right)
|
||||
}
|
||||
|
||||
// FlatMap applies a function that returns a Result to the success value if it
|
||||
// exists.
|
||||
return r.left
|
||||
}
|
||||
|
||||
// FlatMap applies a kleisli endomorphic function that returns a Result to the
|
||||
// success value if it exists.
|
||||
func (r Result[T]) FlatMap(f func(T) Result[T]) Result[T] {
|
||||
if r.IsOk() {
|
||||
return r
|
||||
@ -143,17 +165,17 @@ func (r Result[T]) AndThen(f func(T) Result[T]) Result[T] {
|
||||
// OrElse returns the original Result if it is a success, otherwise it returns
|
||||
// the provided alternative Result. This along with AndThen can be used to
|
||||
// Railway Oriented Programming (ROP).
|
||||
func (r Result[T]) OrElse(f func() Result[T]) Result[T] {
|
||||
func (r Result[T]) OrElse(f func(error) Result[T]) Result[T] {
|
||||
if r.IsOk() {
|
||||
return r
|
||||
}
|
||||
|
||||
return f()
|
||||
return f(r.right)
|
||||
}
|
||||
|
||||
// FlatMap applies a function that returns a Result[B] to the success value if
|
||||
// it exists.
|
||||
func FlatMap[A, B any](r Result[A], f func(A) Result[B]) Result[B] {
|
||||
// FlatMapResult applies a function that returns a Result[B] to the success
|
||||
// value if it exists.
|
||||
func FlatMapResult[A, B any](r Result[A], f func(A) Result[B]) Result[B] {
|
||||
if r.IsOk() {
|
||||
return f(r.left)
|
||||
}
|
||||
@ -164,17 +186,45 @@ func FlatMap[A, B any](r Result[A], f func(A) Result[B]) Result[B] {
|
||||
// AndThen is an alias for FlatMap. This along with OrElse can be used to
|
||||
// Railway Oriented Programming (ROP).
|
||||
func AndThen[A, B any](r Result[A], f func(A) Result[B]) Result[B] {
|
||||
return FlatMap(r, f)
|
||||
return FlatMapResult(r, f)
|
||||
}
|
||||
|
||||
// AndThen2 applies a function that returns a Result[C] to the success values
|
||||
// of two Result types if both exist.
|
||||
func AndThen2[A, B, C any](ra Result[A], rb Result[B],
|
||||
f func(A, B) Result[C]) Result[C] {
|
||||
// LiftA2Result lifts a two-argument function to a function that can operate
|
||||
// over results of its arguments.
|
||||
func LiftA2Result[A, B, C any](f func(A, B) C,
|
||||
) func(Result[A], Result[B]) Result[C] {
|
||||
|
||||
return AndThen(ra, func(a A) Result[C] {
|
||||
return AndThen(rb, func(b B) Result[C] {
|
||||
return f(a, b)
|
||||
})
|
||||
})
|
||||
return func(ra Result[A], rb Result[B]) Result[C] {
|
||||
if ra.IsErr() {
|
||||
return Err[C](ra.right)
|
||||
}
|
||||
|
||||
if rb.IsErr() {
|
||||
return Err[C](rb.right)
|
||||
}
|
||||
|
||||
return Ok(f(ra.left, rb.left))
|
||||
}
|
||||
}
|
||||
|
||||
// Sink consumes a Result, either propagating its error or processing its
|
||||
// success value with a function that can fail.
|
||||
func (r Result[A]) Sink(f func(A) error) error {
|
||||
if r.IsErr() {
|
||||
return r.right
|
||||
}
|
||||
|
||||
return f(r.left)
|
||||
}
|
||||
|
||||
// TransposeResOpt transposes the Result[Option[A]] into a Option[Result[A]].
|
||||
// This has the effect of leaving an A value alone while inverting the Result
|
||||
// and Option layers. If there is no internal A value, it will convert the
|
||||
// non-success value to the proper one in the transposition.
|
||||
func TransposeResOpt[A any](r Result[Option[A]]) Option[Result[A]] {
|
||||
if r.IsErr() {
|
||||
return Some(Err[A](r.right))
|
||||
}
|
||||
|
||||
return MapOption(Ok[A])(r.left)
|
||||
}
|
||||
|
98
fn/result_test.go
Normal file
98
fn/result_test.go
Normal file
@ -0,0 +1,98 @@
|
||||
package fn
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestResultUnwrapOrFail(t *testing.T) {
|
||||
require.Equal(t, Ok(1).UnwrapOrFail(t), 1)
|
||||
}
|
||||
|
||||
func TestOkToSome(t *testing.T) {
|
||||
require.Equal(t, Ok(1).OkToSome(), Some(1))
|
||||
require.Equal(
|
||||
t, Err[uint8](errors.New("err")).OkToSome(), None[uint8](),
|
||||
)
|
||||
}
|
||||
|
||||
func TestMapOk(t *testing.T) {
|
||||
inc := func(i int) int {
|
||||
return i + 1
|
||||
}
|
||||
f := func(i int) bool {
|
||||
ok := Ok(i)
|
||||
return MapOk(inc)(ok) == Ok(inc(i))
|
||||
}
|
||||
|
||||
require.NoError(t, quick.Check(f, nil))
|
||||
}
|
||||
|
||||
func TestFlattenResult(t *testing.T) {
|
||||
f := func(i int) bool {
|
||||
e := fmt.Errorf("error")
|
||||
|
||||
x := FlattenResult(Ok(Ok(i))) == Ok(i)
|
||||
y := FlattenResult(Ok(Err[int](e))) == Err[int](e)
|
||||
z := FlattenResult(Err[Result[int]](e)) == Err[int](e)
|
||||
|
||||
return x && y && z
|
||||
}
|
||||
|
||||
require.NoError(t, quick.Check(f, nil))
|
||||
}
|
||||
|
||||
func TestPropTransposeResOptInverts(t *testing.T) {
|
||||
f := func(i uint) bool {
|
||||
var r Result[Option[uint]]
|
||||
switch i % 3 {
|
||||
case 0:
|
||||
r = Ok(Some(i))
|
||||
case 1:
|
||||
r = Ok(None[uint]())
|
||||
case 2:
|
||||
r = Errf[Option[uint]]("error")
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
odd := TransposeResOpt(TransposeOptRes(TransposeResOpt(r))) ==
|
||||
TransposeResOpt(r)
|
||||
|
||||
even := TransposeOptRes(TransposeResOpt(r)) == r
|
||||
|
||||
return odd && even
|
||||
}
|
||||
|
||||
require.NoError(t, quick.Check(f, nil))
|
||||
}
|
||||
|
||||
func TestSinkOnErrNoContinutationCall(t *testing.T) {
|
||||
called := false
|
||||
res := Err[uint8](errors.New("err")).Sink(
|
||||
func(a uint8) error {
|
||||
called = true
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
require.False(t, called)
|
||||
require.NotNil(t, res)
|
||||
}
|
||||
|
||||
func TestSinkOnOkContinuationCall(t *testing.T) {
|
||||
called := false
|
||||
res := Ok(uint8(1)).Sink(
|
||||
func(a uint8) error {
|
||||
called = true
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
require.True(t, called)
|
||||
require.Nil(t, res)
|
||||
}
|
130
fn/slice.go
130
fn/slice.go
@ -17,7 +17,7 @@ type Number interface {
|
||||
|
||||
// All returns true when the supplied predicate evaluates to true for all of
|
||||
// the values in the slice.
|
||||
func All[A any](pred func(A) bool, s []A) bool {
|
||||
func All[A any](s []A, pred Pred[A]) bool {
|
||||
for _, val := range s {
|
||||
if !pred(val) {
|
||||
return false
|
||||
@ -29,7 +29,7 @@ func All[A any](pred func(A) bool, s []A) bool {
|
||||
|
||||
// Any returns true when the supplied predicate evaluates to true for any of
|
||||
// the values in the slice.
|
||||
func Any[A any](pred func(A) bool, s []A) bool {
|
||||
func Any[A any](s []A, pred Pred[A]) bool {
|
||||
for _, val := range s {
|
||||
if pred(val) {
|
||||
return true
|
||||
@ -41,7 +41,7 @@ func Any[A any](pred func(A) bool, s []A) bool {
|
||||
|
||||
// Map applies the function argument to all members of the slice and returns a
|
||||
// slice of those return values.
|
||||
func Map[A, B any](f func(A) B, s []A) []B {
|
||||
func Map[A, B any](s []A, f func(A) B) []B {
|
||||
res := make([]B, 0, len(s))
|
||||
|
||||
for _, val := range s {
|
||||
@ -53,7 +53,7 @@ func Map[A, B any](f func(A) B, s []A) []B {
|
||||
|
||||
// Filter creates a new slice of values where all the members of the returned
|
||||
// slice pass the predicate that is supplied in the argument.
|
||||
func Filter[A any](pred Pred[A], s []A) []A {
|
||||
func Filter[A any](s []A, pred Pred[A]) []A {
|
||||
res := make([]A, 0)
|
||||
|
||||
for _, val := range s {
|
||||
@ -65,10 +65,38 @@ func Filter[A any](pred Pred[A], s []A) []A {
|
||||
return res
|
||||
}
|
||||
|
||||
// FilterMap takes a function argument that optionally produces a value and
|
||||
// returns a slice of the 'Some' return values.
|
||||
func FilterMap[A, B any](as []A, f func(A) Option[B]) []B {
|
||||
var bs []B
|
||||
|
||||
for _, a := range as {
|
||||
f(a).WhenSome(func(b B) {
|
||||
bs = append(bs, b)
|
||||
})
|
||||
}
|
||||
|
||||
return bs
|
||||
}
|
||||
|
||||
// TrimNones takes a slice of Option values and returns a slice of the Some
|
||||
// values in it.
|
||||
func TrimNones[A any](as []Option[A]) []A {
|
||||
var somes []A
|
||||
|
||||
for _, a := range as {
|
||||
a.WhenSome(func(b A) {
|
||||
somes = append(somes, b)
|
||||
})
|
||||
}
|
||||
|
||||
return somes
|
||||
}
|
||||
|
||||
// Foldl iterates through all members of the slice left to right and reduces
|
||||
// them pairwise with an accumulator value that is seeded with the seed value in
|
||||
// the argument.
|
||||
func Foldl[A, B any](f func(B, A) B, seed B, s []A) B {
|
||||
func Foldl[A, B any](seed B, s []A, f func(B, A) B) B {
|
||||
acc := seed
|
||||
|
||||
for _, val := range s {
|
||||
@ -80,7 +108,7 @@ func Foldl[A, B any](f func(B, A) B, seed B, s []A) B {
|
||||
|
||||
// Foldr, is exactly like Foldl except that it iterates over the slice from
|
||||
// right to left.
|
||||
func Foldr[A, B any](f func(A, B) B, seed B, s []A) B {
|
||||
func Foldr[A, B any](seed B, s []A, f func(A, B) B) B {
|
||||
acc := seed
|
||||
|
||||
for i := range s {
|
||||
@ -92,7 +120,7 @@ func Foldr[A, B any](f func(A, B) B, seed B, s []A) B {
|
||||
|
||||
// Find returns the first value that passes the supplied predicate, or None if
|
||||
// the value wasn't found.
|
||||
func Find[A any](pred Pred[A], s []A) Option[A] {
|
||||
func Find[A any](s []A, pred Pred[A]) Option[A] {
|
||||
for _, val := range s {
|
||||
if pred(val) {
|
||||
return Some(val)
|
||||
@ -104,7 +132,7 @@ func Find[A any](pred Pred[A], s []A) Option[A] {
|
||||
|
||||
// FindIdx returns the first value that passes the supplied predicate along with
|
||||
// its index in the slice. If no satisfactory value is found, None is returned.
|
||||
func FindIdx[A any](pred Pred[A], s []A) Option[T2[int, A]] {
|
||||
func FindIdx[A any](s []A, pred Pred[A]) Option[T2[int, A]] {
|
||||
for i, val := range s {
|
||||
if pred(val) {
|
||||
return Some(NewT2[int, A](i, val))
|
||||
@ -116,16 +144,14 @@ func FindIdx[A any](pred Pred[A], s []A) Option[T2[int, A]] {
|
||||
|
||||
// Elem returns true if the element in the argument is found in the slice
|
||||
func Elem[A comparable](a A, s []A) bool {
|
||||
return Any(Eq(a), s)
|
||||
return Any(s, Eq(a))
|
||||
}
|
||||
|
||||
// Flatten takes a slice of slices and returns a concatenation of those slices.
|
||||
func Flatten[A any](s [][]A) []A {
|
||||
sz := Foldr(
|
||||
func(l []A, acc uint64) uint64 {
|
||||
sz := Foldr(0, s, func(l []A, acc uint64) uint64 {
|
||||
return uint64(len(l)) + acc
|
||||
}, 0, s,
|
||||
)
|
||||
})
|
||||
|
||||
res := make([]A, 0, sz)
|
||||
|
||||
@ -150,7 +176,7 @@ func Replicate[A any](n uint, val A) []A {
|
||||
// Span, applied to a predicate and a slice, returns two slices where the first
|
||||
// element is the longest prefix (possibly empty) of slice elements that
|
||||
// satisfy the predicate and second element is the remainder of the slice.
|
||||
func Span[A any](pred func(A) bool, s []A) ([]A, []A) {
|
||||
func Span[A any](s []A, pred Pred[A]) ([]A, []A) {
|
||||
for i := range s {
|
||||
if !pred(s[i]) {
|
||||
fst := make([]A, i)
|
||||
@ -183,7 +209,7 @@ func SplitAt[A any](n uint, s []A) ([]A, []A) {
|
||||
|
||||
// ZipWith combines slice elements with the same index using the function
|
||||
// argument, returning a slice of the results.
|
||||
func ZipWith[A, B, C any](f func(A, B) C, a []A, b []B) []C {
|
||||
func ZipWith[A, B, C any](a []A, b []B, f func(A, B) C) []C {
|
||||
var l uint
|
||||
|
||||
if la, lb := len(a), len(b); la < lb {
|
||||
@ -218,9 +244,9 @@ func SliceToMap[A any, K comparable, V any](s []A, keyFunc func(A) K,
|
||||
|
||||
// Sum calculates the sum of a slice of numbers, `items`.
|
||||
func Sum[B Number](items []B) B {
|
||||
return Foldl(func(a, b B) B {
|
||||
return Foldl(0, items, func(a, b B) B {
|
||||
return a + b
|
||||
}, 0, items)
|
||||
})
|
||||
}
|
||||
|
||||
// HasDuplicates checks if the given slice contains any duplicate elements.
|
||||
@ -233,9 +259,7 @@ func HasDuplicates[A comparable](items []A) bool {
|
||||
// ForEachConc maps the argument function over the slice, spawning a new
|
||||
// goroutine for each element in the slice and then awaits all results before
|
||||
// returning them.
|
||||
func ForEachConc[A, B any](f func(A) B,
|
||||
as []A) []B {
|
||||
|
||||
func ForEachConc[A, B any](as []A, f func(A) B) []B {
|
||||
var wait sync.WaitGroup
|
||||
ctx := context.Background()
|
||||
|
||||
@ -318,3 +342,69 @@ func Unsnoc[A any](items []A) Option[T2[[]A, A]] {
|
||||
func Len[A any](items []A) uint {
|
||||
return uint(len(items))
|
||||
}
|
||||
|
||||
// CollectOptions collects a list of Options into a single Option of the list of
|
||||
// Some values in it. If there are any Nones present it will return None.
|
||||
func CollectOptions[A any](options []Option[A]) Option[[]A] {
|
||||
// We intentionally do a separate None checking pass here to avoid
|
||||
// allocating a new slice for the values until we're sure we need to.
|
||||
for _, r := range options {
|
||||
if r.IsNone() {
|
||||
return None[[]A]()
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we're sure we have no Nones, we can just do an unchecked
|
||||
// index into the some value of the option.
|
||||
return Some(Map(options, func(o Option[A]) A { return o.some }))
|
||||
}
|
||||
|
||||
// CollectResults collects a list of Results into a single Result of the list of
|
||||
// Ok values in it. If there are any errors present it will return the first
|
||||
// error encountered.
|
||||
func CollectResults[A any](results []Result[A]) Result[[]A] {
|
||||
// We intentionally do a separate error checking pass here to avoid
|
||||
// allocating a new slice for the results until we're sure we need to.
|
||||
for _, r := range results {
|
||||
if r.IsErr() {
|
||||
return Err[[]A](r.right)
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we're sure we have no errors, we can just do an unchecked
|
||||
// index into the left side of the result.
|
||||
return Ok(Map(results, func(r Result[A]) A { return r.left }))
|
||||
}
|
||||
|
||||
// TraverseOption traverses a slice of A values, applying the provided
|
||||
// function to each, collecting the results into an Option of a slice of B
|
||||
// values. If any of the results are None, the entire result is None.
|
||||
func TraverseOption[A, B any](as []A, f func(A) Option[B]) Option[[]B] {
|
||||
var bs []B
|
||||
for _, a := range as {
|
||||
b := f(a)
|
||||
if b.IsNone() {
|
||||
return None[[]B]()
|
||||
}
|
||||
bs = append(bs, b.some)
|
||||
}
|
||||
|
||||
return Some(bs)
|
||||
}
|
||||
|
||||
// TraverseResult traverses a slice of A values, applying the provided
|
||||
// function to each, collecting the results into a Result of a slice of B
|
||||
// values. If any of the results are Err, the entire result is the first
|
||||
// error encountered.
|
||||
func TraverseResult[A, B any](as []A, f func(A) Result[B]) Result[[]B] {
|
||||
var bs []B
|
||||
for _, a := range as {
|
||||
b := f(a)
|
||||
if b.IsErr() {
|
||||
return Err[[]B](b.right)
|
||||
}
|
||||
bs = append(bs, b.left)
|
||||
}
|
||||
|
||||
return Ok(bs)
|
||||
}
|
||||
|
312
fn/slice_test.go
312
fn/slice_test.go
@ -2,6 +2,7 @@ package fn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"slices"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
@ -15,30 +16,30 @@ func odd(a int) bool { return a%2 != 0 }
|
||||
|
||||
func TestAll(t *testing.T) {
|
||||
x := []int{0, 2, 4, 6, 8}
|
||||
require.True(t, All(even, x))
|
||||
require.False(t, All(odd, x))
|
||||
require.True(t, All(x, even))
|
||||
require.False(t, All(x, odd))
|
||||
|
||||
y := []int{1, 3, 5, 7, 9}
|
||||
require.False(t, All(even, y))
|
||||
require.True(t, All(odd, y))
|
||||
require.False(t, All(y, even))
|
||||
require.True(t, All(y, odd))
|
||||
|
||||
z := []int{0, 2, 4, 6, 9}
|
||||
require.False(t, All(even, z))
|
||||
require.False(t, All(odd, z))
|
||||
require.False(t, All(z, even))
|
||||
require.False(t, All(z, odd))
|
||||
}
|
||||
|
||||
func TestAny(t *testing.T) {
|
||||
x := []int{1, 3, 5, 7, 9}
|
||||
require.False(t, Any(even, x))
|
||||
require.True(t, Any(odd, x))
|
||||
require.False(t, Any(x, even))
|
||||
require.True(t, Any(x, odd))
|
||||
|
||||
y := []int{0, 3, 5, 7, 9}
|
||||
require.True(t, Any(even, y))
|
||||
require.True(t, Any(odd, y))
|
||||
require.True(t, Any(y, even))
|
||||
require.True(t, Any(y, odd))
|
||||
|
||||
z := []int{0, 2, 4, 6, 8}
|
||||
require.True(t, Any(even, z))
|
||||
require.False(t, Any(odd, z))
|
||||
require.True(t, Any(z, even))
|
||||
require.False(t, Any(z, odd))
|
||||
}
|
||||
|
||||
func TestMap(t *testing.T) {
|
||||
@ -46,7 +47,7 @@ func TestMap(t *testing.T) {
|
||||
|
||||
x := []int{0, 2, 4, 6, 8}
|
||||
|
||||
y := Map(inc, x)
|
||||
y := Map(x, inc)
|
||||
|
||||
z := []int{1, 3, 5, 7, 9}
|
||||
|
||||
@ -56,11 +57,11 @@ func TestMap(t *testing.T) {
|
||||
func TestFilter(t *testing.T) {
|
||||
x := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||
|
||||
y := Filter(even, x)
|
||||
y := Filter(x, even)
|
||||
|
||||
require.True(t, All(even, y))
|
||||
require.True(t, All(y, even))
|
||||
|
||||
z := Filter(odd, y)
|
||||
z := Filter(y, odd)
|
||||
|
||||
require.Zero(t, len(z))
|
||||
}
|
||||
@ -71,7 +72,7 @@ func TestFoldl(t *testing.T) {
|
||||
|
||||
x := []int{0, 1, 2, 3, 4}
|
||||
|
||||
r := Foldl(stupid, seed, x)
|
||||
r := Foldl(seed, x, stupid)
|
||||
|
||||
require.True(t, slices.Equal(x, r))
|
||||
}
|
||||
@ -82,7 +83,7 @@ func TestFoldr(t *testing.T) {
|
||||
|
||||
x := []int{0, 1, 2, 3, 4}
|
||||
|
||||
z := Foldr(stupid, seed, x)
|
||||
z := Foldr(seed, x, stupid)
|
||||
|
||||
slices.Reverse[[]int](x)
|
||||
|
||||
@ -95,9 +96,9 @@ func TestFind(t *testing.T) {
|
||||
div3 := func(a int) bool { return a%3 == 0 }
|
||||
div8 := func(a int) bool { return a%8 == 0 }
|
||||
|
||||
require.Equal(t, Find(div3, x), Some(12))
|
||||
require.Equal(t, Find(x, div3), Some(12))
|
||||
|
||||
require.Equal(t, Find(div8, x), None[int]())
|
||||
require.Equal(t, Find(x, div8), None[int]())
|
||||
}
|
||||
|
||||
func TestFlatten(t *testing.T) {
|
||||
@ -116,7 +117,7 @@ func TestSpan(t *testing.T) {
|
||||
x := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||
lt5 := func(a int) bool { return a < 5 }
|
||||
|
||||
low, high := Span(lt5, x)
|
||||
low, high := Span(x, lt5)
|
||||
|
||||
require.True(t, slices.Equal(low, []int{0, 1, 2, 3, 4}))
|
||||
require.True(t, slices.Equal(high, []int{5, 6, 7, 8, 9}))
|
||||
@ -134,7 +135,7 @@ func TestZipWith(t *testing.T) {
|
||||
eq := func(a, b int) bool { return a == b }
|
||||
x := []int{0, 1, 2, 3, 4}
|
||||
y := Replicate(5, 1)
|
||||
z := ZipWith(eq, x, y)
|
||||
z := ZipWith(x, y, eq)
|
||||
require.True(t, slices.Equal(
|
||||
z, []bool{false, true, false, false, false},
|
||||
))
|
||||
@ -289,8 +290,8 @@ func TestHasDuplicates(t *testing.T) {
|
||||
func TestPropForEachConcMapIsomorphism(t *testing.T) {
|
||||
f := func(incSize int, s []int) bool {
|
||||
inc := func(i int) int { return i + incSize }
|
||||
mapped := Map(inc, s)
|
||||
conc := ForEachConc(inc, s)
|
||||
mapped := Map(s, inc)
|
||||
conc := ForEachConc(s, inc)
|
||||
|
||||
return slices.Equal(mapped, conc)
|
||||
}
|
||||
@ -318,7 +319,7 @@ func TestPropForEachConcOutperformsMapWhenExpensive(t *testing.T) {
|
||||
c := make(chan bool, 1)
|
||||
|
||||
go func() {
|
||||
Map(inc, s)
|
||||
Map(s, inc)
|
||||
select {
|
||||
case c <- false:
|
||||
default:
|
||||
@ -326,7 +327,7 @@ func TestPropForEachConcOutperformsMapWhenExpensive(t *testing.T) {
|
||||
}()
|
||||
|
||||
go func() {
|
||||
ForEachConc(inc, s)
|
||||
ForEachConc(s, inc)
|
||||
select {
|
||||
case c <- true:
|
||||
default:
|
||||
@ -351,14 +352,14 @@ func TestPropFindIdxFindIdentity(t *testing.T) {
|
||||
return i%div == mod
|
||||
}
|
||||
|
||||
foundIdx := FindIdx(pred, s)
|
||||
foundIdx := FindIdx(s, pred)
|
||||
|
||||
// onlyVal :: Option[T2[A, B]] -> Option[B]
|
||||
onlyVal := MapOption(func(t2 T2[int, uint8]) uint8 {
|
||||
return t2.Second()
|
||||
})
|
||||
|
||||
valuesEqual := Find(pred, s) == onlyVal(foundIdx)
|
||||
valuesEqual := Find(s, pred) == onlyVal(foundIdx)
|
||||
|
||||
idxGetsVal := ElimOption(
|
||||
foundIdx,
|
||||
@ -382,7 +383,7 @@ func TestPropLastTailIsLast(t *testing.T) {
|
||||
return true
|
||||
}
|
||||
|
||||
return Last(s) == ChainOption(Last[uint8])(Tail(s))
|
||||
return Last(s) == FlatMapOption(Last[uint8])(Tail(s))
|
||||
}
|
||||
|
||||
require.NoError(t, quick.Check(f, nil))
|
||||
@ -395,7 +396,7 @@ func TestPropHeadInitIsHead(t *testing.T) {
|
||||
return true
|
||||
}
|
||||
|
||||
return Head(s) == ChainOption(Head[uint8])(Init(s))
|
||||
return Head(s) == FlatMapOption(Head[uint8])(Init(s))
|
||||
}
|
||||
|
||||
require.NoError(t, quick.Check(f, nil))
|
||||
@ -413,6 +414,122 @@ func TestPropTailDecrementsLength(t *testing.T) {
|
||||
require.NoError(t, quick.Check(f, nil))
|
||||
}
|
||||
|
||||
func TestSingletonTailIsEmpty(t *testing.T) {
|
||||
require.Equal(t, Tail([]int{1}), Some([]int{}))
|
||||
}
|
||||
|
||||
func TestSingletonInitIsEmpty(t *testing.T) {
|
||||
require.Equal(t, Init([]int{1}), Some([]int{}))
|
||||
}
|
||||
|
||||
// TestPropAlwaysNoneEmptyFilterMap ensures the property that if we were to
|
||||
// always return none from our filter function then we would end up with an
|
||||
// empty slice.
|
||||
func TestPropAlwaysNoneEmptyFilterMap(t *testing.T) {
|
||||
f := func(s []int) bool {
|
||||
filtered := FilterMap(s, Const[int](None[int]()))
|
||||
return len(filtered) == 0
|
||||
}
|
||||
|
||||
require.NoError(t, quick.Check(f, nil))
|
||||
}
|
||||
|
||||
// TestPropFilterMapSomeIdentity ensures that if the filter function is a
|
||||
// trivial lift into Option space, then we will get back the original slice.
|
||||
func TestPropFilterMapSomeIdentity(t *testing.T) {
|
||||
f := func(s []int) bool {
|
||||
filtered := FilterMap(s, Some[int])
|
||||
return slices.Equal(s, filtered)
|
||||
}
|
||||
|
||||
require.NoError(t, quick.Check(f, nil))
|
||||
}
|
||||
|
||||
// TestPropFilterMapCantGrow ensures that regardless of the filter functions
|
||||
// return values, we will never end up with a slice larger than the original.
|
||||
func TestPropFilterMapCantGrow(t *testing.T) {
|
||||
f := func(s []int) bool {
|
||||
filterFunc := func(i int) Option[int] {
|
||||
if rand.Int()%2 == 0 {
|
||||
return None[int]()
|
||||
}
|
||||
|
||||
return Some(i + rand.Int())
|
||||
}
|
||||
|
||||
return len(FilterMap(s, filterFunc)) <= len(s)
|
||||
}
|
||||
|
||||
require.NoError(t, quick.Check(f, nil))
|
||||
}
|
||||
|
||||
// TestPropFilterMapBisectIdentity ensures that the concatenation of the
|
||||
// FilterMaps is the same as the FilterMap of the concatenation.
|
||||
func TestPropFilterMapBisectIdentity(t *testing.T) {
|
||||
f := func(s []int) bool {
|
||||
sz := len(s)
|
||||
first := s[0 : sz/2]
|
||||
second := s[sz/2 : sz]
|
||||
|
||||
filterFunc := func(i int) Option[int] {
|
||||
if i%2 == 0 {
|
||||
return None[int]()
|
||||
}
|
||||
|
||||
return Some(i)
|
||||
}
|
||||
|
||||
firstFiltered := FilterMap(first, filterFunc)
|
||||
secondFiltered := FilterMap(second, filterFunc)
|
||||
allFiltered := FilterMap(s, filterFunc)
|
||||
reassembled := slices.Concat(firstFiltered, secondFiltered)
|
||||
|
||||
return slices.Equal(allFiltered, reassembled)
|
||||
}
|
||||
|
||||
require.NoError(t, quick.Check(f, nil))
|
||||
}
|
||||
|
||||
// TestTraverseOkIdentity ensures that trivially lifting the elements of a slice
|
||||
// via the Ok function during a Traverse is equivalent to just lifting the
|
||||
// entire slice via the Ok function.
|
||||
func TestPropTraverseOkIdentity(t *testing.T) {
|
||||
f := func(s []int) bool {
|
||||
traversed := TraverseResult(s, Ok[int])
|
||||
|
||||
traversedOk := traversed.UnwrapOrFail(t)
|
||||
|
||||
return slices.Equal(s, traversedOk)
|
||||
}
|
||||
|
||||
require.NoError(t, quick.Check(f, nil))
|
||||
}
|
||||
|
||||
// TestPropTraverseSingleErrEjection ensures that if the traverse function
|
||||
// returns even a single error, then the entire Traverse will error.
|
||||
func TestPropTraverseSingleErrEjection(t *testing.T) {
|
||||
f := func(s []int, errIdx uint8) bool {
|
||||
if len(s) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
errIdxMut := int(errIdx) % len(s)
|
||||
f := func(i int) Result[int] {
|
||||
if errIdxMut == 0 {
|
||||
return Errf[int]("err")
|
||||
}
|
||||
|
||||
errIdxMut--
|
||||
|
||||
return Ok(i)
|
||||
}
|
||||
|
||||
return TraverseResult(s, f).IsErr()
|
||||
}
|
||||
|
||||
require.NoError(t, quick.Check(f, nil))
|
||||
}
|
||||
|
||||
func TestPropInitDecrementsLength(t *testing.T) {
|
||||
f := func(s []uint8) bool {
|
||||
if len(s) == 0 {
|
||||
@ -425,10 +542,137 @@ func TestPropInitDecrementsLength(t *testing.T) {
|
||||
require.NoError(t, quick.Check(f, nil))
|
||||
}
|
||||
|
||||
func TestSingletonTailIsEmpty(t *testing.T) {
|
||||
require.Equal(t, Tail([]int{1}), Some([]int{}))
|
||||
// TestPropTrimNonesEqualsFilterMapIden checks that if we use the Iden
|
||||
// function when calling FilterMap on a slice of Options that we get the same
|
||||
// result as we would if we called TrimNones on it.
|
||||
func TestPropTrimNonesEqualsFilterMapIden(t *testing.T) {
|
||||
f := func(s []uint8) bool {
|
||||
withNones := make([]Option[uint8], len(s))
|
||||
for i, x := range s {
|
||||
if x%3 == 0 {
|
||||
withNones[i] = None[uint8]()
|
||||
} else {
|
||||
withNones[i] = Some(x)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSingletonInitIsEmpty(t *testing.T) {
|
||||
require.Equal(t, Init([]int{1}), Some([]int{}))
|
||||
return slices.Equal(
|
||||
FilterMap(withNones, Iden[Option[uint8]]),
|
||||
TrimNones(withNones),
|
||||
)
|
||||
}
|
||||
|
||||
require.NoError(t, quick.Check(f, nil))
|
||||
}
|
||||
|
||||
// TestPropCollectResultsSingleErrEjection ensures that if there is even a
|
||||
// single error in the batch, then CollectResults will return an error.
|
||||
func TestPropCollectResultsSingleErrEjection(t *testing.T) {
|
||||
f := func(s []int, errIdx uint8) bool {
|
||||
if len(s) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
errIdxMut := int(errIdx) % len(s)
|
||||
f := func(i int) Result[int] {
|
||||
if errIdxMut == 0 {
|
||||
return Errf[int]("err")
|
||||
}
|
||||
|
||||
errIdxMut--
|
||||
|
||||
return Ok(i)
|
||||
}
|
||||
|
||||
return CollectResults(Map(s, f)).IsErr()
|
||||
}
|
||||
|
||||
require.NoError(t, quick.Check(f, nil))
|
||||
}
|
||||
|
||||
// TestPropCollectResultsNoErrUnwrap ensures that if there are no errors in the
|
||||
// results then we end up with unwrapping all of the Results in the slice.
|
||||
func TestPropCollectResultsNoErrUnwrap(t *testing.T) {
|
||||
f := func(s []int) bool {
|
||||
res := CollectResults(Map(s, Ok[int]))
|
||||
return !res.isRight && slices.Equal(res.left, s)
|
||||
}
|
||||
|
||||
require.NoError(t, quick.Check(f, nil))
|
||||
}
|
||||
|
||||
// TestPropTraverseSomeIdentity ensures that trivially lifting the elements of a
|
||||
// slice via the Some function during a Traverse is equivalent to just lifting
|
||||
// the entire slice via the Some function.
|
||||
func TestPropTraverseSomeIdentity(t *testing.T) {
|
||||
f := func(s []int) bool {
|
||||
traversed := TraverseOption(s, Some[int])
|
||||
|
||||
traversedSome := traversed.UnwrapOrFail(t)
|
||||
|
||||
return slices.Equal(s, traversedSome)
|
||||
}
|
||||
|
||||
require.NoError(t, quick.Check(f, nil))
|
||||
}
|
||||
|
||||
// TestTraverseSingleNoneEjection ensures that if the traverse function returns
|
||||
// even a single None, then the entire Traverse will return None.
|
||||
func TestTraverseSingleNoneEjection(t *testing.T) {
|
||||
f := func(s []int, errIdx uint8) bool {
|
||||
if len(s) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
errIdxMut := int(errIdx) % len(s)
|
||||
f := func(i int) Option[int] {
|
||||
if errIdxMut == 0 {
|
||||
return None[int]()
|
||||
}
|
||||
|
||||
errIdxMut--
|
||||
|
||||
return Some(i)
|
||||
}
|
||||
|
||||
return TraverseOption(s, f).IsNone()
|
||||
}
|
||||
|
||||
require.NoError(t, quick.Check(f, nil))
|
||||
}
|
||||
|
||||
// TestPropCollectOptionsSingleNoneEjection ensures that if there is even a
|
||||
// single None in the batch, then CollectOptions will return a None.
|
||||
func TestPropCollectOptionsSingleNoneEjection(t *testing.T) {
|
||||
f := func(s []int, errIdx uint8) bool {
|
||||
if len(s) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
errIdxMut := int(errIdx) % len(s)
|
||||
f := func(i int) Option[int] {
|
||||
if errIdxMut == 0 {
|
||||
return None[int]()
|
||||
}
|
||||
|
||||
errIdxMut--
|
||||
|
||||
return Some(i)
|
||||
}
|
||||
|
||||
return CollectOptions(Map(s, f)).IsNone()
|
||||
}
|
||||
|
||||
require.NoError(t, quick.Check(f, nil))
|
||||
}
|
||||
|
||||
// TestPropCollectOptionsNoNoneUnwrap ensures that if there are no nones in the
|
||||
// options then we end up with unwrapping all of the Options in the slice.
|
||||
func TestPropCollectOptionsNoNoneUnwrap(t *testing.T) {
|
||||
f := func(s []int) bool {
|
||||
res := CollectOptions(Map(s, Some[int]))
|
||||
return res.isSome && slices.Equal(res.some, s)
|
||||
}
|
||||
|
||||
require.NoError(t, quick.Check(f, nil))
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user