1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-08-10 22:31:32 +02:00

initial checkin

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
This commit is contained in:
Dr. Carsten Leue
2023-07-07 22:31:06 +02:00
parent 71c47ca560
commit c07df5c771
128 changed files with 5827 additions and 2 deletions

14
either/apply.go Normal file
View File

@@ -0,0 +1,14 @@
package either
import (
M "github.com/ibm/fp-go/monoid"
S "github.com/ibm/fp-go/semigroup"
)
func ApplySemigroup[E, A any](s S.Semigroup[A]) S.Semigroup[Either[E, A]] {
return S.ApplySemigroup(MonadMap[E, A, func(A) A], MonadAp[E, A, A], s)
}
func ApplicativeMonoid[E, A any](m M.Monoid[A]) M.Monoid[Either[E, A]] {
return M.ApplicativeMonoid(Of[E, A], MonadMap[E, A, func(A) A], MonadAp[E, A, A], m)
}

48
either/apply_test.go Normal file
View File

@@ -0,0 +1,48 @@
package either
import (
"testing"
M "github.com/ibm/fp-go/monoid/testing"
N "github.com/ibm/fp-go/number"
"github.com/stretchr/testify/assert"
)
func TestApplySemigroup(t *testing.T) {
sg := ApplySemigroup[string](N.SemigroupSum[int]())
la := Left[string, int]("a")
lb := Left[string, int]("b")
r1 := Right[string](1)
r2 := Right[string](2)
r3 := Right[string](3)
assert.Equal(t, la, sg.Concat(la, lb))
assert.Equal(t, lb, sg.Concat(r1, lb))
assert.Equal(t, la, sg.Concat(la, r2))
assert.Equal(t, lb, sg.Concat(r1, lb))
assert.Equal(t, r3, sg.Concat(r1, r2))
}
func TestApplicativeMonoid(t *testing.T) {
m := ApplicativeMonoid[string](N.MonoidSum[int]())
la := Left[string, int]("a")
lb := Left[string, int]("b")
r1 := Right[string](1)
r2 := Right[string](2)
r3 := Right[string](3)
assert.Equal(t, la, m.Concat(la, m.Empty()))
assert.Equal(t, lb, m.Concat(m.Empty(), lb))
assert.Equal(t, r1, m.Concat(r1, m.Empty()))
assert.Equal(t, r2, m.Concat(m.Empty(), r2))
assert.Equal(t, r3, m.Concat(r1, r2))
}
func TestApplicativeMonoidLaws(t *testing.T) {
m := ApplicativeMonoid[string](N.MonoidSum[int]())
M.AssertLaws(t, m)([]Either[string, int]{Left[string, int]("a"), Right[string](1)})
}

61
either/curry.go Normal file
View File

@@ -0,0 +1,61 @@
package either
import (
F "github.com/ibm/fp-go/function"
)
// these function curry a golang function that returns an error into its curried version that returns an either
func Curry0[R any](f func() (R, error)) func() Either[error, R] {
return Eitherize0(f)
}
func Curry1[T1, R any](f func(T1) (R, error)) func(T1) Either[error, R] {
return Eitherize1(f)
}
func Curry2[T1, T2, R any](f func(T1, T2) (R, error)) func(T1) func(T2) Either[error, R] {
return F.Curry2(Eitherize2(f))
}
func Curry3[T1, T2, T3, R any](f func(T1, T2, T3) (R, error)) func(T1) func(T2) func(T3) Either[error, R] {
return F.Curry3(Eitherize3(f))
}
func Curry4[T1, T2, T3, T4, R any](f func(T1, T2, T3, T4) (R, error)) func(T1) func(T2) func(T3) func(T4) Either[error, R] {
return F.Curry4(Eitherize4(f))
}
func Uncurry0[R any](f func() Either[error, R]) func() (R, error) {
return func() (R, error) {
return UnwrapError(f())
}
}
func Uncurry1[T1, R any](f func(T1) Either[error, R]) func(T1) (R, error) {
uc := F.Uncurry1(f)
return func(t1 T1) (R, error) {
return UnwrapError(uc(t1))
}
}
func Uncurry2[T1, T2, R any](f func(T1) func(T2) Either[error, R]) func(T1, T2) (R, error) {
uc := F.Uncurry2(f)
return func(t1 T1, t2 T2) (R, error) {
return UnwrapError(uc(t1, t2))
}
}
func Uncurry3[T1, T2, T3, R any](f func(T1) func(T2) func(T3) Either[error, R]) func(T1, T2, T3) (R, error) {
uc := F.Uncurry3(f)
return func(t1 T1, t2 T2, t3 T3) (R, error) {
return UnwrapError(uc(t1, t2, t3))
}
}
func Uncurry4[T1, T2, T3, T4, R any](f func(T1) func(T2) func(T3) func(T4) Either[error, R]) func(T1, T2, T3, T4) (R, error) {
uc := F.Uncurry4(f)
return func(t1 T1, t2 T2, t3 T3, t4 T4) (R, error) {
return UnwrapError(uc(t1, t2, t3, t4))
}
}

344
either/either.go Normal file
View File

@@ -0,0 +1,344 @@
// package either implements the Either monad
//
// A data type that can be of either of two types but not both. This is
// typically used to carry an error or a return value
package either
import (
E "github.com/ibm/fp-go/errors"
F "github.com/ibm/fp-go/function"
O "github.com/ibm/fp-go/option"
)
func Of[E, A any](value A) Either[E, A] {
return F.Pipe1(value, Right[E, A])
}
func FromIO[E, A any](f func() A) Either[E, A] {
return F.Pipe1(f(), Right[E, A])
}
func MonadAp[E, A, B any](fab Either[E, func(a A) B], fa Either[E, A]) Either[E, B] {
return fold(fab, Left[E, B], func(ab func(A) B) Either[E, B] {
return fold(fa, Left[E, B], F.Flow2(ab, Right[E, B]))
})
}
func Ap[E, A, B any](fa Either[E, A]) func(fab Either[E, func(a A) B]) Either[E, B] {
return F.Bind2nd(MonadAp[E, A, B], fa)
}
func MonadMap[E, A, B any](fa Either[E, A], f func(a A) B) Either[E, B] {
return MonadChain(fa, F.Flow2(f, Right[E, B]))
}
func MonadBiMap[E1, E2, A, B any](fa Either[E1, A], f func(E1) E2, g func(a A) B) Either[E2, B] {
return fold(fa, F.Flow2(f, Left[E2, B]), F.Flow2(g, Right[E2, B]))
}
// BiMap maps a pair of functions over the two type arguments of the bifunctor.
func BiMap[E1, E2, A, B any](f func(E1) E2, g func(a A) B) func(Either[E1, A]) Either[E2, B] {
return Fold(F.Flow2(f, Left[E2, B]), F.Flow2(g, Right[E2, B]))
}
func MonadMapTo[E, A, B any](fa Either[E, A], b B) Either[E, B] {
return MonadMap(fa, F.Constant1[A](b))
}
func MapTo[E, A, B any](b B) func(Either[E, A]) Either[E, B] {
return F.Bind2nd(MonadMapTo[E, A, B], b)
}
func MonadMapLeft[E, A, B any](fa Either[E, A], f func(E) B) Either[B, A] {
return fold(fa, F.Flow2(f, Left[B, A]), Right[B, A])
}
func Map[E, A, B any](f func(a A) B) func(fa Either[E, A]) Either[E, B] {
return Chain(F.Flow2(f, Right[E, B]))
}
func MapLeft[E, A, B any](f func(E) B) func(fa Either[E, A]) Either[B, A] {
return F.Bind2nd(MonadMapLeft[E, A, B], f)
}
func MonadChain[E, A, B any](fa Either[E, A], f func(a A) Either[E, B]) Either[E, B] {
return fold(fa, Left[E, B], f)
}
func MonadChainFirst[E, A, B any](ma Either[E, A], f func(a A) Either[E, B]) Either[E, A] {
return MonadChain(ma, func(a A) Either[E, A] {
return MonadMap(f(a), F.Constant1[B](a))
})
}
func MonadChainTo[E, A, B any](ma Either[E, A], mb Either[E, B]) Either[E, B] {
return mb
}
func MonadChainOptionK[E, A, B any](onNone func() E, ma Either[E, A], f func(A) O.Option[B]) Either[E, B] {
return MonadChain(ma, F.Flow2(f, FromOption[E, B](onNone)))
}
func ChainOptionK[E, A, B any](onNone func() E) func(func(A) O.Option[B]) func(Either[E, A]) Either[E, B] {
from := FromOption[E, B](onNone)
return func(f func(A) O.Option[B]) func(Either[E, A]) Either[E, B] {
return Chain(F.Flow2(f, from))
}
}
func ChainTo[E, A, B any](mb Either[E, B]) func(Either[E, A]) Either[E, B] {
return F.Bind2nd(MonadChainTo[E, A, B], mb)
}
func Chain[E, A, B any](f func(a A) Either[E, B]) func(Either[E, A]) Either[E, B] {
return F.Bind2nd(MonadChain[E, A, B], f)
}
func ChainFirst[E, A, B any](f func(a A) Either[E, B]) func(Either[E, A]) Either[E, A] {
return F.Bind2nd(MonadChainFirst[E, A, B], f)
}
func Flatten[E, A any](mma Either[E, Either[E, A]]) Either[E, A] {
return MonadChain(mma, F.Identity[Either[E, A]])
}
func TryCatch[E, A any](f func() (A, error), onThrow func(error) E) Either[E, A] {
val, err := f()
if err != nil {
return F.Pipe2(err, onThrow, Left[E, A])
}
return F.Pipe1(val, Right[E, A])
}
func TryCatchErrorG[GA ~func() (A, error), A any](f GA) Either[error, A] {
return TryCatch(f, E.IdentityError)
}
func TryCatchError[A any](f func() (A, error)) Either[error, A] {
return TryCatchErrorG(f)
}
func Sequence2[E, T1, T2, R any](f func(T1, T2) Either[E, R]) func(Either[E, T1], Either[E, T2]) Either[E, R] {
return func(e1 Either[E, T1], e2 Either[E, T2]) Either[E, R] {
return MonadSequence2(e1, e2, f)
}
}
func Sequence3[E, T1, T2, T3, R any](f func(T1, T2, T3) Either[E, R]) func(Either[E, T1], Either[E, T2], Either[E, T3]) Either[E, R] {
return func(e1 Either[E, T1], e2 Either[E, T2], e3 Either[E, T3]) Either[E, R] {
return MonadSequence3(e1, e2, e3, f)
}
}
func FromOption[E, A any](onNone func() E) func(O.Option[A]) Either[E, A] {
return O.Fold(F.Nullary2(onNone, Left[E, A]), Right[E, A])
}
func ToOption[E, A any]() func(Either[E, A]) O.Option[A] {
return Fold(F.Ignore1[E](O.None[A]), O.Some[A])
}
func FromError[A any](f func(a A) error) func(A) Either[error, A] {
return func(a A) Either[error, A] {
return TryCatchError(func() (A, error) {
return a, f(a)
})
}
}
func ToError[A any](e Either[error, A]) error {
return fold(e, E.IdentityError, F.Constant1[A, error](nil))
}
func Eitherize0G[GA ~func() (R, error), GB ~func() Either[error, R], R any](f GA) GB {
return F.Bind1(TryCatchErrorG[GA, R], f)
}
func Eitherize0[R any](f func() (R, error)) func() Either[error, R] {
return Eitherize0G[func() (R, error), func() Either[error, R]](f)
}
func Uneitherize0G[GA ~func() Either[error, R], GB ~func() (R, error), R any](f GA) GB {
return func() (R, error) {
return UnwrapError(f())
}
}
func Uneitherize0[R any](f func() Either[error, R]) func() (R, error) {
return Uneitherize0G[func() Either[error, R], func() (R, error)](f)
}
func Eitherize1G[GA ~func(T1) (R, error), GB ~func(T1) Either[error, R], T1, R any](f GA) GB {
return func(t1 T1) Either[error, R] {
return TryCatchError(func() (R, error) {
return f(t1)
})
}
}
func Eitherize1[T1, R any](f func(T1) (R, error)) func(T1) Either[error, R] {
return Eitherize1G[func(T1) (R, error), func(T1) Either[error, R]](f)
}
func Uneitherize1G[GA ~func(T1) Either[error, R], GB ~func(T1) (R, error), T1, R any](f GA) GB {
return func(t1 T1) (R, error) {
return UnwrapError(f(t1))
}
}
func Uneitherize1[T1, R any](f func(T1) Either[error, R]) func(T1) (R, error) {
return Uneitherize1G[func(T1) Either[error, R], func(T1) (R, error)](f)
}
func Eitherize2G[GA ~func(t1 T1, t2 T2) (R, error), GB ~func(t1 T1, t2 T2) Either[error, R], T1, T2, R any](f GA) GB {
return func(t1 T1, t2 T2) Either[error, R] {
return TryCatchError(func() (R, error) {
return f(t1, t2)
})
}
}
func Eitherize2[T1, T2, R any](f func(t1 T1, t2 T2) (R, error)) func(t1 T1, t2 T2) Either[error, R] {
return Eitherize2G[func(t1 T1, t2 T2) (R, error), func(t1 T1, t2 T2) Either[error, R]](f)
}
func Uneitherize2G[GA ~func(T1, T2) Either[error, R], GB ~func(T1, T2) (R, error), T1, T2, R any](f GA) GB {
return func(t1 T1, t2 T2) (R, error) {
return UnwrapError(f(t1, t2))
}
}
func Uneitherize2[T1, T2, R any](f func(T1, T2) Either[error, R]) func(T1, T2) (R, error) {
return Uneitherize2G[func(T1, T2) Either[error, R], func(T1, T2) (R, error)](f)
}
func Eitherize3G[GA ~func(t1 T1, t2 T2, t3 T3) (R, error), GB ~func(t1 T1, t2 T2, t3 T3) Either[error, R], T1, T2, T3, R any](f GA) GB {
return func(t1 T1, t2 T2, t3 T3) Either[error, R] {
return TryCatchError(func() (R, error) {
return f(t1, t2, t3)
})
}
}
func Eitherize3[T1, T2, T3, R any](f func(t1 T1, t2 T2, t3 T3) (R, error)) func(t1 T1, t2 T2, t3 T3) Either[error, R] {
return Eitherize3G[func(t1 T1, t2 T2, t3 T3) (R, error), func(t1 T1, t2 T2, t3 T3) Either[error, R]](f)
}
func Uneitherize3G[GA ~func(T1, T2, T3) Either[error, R], GB ~func(T1, T2, T3) (R, error), T1, T2, T3, R any](f GA) GB {
return func(t1 T1, t2 T2, t3 T3) (R, error) {
return UnwrapError(f(t1, t2, t3))
}
}
func Uneitherize3[T1, T2, T3, R any](f func(T1, T2, T3) Either[error, R]) func(T1, T2, T3) (R, error) {
return Uneitherize3G[func(T1, T2, T3) Either[error, R], func(T1, T2, T3) (R, error)](f)
}
func Eitherize4G[GA ~func(t1 T1, t2 T2, t3 T3, t4 T4) (R, error), GB ~func(t1 T1, t2 T2, t3 T3, t4 T4) Either[error, R], T1, T2, T3, T4, R any](f GA) GB {
return func(t1 T1, t2 T2, t3 T3, t4 T4) Either[error, R] {
return TryCatchError(func() (R, error) {
return f(t1, t2, t3, t4)
})
}
}
func Eitherize4[T1, T2, T3, T4, R any](f func(t1 T1, t2 T2, t3 T3, t4 T4) (R, error)) func(t1 T1, t2 T2, t3 T3, t4 T4) Either[error, R] {
return Eitherize4G[func(t1 T1, t2 T2, t3 T3, t4 T4) (R, error), func(t1 T1, t2 T2, t3 T3, t4 T4) Either[error, R]](f)
}
func Uneitherize4G[GA ~func(T1, T2, T3, T4) Either[error, R], GB ~func(T1, T2, T3, T4) (R, error), T1, T2, T3, T4, R any](f GA) GB {
return func(t1 T1, t2 T2, t3 T3, t4 T4) (R, error) {
return UnwrapError(f(t1, t2, t3, t4))
}
}
func Uneitherize4[T1, T2, T3, T4, R any](f func(T1, T2, T3, T4) Either[error, R]) func(T1, T2, T3, T4) (R, error) {
return Uneitherize4G[func(T1, T2, T3, T4) Either[error, R], func(T1, T2, T3, T4) (R, error)](f)
}
func MonadFold[E, A, B any](ma Either[E, A], onLeft func(e E) B, onRight func(a A) B) B {
return fold(ma, onLeft, onRight)
}
func Fold[E, A, B any](onLeft func(E) B, onRight func(A) B) func(Either[E, A]) B {
return func(ma Either[E, A]) B {
return fold(ma, onLeft, onRight)
}
}
func UnwrapError[A any](ma Either[error, A]) (A, error) {
return Unwrap[error](ma)
}
func FromPredicate[E, A any](pred func(A) bool, onFalse func(A) E) func(A) Either[E, A] {
return func(a A) Either[E, A] {
if pred(a) {
return Right[E](a)
}
return Left[E, A](onFalse(a))
}
}
func FromNillable[E, A any](e E) func(*A) Either[E, *A] {
return FromPredicate(F.IsNonNil[A], F.Constant1[*A](e))
}
func GetOrElse[E, A any](onLeft func(E) A) func(Either[E, A]) A {
return Fold(onLeft, F.Identity[A])
}
func Reduce[E, A, B any](f func(B, A) B, initial B) func(Either[E, A]) B {
return Fold(
F.Constant1[E](initial),
F.Bind1st(f, initial),
)
}
func AltW[E, E1, A any](that func() Either[E1, A]) func(Either[E, A]) Either[E1, A] {
return Fold(F.Ignore1[E](that), Right[E1, A])
}
func Alt[E, A any](that func() Either[E, A]) func(Either[E, A]) Either[E, A] {
return AltW[E](that)
}
func OrElse[E, A any](onLeft func(e E) Either[E, A]) func(Either[E, A]) Either[E, A] {
return Fold(onLeft, Of[E, A])
}
func ToType[E, A any](onError func(any) E) func(any) Either[E, A] {
return func(value any) Either[E, A] {
return F.Pipe2(
value,
O.ToType[A],
O.Fold(F.Nullary3(F.Constant(value), onError, Left[E, A]), Right[E, A]),
)
}
}
func Memoize[E, A any](val Either[E, A]) Either[E, A] {
return val
}
func MonadSequence2[E, T1, T2, R any](e1 Either[E, T1], e2 Either[E, T2], f func(T1, T2) Either[E, R]) Either[E, R] {
return fold(e1, Left[E, R], func(t1 T1) Either[E, R] {
return fold(e2, Left[E, R], func(t2 T2) Either[E, R] {
return f(t1, t2)
})
})
}
func MonadSequence3[E, T1, T2, T3, R any](e1 Either[E, T1], e2 Either[E, T2], e3 Either[E, T3], f func(T1, T2, T3) Either[E, R]) Either[E, R] {
return fold(e1, Left[E, R], func(t1 T1) Either[E, R] {
return fold(e2, Left[E, R], func(t2 T2) Either[E, R] {
return fold(e3, Left[E, R], func(t3 T3) Either[E, R] {
return f(t1, t2, t3)
})
})
})
}
// Swap changes the order of type parameters
func Swap[E, A any](val Either[E, A]) Either[A, E] {
return fold(val, Right[A, E], Left[A, E])
}

96
either/either_test.go Normal file
View File

@@ -0,0 +1,96 @@
package either
import (
"errors"
"testing"
F "github.com/ibm/fp-go/function"
"github.com/ibm/fp-go/internal/utils"
O "github.com/ibm/fp-go/option"
S "github.com/ibm/fp-go/string"
"github.com/stretchr/testify/assert"
)
func TestIsLeft(t *testing.T) {
err := errors.New("Some error")
withError := Left[error, string](err)
assert.True(t, IsLeft(withError))
assert.False(t, IsRight(withError))
}
func TestIsRight(t *testing.T) {
noError := Right[error]("Carsten")
assert.True(t, IsRight(noError))
assert.False(t, IsLeft(noError))
}
func TestMapEither(t *testing.T) {
assert.Equal(t, F.Pipe1(Right[error]("abc"), Map[error](utils.StringLen)), Right[error](3))
val2 := F.Pipe1(Left[string, string]("s"), Map[string](utils.StringLen))
exp2 := Left[string, int]("s")
assert.Equal(t, val2, exp2)
}
func TestUnwrapError(t *testing.T) {
a := ""
err := errors.New("Some error")
withError := Left[error, string](err)
res, extracted := UnwrapError(withError)
assert.Equal(t, a, res)
assert.Equal(t, extracted, err)
}
func TestReduce(t *testing.T) {
s := S.Semigroup()
assert.Equal(t, "foobar", F.Pipe1(Right[string]("bar"), Reduce[string](s.Concat, "foo")))
assert.Equal(t, "foo", F.Pipe1(Left[string, string]("bar"), Reduce[string](s.Concat, "foo")))
}
func TestAp(t *testing.T) {
f := S.Size
assert.Equal(t, Right[string](3), F.Pipe1(Right[string](f), Ap[string, string, int](Right[string]("abc"))))
assert.Equal(t, Left[string, int]("maError"), F.Pipe1(Right[string](f), Ap[string, string, int](Left[string, string]("maError"))))
assert.Equal(t, Left[string, int]("mabError"), F.Pipe1(Left[string, func(string) int]("mabError"), Ap[string, string, int](Left[string, string]("maError"))))
}
func TestAlt(t *testing.T) {
assert.Equal(t, Right[string](1), F.Pipe1(Right[string](1), Alt(F.Constant(Right[string](2)))))
assert.Equal(t, Right[string](1), F.Pipe1(Right[string](1), Alt(F.Constant(Left[string, int]("a")))))
assert.Equal(t, Right[string](2), F.Pipe1(Left[string, int]("b"), Alt(F.Constant(Right[string](2)))))
assert.Equal(t, Left[string, int]("b"), F.Pipe1(Left[string, int]("a"), Alt(F.Constant(Left[string, int]("b")))))
}
func TestChainFirst(t *testing.T) {
f := F.Flow2(S.Size, Right[string, int])
assert.Equal(t, Right[string]("abc"), F.Pipe1(Right[string]("abc"), ChainFirst(f)))
assert.Equal(t, Left[string, string]("maError"), F.Pipe1(Left[string, string]("maError"), ChainFirst(f)))
}
func TestChainOptionK(t *testing.T) {
f := ChainOptionK[string, int, int](F.Constant("a"))(func(n int) O.Option[int] {
if n > 0 {
return O.Some(n)
}
return O.None[int]()
})
assert.Equal(t, Right[string](1), f(Right[string](1)))
assert.Equal(t, Left[string, int]("a"), f(Right[string](-1)))
assert.Equal(t, Left[string, int]("b"), f(Left[string, int]("b")))
}
func TestFromOption(t *testing.T) {
assert.Equal(t, Left[string, int]("none"), FromOption[string, int](F.Constant("none"))(O.None[int]()))
assert.Equal(t, Right[string](1), FromOption[string, int](F.Constant("none"))(O.Some(1)))
}

25
either/eq.go Normal file
View File

@@ -0,0 +1,25 @@
package either
import (
EQ "github.com/ibm/fp-go/eq"
F "github.com/ibm/fp-go/function"
)
// Constructs an equal predicate for an `Either`
func Eq[E, A any](e EQ.Eq[E], a EQ.Eq[A]) EQ.Eq[Either[E, A]] {
// some convenient shortcuts
eqa := F.Curry2(a.Equals)
eqe := F.Curry2(e.Equals)
fca := F.Bind2nd(Fold[E, A, bool], F.Constant1[A](false))
fce := F.Bind1st(Fold[E, A, bool], F.Constant1[E](false))
fld := Fold(F.Flow2(eqe, fca), F.Flow2(eqa, fce))
return EQ.FromEquals(F.Uncurry2(fld))
}
// FromStrictEquals constructs an `Eq` from the canonical comparison function
func FromStrictEquals[E, A comparable]() EQ.Eq[Either[E, A]] {
return Eq(EQ.FromStrictEquals[E](), EQ.FromStrictEquals[A]())
}

30
either/eq_test.go Normal file
View File

@@ -0,0 +1,30 @@
package either
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestEq(t *testing.T) {
r1 := Of[string](1)
r2 := Of[string](1)
r3 := Of[string](2)
e1 := Left[string, int]("a")
e2 := Left[string, int]("a")
e3 := Left[string, int]("b")
eq := FromStrictEquals[string, int]()
assert.True(t, eq.Equals(r1, r1))
assert.True(t, eq.Equals(r1, r2))
assert.False(t, eq.Equals(r1, r3))
assert.False(t, eq.Equals(r1, e1))
assert.True(t, eq.Equals(e1, e1))
assert.True(t, eq.Equals(e1, e2))
assert.False(t, eq.Equals(e1, e3))
assert.False(t, eq.Equals(e2, r2))
}

23
either/exec/exec.go Normal file
View File

@@ -0,0 +1,23 @@
package Exec
import (
"context"
E "github.com/ibm/fp-go/either"
"github.com/ibm/fp-go/exec"
F "github.com/ibm/fp-go/function"
GE "github.com/ibm/fp-go/internal/exec"
)
var (
// Command executes a command
// use this version if the command does not produce any side effect, i.e. if the output is uniquely determined by by the input
// typically you'd rather use the IOEither version of the command
Command = F.Curry3(command)
)
func command(name string, args []string, in []byte) E.Either[error, exec.CommandOutput] {
return E.TryCatchError(func() (exec.CommandOutput, error) {
return GE.Exec(context.Background(), name, args, in)
})
}

36
either/http/request.go Normal file
View File

@@ -0,0 +1,36 @@
package Http
import (
"bytes"
"net/http"
E "github.com/ibm/fp-go/either"
)
var (
PostRequest = bodyRequest("POST")
PutRequest = bodyRequest("PUT")
GetRequest = noBodyRequest("GET")
DeleteRequest = noBodyRequest("DELETE")
OptionsRequest = noBodyRequest("OPTIONS")
HeadRequest = noBodyRequest("HEAD")
)
func bodyRequest(method string) func(string) func([]byte) E.Either[error, *http.Request] {
return func(url string) func([]byte) E.Either[error, *http.Request] {
return func(body []byte) E.Either[error, *http.Request] {
return E.TryCatchError(func() (*http.Request, error) {
return http.NewRequest(method, url, bytes.NewReader(body))
})
}
}
}
func noBodyRequest(method string) func(string) E.Either[error, *http.Request] {
return func(url string) E.Either[error, *http.Request] {
return E.TryCatchError(func() (*http.Request, error) {
return http.NewRequest(method, url, nil)
})
}
}

55
either/legacy.go Normal file
View File

@@ -0,0 +1,55 @@
package either
import "fmt"
type EitherTag int
const (
LeftTag EitherTag = iota
RightTag
)
// Either defines a data structure that logically holds either an E or an A. The tag discriminates the cases
type Either[E, A any] struct {
Tag EitherTag
Left E
Right A
}
// String prints some debug info for the object
func (s Either[E, A]) String() string {
switch s.Tag {
case LeftTag:
return fmt.Sprintf("Left[%T, %T](%v)", s.Left, s.Right, s.Left)
case RightTag:
return fmt.Sprintf("Right[%T, %T](%v)", s.Left, s.Right, s.Right)
}
return "Invalid"
}
func IsLeft[E, A any](val Either[E, A]) bool {
return val.Tag == LeftTag
}
func IsRight[E, A any](val Either[E, A]) bool {
return val.Tag == RightTag
}
func Left[E, A any](value E) Either[E, A] {
return Either[E, A]{Tag: LeftTag, Left: value}
}
func Right[E, A any](value A) Either[E, A] {
return Either[E, A]{Tag: RightTag, Right: value}
}
func fold[E, A, B any](ma Either[E, A], onLeft func(e E) B, onRight func(a A) B) B {
if IsLeft(ma) {
return onLeft(ma.Left)
}
return onRight(ma.Right)
}
func Unwrap[E, A any](ma Either[E, A]) (A, E) {
return ma.Right, ma.Left
}

33
either/logger.go Normal file
View File

@@ -0,0 +1,33 @@
package either
import (
"log"
F "github.com/ibm/fp-go/function"
L "github.com/ibm/fp-go/logging"
)
func _log[E, A any](left func(string, ...any), right func(string, ...any), prefix string) func(Either[E, A]) Either[E, A] {
return Fold(
func(e E) Either[E, A] {
left("%s: %v", prefix, e)
return Left[E, A](e)
},
func(a A) Either[E, A] {
right("%s: %v", prefix, a)
return Right[E](a)
})
}
func Logger[E, A any](loggers ...*log.Logger) func(string) func(Either[E, A]) Either[E, A] {
left, right := L.LoggingCallbacks(loggers...)
return func(prefix string) func(Either[E, A]) Either[E, A] {
delegate := _log[E, A](left, right, prefix)
return func(ma Either[E, A]) Either[E, A] {
return F.Pipe1(
delegate(ma),
ChainTo[E, A](ma),
)
}
}
}

22
either/logger_test.go Normal file
View File

@@ -0,0 +1,22 @@
package either
import (
"testing"
F "github.com/ibm/fp-go/function"
"github.com/stretchr/testify/assert"
)
func TestLogger(t *testing.T) {
l := Logger[error, string]()
r := Right[error]("test")
res := F.Pipe1(
r,
l("out"),
)
assert.Equal(t, r, res)
}

69
either/modern._go Normal file
View File

@@ -0,0 +1,69 @@
//go:build disabled
package either
// Either will either be E or A
type Either[E, A any] interface {
fmt.Stringer
}
type left[E any] struct {
e E
}
func (left[E]) IsLeft() bool {
return true
}
type right[A any] struct {
a A
}
func (right[A]) IsLeft() bool {
return false
}
func (l left[E]) String() string {
return fmt.Sprintf("Left[%v]", l.e)
}
func (r right[A]) String() string {
return fmt.Sprintf("Right[%v]", r.a)
}
func IsLeft[E, A any](val Either[E, A]) bool {
switch any(val).(type) {
case left[E]:
return true
default:
return false
}
}
func IsRight[E, A any](val Either[E, A]) bool {
return !IsLeft(val)
}
func Left[E, A any](value E) Either[E, A] {
return left[E]{e: value}
}
func Right[E, A any](value A) Either[E, A] {
return right[A]{a: value}
}
func fold[E, A, B any](ma Either[E, A], onLeft func(e E) B, onRight func(a A) B) B {
if IsLeft(ma) {
return onLeft(ma.(left[E]).e)
}
return onRight(ma.(right[A]).a)
}
func Unwrap[E, A any](ma Either[E, A]) (A, E) {
if IsLeft(ma) {
var a A
return a, ma.(left[E]).e
}
var E e
return ma.(right[A]).a, e
}

30
either/record.go Normal file
View File

@@ -0,0 +1,30 @@
package either
import (
F "github.com/ibm/fp-go/function"
RR "github.com/ibm/fp-go/internal/record"
)
// TraverseRecord transforms a record of options into an option of a record
func TraverseRecordG[GA ~map[K]A, GB ~map[K]B, K comparable, E, A, B any](f func(A) Either[E, B]) func(GA) Either[E, GB] {
return RR.Traverse[GA](
Of[E, GB],
MonadMap[E, GB, func(B) GB],
MonadAp[E, B, GB],
f,
)
}
// TraverseRecord transforms a record of eithers into an either of a record
func TraverseRecord[K comparable, E, A, B any](f func(A) Either[E, B]) func(map[K]A) Either[E, map[K]B] {
return TraverseRecordG[map[K]A, map[K]B](f)
}
func SequenceRecordG[GA ~map[K]A, GOA ~map[K]Either[E, A], K comparable, E, A any](ma GOA) Either[E, GA] {
return TraverseRecordG[GOA, GA](F.Identity[Either[E, A]])(ma)
}
// SequenceRecord converts a homogeneous sequence of either into an either of sequence
func SequenceRecord[K comparable, E, A any](ma map[K]Either[E, A]) Either[E, map[K]A] {
return SequenceRecordG[map[K]A](ma)
}

29
either/resource.go Normal file
View File

@@ -0,0 +1,29 @@
package either
import (
F "github.com/ibm/fp-go/function"
)
// constructs a function that creates a resource, then operates on it and then releases the resource
func WithResource[E, R, A any](onCreate func() Either[E, R], onRelease func(R) Either[E, any]) func(func(R) Either[E, A]) Either[E, A] {
return func(f func(R) Either[E, A]) Either[E, A] {
return MonadChain(
onCreate(), func(r R) Either[E, A] {
// run the code and make sure to release as quickly as possible
res := f(r)
released := onRelease(r)
// handle the errors
return fold(
res,
Left[E, A],
func(a A) Either[E, A] {
return F.Pipe1(
released,
MapTo[E, any](a),
)
})
},
)
}
}

39
either/resource_test.go Normal file
View File

@@ -0,0 +1,39 @@
package either
import (
"os"
"testing"
F "github.com/ibm/fp-go/function"
"github.com/stretchr/testify/assert"
)
func TestWithResource(t *testing.T) {
onCreate := func() Either[error, *os.File] {
return TryCatchError(func() (*os.File, error) {
return os.CreateTemp("", "*")
})
}
onDelete := F.Flow2(
func(f *os.File) Either[error, string] {
return TryCatchError(func() (string, error) {
return f.Name(), f.Close()
})
},
Chain(func(name string) Either[error, any] {
return TryCatchError(func() (any, error) {
return name, os.Remove(name)
})
}),
)
onHandler := func(f *os.File) Either[error, string] {
return Of[error](f.Name())
}
tempFile := WithResource[error, *os.File, string](onCreate, onDelete)
resE := tempFile(onHandler)
assert.True(t, IsRight(resE))
}

45
either/sequence.go Normal file
View File

@@ -0,0 +1,45 @@
package either
import (
Apply "github.com/ibm/fp-go/apply"
T "github.com/ibm/fp-go/tuple"
)
// SequenceT converts n inputs of higher kinded types into a higher kinded types of n strongly typed values, represented as a tuple
func SequenceT1[E, A any](a Either[E, A]) Either[E, T.Tuple1[A]] {
return Apply.SequenceT1(
MonadMap[E, A, T.Tuple1[A]],
a,
)
}
func SequenceT2[E, A, B any](a Either[E, A], b Either[E, B]) Either[E, T.Tuple2[A, B]] {
return Apply.SequenceT2(
MonadMap[E, A, func(B) T.Tuple2[A, B]],
MonadAp[E, B, T.Tuple2[A, B]],
a, b,
)
}
func SequenceT3[E, A, B, C any](a Either[E, A], b Either[E, B], c Either[E, C]) Either[E, T.Tuple3[A, B, C]] {
return Apply.SequenceT3(
MonadMap[E, A, func(B) func(C) T.Tuple3[A, B, C]],
MonadAp[E, B, func(C) T.Tuple3[A, B, C]],
MonadAp[E, C, T.Tuple3[A, B, C]],
a, b, c,
)
}
func SequenceT4[E, A, B, C, D any](a Either[E, A], b Either[E, B], c Either[E, C], d Either[E, D]) Either[E, T.Tuple4[A, B, C, D]] {
return Apply.SequenceT4(
MonadMap[E, A, func(B) func(C) func(D) T.Tuple4[A, B, C, D]],
MonadAp[E, B, func(C) func(D) T.Tuple4[A, B, C, D]],
MonadAp[E, C, func(D) T.Tuple4[A, B, C, D]],
MonadAp[E, D, T.Tuple4[A, B, C, D]],
a, b, c, d,
)
}

60
either/testing/laws.go Normal file
View File

@@ -0,0 +1,60 @@
package testing
import (
"testing"
ET "github.com/ibm/fp-go/either"
EQ "github.com/ibm/fp-go/eq"
L "github.com/ibm/fp-go/internal/monad/testing"
)
// AssertLaws asserts the apply monad laws for the `Either` monad
func AssertLaws[E, A, B, C any](t *testing.T,
eqe EQ.Eq[E],
eqa EQ.Eq[A],
eqb EQ.Eq[B],
eqc EQ.Eq[C],
ab func(A) B,
bc func(B) C,
) func(a A) bool {
return L.AssertLaws(t,
ET.Eq(eqe, eqa),
ET.Eq(eqe, eqb),
ET.Eq(eqe, eqc),
ET.Of[E, A],
ET.Of[E, B],
ET.Of[E, C],
ET.Of[E, func(A) A],
ET.Of[E, func(A) B],
ET.Of[E, func(B) C],
ET.Of[E, func(func(A) B) B],
ET.MonadMap[E, A, A],
ET.MonadMap[E, A, B],
ET.MonadMap[E, A, C],
ET.MonadMap[E, B, C],
ET.MonadMap[E, func(B) C, func(func(A) B) func(A) C],
ET.MonadChain[E, A, A],
ET.MonadChain[E, A, B],
ET.MonadChain[E, A, C],
ET.MonadChain[E, B, C],
ET.MonadAp[E, A, A],
ET.MonadAp[E, A, B],
ET.MonadAp[E, B, C],
ET.MonadAp[E, A, C],
ET.MonadAp[E, func(A) B, B],
ET.MonadAp[E, func(A) B, func(A) C],
ab,
bc,
)
}

View File

@@ -0,0 +1,33 @@
package testing
import (
"fmt"
"testing"
EQ "github.com/ibm/fp-go/eq"
"github.com/stretchr/testify/assert"
)
func TestMonadLaws(t *testing.T) {
// some comparison
eqe := EQ.FromStrictEquals[string]()
eqa := EQ.FromStrictEquals[bool]()
eqb := EQ.FromStrictEquals[int]()
eqc := EQ.FromStrictEquals[string]()
ab := func(a bool) int {
if a {
return 1
}
return 0
}
bc := func(b int) string {
return fmt.Sprintf("value %d", b)
}
laws := AssertLaws(t, eqe, eqa, eqb, eqc, ab, bc)
assert.True(t, laws(true))
assert.True(t, laws(false))
}

54
either/traverse.go Normal file
View File

@@ -0,0 +1,54 @@
package either
import (
F "github.com/ibm/fp-go/function"
)
/*
*
We need to pass the members of the applicative explicitly, because golang does neither support higher kinded types nor template methods on structs or interfaces
HKTRB = HKT<Either[B]>
HKTA = HKT<A>
HKTB = HKT<B>
*/
func traverse[E, A, B, HKTA, HKTB, HKTRB any](
_of func(Either[E, B]) HKTRB,
_map func(HKTB, func(B) Either[E, B]) HKTRB,
) func(Either[E, A], func(A) HKTB) HKTRB {
left := F.Flow2(Left[E, B], _of)
right := F.Bind2nd(_map, Right[E, B])
return func(ta Either[E, A], f func(A) HKTB) HKTRB {
return fold(ta,
left,
F.Flow2(f, right),
)
}
}
func Traverse[E, A, B, HKTA, HKTB, HKTRB any](
_of func(Either[E, B]) HKTRB,
_map func(HKTB, func(B) Either[E, B]) HKTRB,
) func(func(A) HKTB) func(Either[E, A]) HKTRB {
delegate := traverse[E, A, B, HKTA](_of, _map)
return func(f func(A) HKTB) func(Either[E, A]) HKTRB {
return F.Bind2nd(delegate, f)
}
}
/*
*
We need to pass the members of the applicative explicitly, because golang does neither support higher kinded types nor template methods on structs or interfaces
HKTRA = HKT<Either[A]>
HKTA = HKT<A>
HKTB = HKT<B>
*/
func Sequence[E, A, HKTA, HKTRA any](
_of func(Either[E, A]) HKTRA,
_map func(HKTA, func(A) Either[E, A]) HKTRA,
) func(Either[E, HKTA]) HKTRA {
return Fold(F.Flow2(Left[E, A], _of), F.Bind2nd(_map, Right[E, A]))
}

38
either/traverse_test.go Normal file
View File

@@ -0,0 +1,38 @@
package either
import (
"testing"
F "github.com/ibm/fp-go/function"
O "github.com/ibm/fp-go/option"
"github.com/stretchr/testify/assert"
)
func TestTraverse(t *testing.T) {
f := func(n int) O.Option[int] {
if n >= 2 {
return O.Of(n)
}
return O.None[int]()
}
trav := Traverse[string, int, int, O.Option[Either[string, int]]](
O.Of[Either[string, int]],
O.MonadMap[int, Either[string, int]],
)(f)
assert.Equal(t, O.Of(Left[string, int]("a")), F.Pipe1(Left[string, int]("a"), trav))
assert.Equal(t, O.None[Either[string, int]](), F.Pipe1(Right[string](1), trav))
assert.Equal(t, O.Of(Right[string](3)), F.Pipe1(Right[string](3), trav))
}
func TestSequence(t *testing.T) {
seq := Sequence(
O.Of[Either[string, int]],
O.MonadMap[int, Either[string, int]],
)
assert.Equal(t, O.Of(Right[string](1)), seq(Right[string](O.Of(1))))
assert.Equal(t, O.Of(Left[string, int]("a")), seq(Left[string, O.Option[int]]("a")))
assert.Equal(t, O.None[Either[string, int]](), seq(Right[string](O.None[int]())))
}

81
either/variadic.go Normal file
View File

@@ -0,0 +1,81 @@
package either
func Variadic0[V, R any](f func([]V) (R, error)) func(...V) Either[error, R] {
return func(v ...V) Either[error, R] {
return TryCatchError(func() (R, error) {
return f(v)
})
}
}
func Variadic1[T1, V, R any](f func(T1, []V) (R, error)) func(T1, ...V) Either[error, R] {
return func(t1 T1, v ...V) Either[error, R] {
return TryCatchError(func() (R, error) {
return f(t1, v)
})
}
}
func Variadic2[T1, T2, V, R any](f func(T1, T2, []V) (R, error)) func(T1, T2, ...V) Either[error, R] {
return func(t1 T1, t2 T2, v ...V) Either[error, R] {
return TryCatchError(func() (R, error) {
return f(t1, t2, v)
})
}
}
func Variadic3[T1, T2, T3, V, R any](f func(T1, T2, T3, []V) (R, error)) func(T1, T2, T3, ...V) Either[error, R] {
return func(t1 T1, t2 T2, t3 T3, v ...V) Either[error, R] {
return TryCatchError(func() (R, error) {
return f(t1, t2, t3, v)
})
}
}
func Variadic4[T1, T2, T3, T4, V, R any](f func(T1, T2, T3, T4, []V) (R, error)) func(T1, T2, T3, T4, ...V) Either[error, R] {
return func(t1 T1, t2 T2, t3 T3, t4 T4, v ...V) Either[error, R] {
return TryCatchError(func() (R, error) {
return f(t1, t2, t3, t4, v)
})
}
}
func Unvariadic0[V, R any](f func(...V) (R, error)) func([]V) Either[error, R] {
return func(v []V) Either[error, R] {
return TryCatchError(func() (R, error) {
return f(v...)
})
}
}
func Unvariadic1[T1, V, R any](f func(T1, ...V) (R, error)) func(T1, []V) Either[error, R] {
return func(t1 T1, v []V) Either[error, R] {
return TryCatchError(func() (R, error) {
return f(t1, v...)
})
}
}
func Unvariadic2[T1, T2, V, R any](f func(T1, T2, ...V) (R, error)) func(T1, T2, []V) Either[error, R] {
return func(t1 T1, t2 T2, v []V) Either[error, R] {
return TryCatchError(func() (R, error) {
return f(t1, t2, v...)
})
}
}
func Unvariadic3[T1, T2, T3, V, R any](f func(T1, T2, T3, ...V) (R, error)) func(T1, T2, T3, []V) Either[error, R] {
return func(t1 T1, t2 T2, t3 T3, v []V) Either[error, R] {
return TryCatchError(func() (R, error) {
return f(t1, t2, t3, v...)
})
}
}
func Unvariadic4[T1, T2, T3, T4, V, R any](f func(T1, T2, T3, T4, ...V) (R, error)) func(T1, T2, T3, T4, []V) Either[error, R] {
return func(t1 T1, t2 T2, t3 T3, t4 T4, v []V) Either[error, R] {
return TryCatchError(func() (R, error) {
return f(t1, t2, t3, t4, v...)
})
}
}