mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-05-05 17:30:21 +02:00
fn: reimplement internals of either, add tests
This commit is contained in:
parent
1dd56f1b2a
commit
c4df2f1dce
76
fn/either.go
76
fn/either.go
@ -2,97 +2,119 @@ package fn
|
|||||||
|
|
||||||
// Either is a type that can be either left or right.
|
// Either is a type that can be either left or right.
|
||||||
type Either[L any, R any] struct {
|
type Either[L any, R any] struct {
|
||||||
left Option[L]
|
isRight bool
|
||||||
right Option[R]
|
left L
|
||||||
|
right R
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLeft returns an Either with a left value.
|
// NewLeft returns an Either with a left value.
|
||||||
func NewLeft[L any, R any](l L) Either[L, R] {
|
func NewLeft[L any, R any](l L) Either[L, R] {
|
||||||
return Either[L, R]{left: Some(l), right: None[R]()}
|
return Either[L, R]{left: l}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRight returns an Either with a right value.
|
// NewRight returns an Either with a right value.
|
||||||
func NewRight[L any, R any](r R) Either[L, R] {
|
func NewRight[L any, R any](r R) Either[L, R] {
|
||||||
return Either[L, R]{left: None[L](), right: Some(r)}
|
return Either[L, R]{isRight: true, right: r}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ElimEither is the universal Either eliminator. It can be used to safely
|
// ElimEither is the universal Either eliminator. It can be used to safely
|
||||||
// handle all possible values inside the Either by supplying two continuations,
|
// handle all possible values inside the Either by supplying two continuations,
|
||||||
// one for each side of the Either.
|
// 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](f func(L) O, g func(R) O, e Either[L, R]) O {
|
||||||
if e.left.IsSome() {
|
if !e.isRight {
|
||||||
return f(e.left.some)
|
return f(e.left)
|
||||||
}
|
}
|
||||||
|
|
||||||
return g(e.right.some)
|
return g(e.right)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WhenLeft executes the given function if the Either is left.
|
// WhenLeft executes the given function if the Either is left.
|
||||||
func (e Either[L, R]) WhenLeft(f func(L)) {
|
func (e Either[L, R]) WhenLeft(f func(L)) {
|
||||||
e.left.WhenSome(f)
|
if !e.isRight {
|
||||||
|
f(e.left)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WhenRight executes the given function if the Either is right.
|
// WhenRight executes the given function if the Either is right.
|
||||||
func (e Either[L, R]) WhenRight(f func(R)) {
|
func (e Either[L, R]) WhenRight(f func(R)) {
|
||||||
e.right.WhenSome(f)
|
if e.isRight {
|
||||||
|
f(e.right)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsLeft returns true if the Either is left.
|
// IsLeft returns true if the Either is left.
|
||||||
func (e Either[L, R]) IsLeft() bool {
|
func (e Either[L, R]) IsLeft() bool {
|
||||||
return e.left.IsSome()
|
return !e.isRight
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRight returns true if the Either is right.
|
// IsRight returns true if the Either is right.
|
||||||
func (e Either[L, R]) IsRight() bool {
|
func (e Either[L, R]) IsRight() bool {
|
||||||
return e.right.IsSome()
|
return e.isRight
|
||||||
}
|
}
|
||||||
|
|
||||||
// LeftToOption converts a Left value to an Option, returning None if the inner
|
// LeftToOption converts a Left value to an Option, returning None if the inner
|
||||||
// Either value is a Right value.
|
// Either value is a Right value.
|
||||||
func (e Either[L, R]) LeftToOption() Option[L] {
|
func (e Either[L, R]) LeftToOption() Option[L] {
|
||||||
return e.left
|
if e.isRight {
|
||||||
|
return None[L]()
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(e.left)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RightToOption converts a Right value to an Option, returning None if the
|
// RightToOption converts a Right value to an Option, returning None if the
|
||||||
// inner Either value is a Left value.
|
// inner Either value is a Left value.
|
||||||
func (e Either[L, R]) RightToOption() Option[R] {
|
func (e Either[L, R]) RightToOption() Option[R] {
|
||||||
return e.right
|
if !e.isRight {
|
||||||
|
return None[R]()
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(e.right)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnwrapLeftOr will extract the Left value from the Either if it is present
|
// UnwrapLeftOr will extract the Left value from the Either if it is present
|
||||||
// returning the supplied default if it is not.
|
// returning the supplied default if it is not.
|
||||||
func (e Either[L, R]) UnwrapLeftOr(l L) L {
|
func (e Either[L, R]) UnwrapLeftOr(l L) L {
|
||||||
return e.left.UnwrapOr(l)
|
if e.isRight {
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.left
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnwrapRightOr will extract the Right value from the Either if it is present
|
// UnwrapRightOr will extract the Right value from the Either if it is present
|
||||||
// returning the supplied default if it is not.
|
// returning the supplied default if it is not.
|
||||||
func (e Either[L, R]) UnwrapRightOr(r R) R {
|
func (e Either[L, R]) UnwrapRightOr(r R) R {
|
||||||
return e.right.UnwrapOr(r)
|
if !e.isRight {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.right
|
||||||
}
|
}
|
||||||
|
|
||||||
// Swap reverses the type argument order. This can be useful as an adapter
|
// Swap reverses the type argument order. This can be useful as an adapter
|
||||||
// between APIs.
|
// between APIs.
|
||||||
func (e Either[L, R]) Swap() Either[R, L] {
|
func (e Either[L, R]) Swap() Either[R, L] {
|
||||||
return Either[R, L]{
|
return Either[R, L]{
|
||||||
left: e.right,
|
isRight: !e.isRight,
|
||||||
right: e.left,
|
left: e.right,
|
||||||
|
right: e.left,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MapLeft maps the left value of the Either to a new value.
|
// MapLeft maps the left value of the Either to a new value.
|
||||||
func MapLeft[L, R, O any](f func(L) O) func(Either[L, R]) Either[O, R] {
|
func MapLeft[L, R, O any](f func(L) O) func(Either[L, R]) Either[O, R] {
|
||||||
return func(e Either[L, R]) Either[O, R] {
|
return func(e Either[L, R]) Either[O, R] {
|
||||||
if e.IsLeft() {
|
if !e.isRight {
|
||||||
return Either[O, R]{
|
return Either[O, R]{
|
||||||
left: MapOption(f)(e.left),
|
isRight: false,
|
||||||
right: None[R](),
|
left: f(e.left),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Either[O, R]{
|
return Either[O, R]{
|
||||||
left: None[O](),
|
isRight: true,
|
||||||
right: e.right,
|
right: e.right,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,16 +122,16 @@ func MapLeft[L, R, O any](f func(L) O) func(Either[L, R]) Either[O, R] {
|
|||||||
// MapRight maps the right value of the Either to a new value.
|
// MapRight maps the right value of the Either to a new value.
|
||||||
func MapRight[L, R, O any](f func(R) O) func(Either[L, R]) Either[L, O] {
|
func MapRight[L, R, O any](f func(R) O) func(Either[L, R]) Either[L, O] {
|
||||||
return func(e Either[L, R]) Either[L, O] {
|
return func(e Either[L, R]) Either[L, O] {
|
||||||
if e.IsRight() {
|
if e.isRight {
|
||||||
return Either[L, O]{
|
return Either[L, O]{
|
||||||
left: None[L](),
|
isRight: true,
|
||||||
right: MapOption(f)(e.right),
|
right: f(e.right),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Either[L, O]{
|
return Either[L, O]{
|
||||||
left: e.left,
|
isRight: false,
|
||||||
right: None[O](),
|
left: e.left,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
139
fn/either_test.go
Normal file
139
fn/either_test.go
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
package fn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"testing/quick"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPropConstructorEliminatorDuality(t *testing.T) {
|
||||||
|
f := func(i int, s string, isRight bool) bool {
|
||||||
|
Len := func(s string) int { return len(s) } // smh
|
||||||
|
if isRight {
|
||||||
|
v := ElimEither(
|
||||||
|
Iden[int],
|
||||||
|
Len,
|
||||||
|
NewRight[int, string](s),
|
||||||
|
)
|
||||||
|
return v == Len(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
v := ElimEither(
|
||||||
|
Iden[int],
|
||||||
|
Len,
|
||||||
|
NewLeft[int, string](i),
|
||||||
|
)
|
||||||
|
return v == i
|
||||||
|
}
|
||||||
|
if err := quick.Check(f, nil); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPropWhenClauseExclusivity(t *testing.T) {
|
||||||
|
f := func(i int, isRight bool) bool {
|
||||||
|
var e Either[int, int]
|
||||||
|
if isRight {
|
||||||
|
e = NewRight[int, int](i)
|
||||||
|
} else {
|
||||||
|
e = NewLeft[int, int](i)
|
||||||
|
}
|
||||||
|
z := 0
|
||||||
|
e.WhenLeft(func(x int) { z += x })
|
||||||
|
e.WhenRight(func(x int) { z += x })
|
||||||
|
|
||||||
|
return z != 2*i && e.IsLeft() != e.IsRight()
|
||||||
|
}
|
||||||
|
if err := quick.Check(f, nil); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPropSwapEitherSelfInverting(t *testing.T) {
|
||||||
|
f := func(i int, s string, isRight bool) bool {
|
||||||
|
var e Either[int, string]
|
||||||
|
if isRight {
|
||||||
|
e = NewRight[int, string](s)
|
||||||
|
} else {
|
||||||
|
e = NewLeft[int, string](i)
|
||||||
|
}
|
||||||
|
return e.Swap().Swap() == e
|
||||||
|
}
|
||||||
|
if err := quick.Check(f, nil); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPropMapLeftIdentity(t *testing.T) {
|
||||||
|
f := func(i int, s string, isRight bool) bool {
|
||||||
|
var e Either[int, string]
|
||||||
|
if isRight {
|
||||||
|
e = NewRight[int, string](s)
|
||||||
|
} else {
|
||||||
|
e = NewLeft[int, string](i)
|
||||||
|
}
|
||||||
|
return MapLeft[int, string, int](Iden[int])(e) == e
|
||||||
|
}
|
||||||
|
if err := quick.Check(f, nil); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPropMapRightIdentity(t *testing.T) {
|
||||||
|
f := func(i int, s string, isRight bool) bool {
|
||||||
|
var e Either[int, string]
|
||||||
|
if isRight {
|
||||||
|
e = NewRight[int, string](s)
|
||||||
|
} else {
|
||||||
|
e = NewLeft[int, string](i)
|
||||||
|
}
|
||||||
|
return MapRight[int, string, string](Iden[string])(e) == e
|
||||||
|
}
|
||||||
|
if err := quick.Check(f, nil); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPropToOptionIdentities(t *testing.T) {
|
||||||
|
f := func(i int, s string, isRight bool) bool {
|
||||||
|
var e Either[int, string]
|
||||||
|
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]()
|
||||||
|
|
||||||
|
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]()
|
||||||
|
|
||||||
|
return l2O && o2L && r2O
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := quick.Check(f, nil); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPropUnwrapIdentities(t *testing.T) {
|
||||||
|
f := func(i int, s string, isRight bool) bool {
|
||||||
|
var e Either[int, string]
|
||||||
|
if isRight {
|
||||||
|
e = NewRight[int, string](s)
|
||||||
|
return e.UnwrapRightOr("") == s &&
|
||||||
|
e.UnwrapLeftOr(0) == 0
|
||||||
|
} else {
|
||||||
|
e = NewLeft[int, string](i)
|
||||||
|
return e.UnwrapLeftOr(0) == i &&
|
||||||
|
e.UnwrapRightOr("") == ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := quick.Check(f, nil); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
20
fn/option.go
20
fn/option.go
@ -218,30 +218,18 @@ func (o Option[A]) UnsafeFromSome() A {
|
|||||||
// providing the Right value that should be used if the Option value is None.
|
// 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 OptionToLeft[O, L, R any](o Option[O], r R) Either[O, R] {
|
||||||
if o.IsSome() {
|
if o.IsSome() {
|
||||||
return Either[O, R]{
|
return NewLeft[O, R](o.some)
|
||||||
left: o,
|
|
||||||
right: None[R](),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Either[O, R]{
|
return NewRight[O, R](r)
|
||||||
left: None[O](),
|
|
||||||
right: Some(r),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// OptionToRight can be used to convert an Option value into an Either, by
|
// OptionToRight 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.
|
// 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 OptionToRight[O, L, R any](o Option[O], l L) Either[L, O] {
|
||||||
if o.IsSome() {
|
if o.IsSome() {
|
||||||
return Either[L, O]{
|
return NewRight[L, O](o.some)
|
||||||
left: None[L](),
|
|
||||||
right: o,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Either[L, O]{
|
return NewLeft[L, O](l)
|
||||||
left: Some(l),
|
|
||||||
right: None[O](),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
60
fn/result.go
60
fn/result.go
@ -39,12 +39,17 @@ func Errf[T any](errString string, args ...any) Result[T] {
|
|||||||
// Unpack extracts the value or error from the Result.
|
// Unpack extracts the value or error from the Result.
|
||||||
func (r Result[T]) Unpack() (T, error) {
|
func (r Result[T]) Unpack() (T, error) {
|
||||||
var zero T
|
var zero T
|
||||||
return r.left.UnwrapOr(zero), r.right.UnwrapOr(nil)
|
|
||||||
|
if r.IsErr() {
|
||||||
|
return zero, r.right
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.left, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Err exposes the underlying error of the result type as a normal error type.
|
// Err exposes the underlying error of the result type as a normal error type.
|
||||||
func (r Result[T]) Err() error {
|
func (r Result[T]) Err() error {
|
||||||
return r.right.some
|
return r.right
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsOk returns true if the Result is a success value.
|
// IsOk returns true if the Result is a success value.
|
||||||
@ -59,66 +64,73 @@ func (r Result[T]) IsErr() bool {
|
|||||||
|
|
||||||
// Map applies a function to the success value if it exists.
|
// Map applies a function to the success value if it exists.
|
||||||
func (r Result[T]) Map(f func(T) T) Result[T] {
|
func (r Result[T]) Map(f func(T) T) Result[T] {
|
||||||
if r.IsOk() {
|
return Result[T]{
|
||||||
return Ok(f(r.left.some))
|
MapLeft[T, error](f)(r.Either),
|
||||||
}
|
}
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MapErr applies a function to the error value if it exists.
|
// MapErr applies a function to the error value if it exists.
|
||||||
func (r Result[T]) MapErr(f func(error) error) Result[T] {
|
func (r Result[T]) MapErr(f func(error) error) Result[T] {
|
||||||
if r.IsErr() {
|
return Result[T]{
|
||||||
return Err[T](f(r.right.some))
|
MapRight[T](f)(r.Either),
|
||||||
}
|
}
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Option returns the success value as an Option.
|
// Option returns the success value as an Option.
|
||||||
func (r Result[T]) Option() Option[T] {
|
func (r Result[T]) Option() Option[T] {
|
||||||
return r.left
|
return r.Either.LeftToOption()
|
||||||
}
|
}
|
||||||
|
|
||||||
// WhenResult executes the given function if the Result is a success.
|
// WhenResult executes the given function if the Result is a success.
|
||||||
func (r Result[T]) WhenResult(f func(T)) {
|
func (r Result[T]) WhenResult(f func(T)) {
|
||||||
r.left.WhenSome(func(t T) {
|
r.WhenLeft(f)
|
||||||
f(t)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WhenErr executes the given function if the Result is an error.
|
// WhenErr executes the given function if the Result is an error.
|
||||||
func (r Result[T]) WhenErr(f func(error)) {
|
func (r Result[T]) WhenErr(f func(error)) {
|
||||||
r.right.WhenSome(func(e error) {
|
r.WhenRight(f)
|
||||||
f(e)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnwrapOr returns the success value or a default value if it's an error.
|
// UnwrapOr returns the success value or a default value if it's an error.
|
||||||
func (r Result[T]) UnwrapOr(defaultValue T) T {
|
func (r Result[T]) UnwrapOr(defaultValue T) T {
|
||||||
return r.left.UnwrapOr(defaultValue)
|
if r.IsErr() {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.left
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnwrapOrElse returns the success value or computes a value from a function
|
// UnwrapOrElse returns the success value or computes a value from a function
|
||||||
// if it's an error.
|
// if it's an error.
|
||||||
func (r Result[T]) UnwrapOrElse(f func() T) T {
|
func (r Result[T]) UnwrapOrElse(f func() T) T {
|
||||||
return r.left.UnwrapOrFunc(f)
|
if r.IsErr() {
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.left
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnwrapOrFail returns the success value or fails the test if it's an error.
|
// UnwrapOrFail returns the success value or fails the test if it's an error.
|
||||||
func (r Result[T]) UnwrapOrFail(t *testing.T) T {
|
func (r Result[T]) UnwrapOrFail(t *testing.T) T {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
return r.left.UnwrapOrFail(t)
|
if r.IsErr() {
|
||||||
|
t.Fatalf("Result[%T] contained error: %v", r.left, r.right)
|
||||||
|
}
|
||||||
|
|
||||||
|
var zero T
|
||||||
|
|
||||||
|
return zero
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlatMap applies a function that returns a Result to the success value if it
|
// FlatMap applies a function that returns a Result to the success value if it
|
||||||
// exists.
|
// exists.
|
||||||
func (r Result[T]) FlatMap(f func(T) Result[T]) Result[T] {
|
func (r Result[T]) FlatMap(f func(T) Result[T]) Result[T] {
|
||||||
if r.IsOk() {
|
if r.IsOk() {
|
||||||
return f(r.left.some)
|
return r
|
||||||
}
|
}
|
||||||
return r
|
|
||||||
|
return f(r.left)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AndThen is an alias for FlatMap. This along with OrElse can be used to
|
// AndThen is an alias for FlatMap. This along with OrElse can be used to
|
||||||
@ -143,10 +155,10 @@ func (r Result[T]) OrElse(f func() Result[T]) Result[T] {
|
|||||||
// it exists.
|
// it exists.
|
||||||
func FlatMap[A, B any](r Result[A], f func(A) Result[B]) Result[B] {
|
func FlatMap[A, B any](r Result[A], f func(A) Result[B]) Result[B] {
|
||||||
if r.IsOk() {
|
if r.IsOk() {
|
||||||
return f(r.left.some)
|
return f(r.left)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Err[B](r.right.some)
|
return Err[B](r.right)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AndThen is an alias for FlatMap. This along with OrElse can be used to
|
// AndThen is an alias for FlatMap. This along with OrElse can be used to
|
||||||
|
Loading…
x
Reference in New Issue
Block a user