Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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[A] represents a value which may or may not be there. This is very
// often preferable to nil-able pointers.
Expand Down Expand Up @@ -61,14 +66,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 @@ -133,11 +133,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 @@ -214,22 +214,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