diff --git a/fn/option.go b/fn/option.go index 06007f781..57245a9ba 100644 --- a/fn/option.go +++ b/fn/option.go @@ -251,3 +251,15 @@ func (o Option[A]) SomeToOkf(errString string, args ...interface{}) Result[A] { OptionToLeft[A, A, error](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)} +} diff --git a/fn/option_test.go b/fn/option_test.go index 484132d69..69f6608d3 100644 --- a/fn/option_test.go +++ b/fn/option_test.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "testing" + "testing/quick" "github.com/stretchr/testify/require" ) @@ -26,3 +27,27 @@ func TestSomeToOkf(t *testing.T) { 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)) +} diff --git a/fn/result.go b/fn/result.go index 5be5362ba..3fa6492d9 100644 --- a/fn/result.go +++ b/fn/result.go @@ -223,3 +223,15 @@ func AndThen2[A, B, C any](ra Result[A], rb Result[B], }) }) } + +// 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) +} diff --git a/fn/result_test.go b/fn/result_test.go index bfbe8d9bd..5830b8214 100644 --- a/fn/result_test.go +++ b/fn/result_test.go @@ -45,3 +45,28 @@ func TestFlattenResult(t *testing.T) { 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)) +}