Skip to content

Commit

Permalink
Merge pull request #8985 from ProofOfKeags/fn/collect-results
Browse files Browse the repository at this point in the history
fn: more fn goodies
  • Loading branch information
guggero authored Nov 7, 2024
2 parents e3cc4d7 + 9f35664 commit f42636f
Show file tree
Hide file tree
Showing 10 changed files with 680 additions and 117 deletions.
10 changes: 5 additions & 5 deletions fn/either.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -52,19 +52,19 @@ 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]()
}

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]()
}
Expand Down
18 changes: 8 additions & 10 deletions fn/either_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion fn/fn.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion fn/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
},
Expand Down
60 changes: 45 additions & 15 deletions fn/option.go
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -72,14 +77,9 @@ func (o Option[A]) UnwrapOrFunc(f func() A) A {
func (o Option[A]) UnwrapOrFail(t *testing.T) A {
t.Helper()

if o.isSome {
return o.some
}

t.Fatalf("Option[%T] was None()", o.some)
require.True(t, o.isSome, "Option[%T] was None()", o.some)

var zero A
return zero
return o.some
}

// UnwrapOrErr is used to extract a value from an option, if the option is
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -225,22 +225,52 @@ 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)
}

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 changes: 53 additions & 0 deletions fn/option_test.go
Original file line number Diff line number Diff line change
@@ -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))
}
Loading

0 comments on commit f42636f

Please sign in to comment.