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
option/apply.go Normal file
View File

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

31
option/array.go Normal file
View File

@@ -0,0 +1,31 @@
package option
import (
F "github.com/ibm/fp-go/function"
RA "github.com/ibm/fp-go/internal/array"
)
// TraverseArray transforms an array
func TraverseArrayG[GA ~[]A, GB ~[]B, A, B any](f func(A) Option[B]) func(GA) Option[GB] {
return RA.Traverse[GA](
Of[GB],
MonadMap[GB, func(B) GB],
MonadAp[B, GB],
f,
)
}
// TraverseArray transforms an array
func TraverseArray[A, B any](f func(A) Option[B]) func([]A) Option[[]B] {
return TraverseArrayG[[]A, []B](f)
}
func SequenceArrayG[GA ~[]A, GOA ~[]Option[A], A any](ma GOA) Option[GA] {
return TraverseArrayG[GOA, GA](F.Identity[Option[A]])(ma)
}
// SequenceArray converts a homogeneous sequence of either into an either of sequence
func SequenceArray[A any](ma []Option[A]) Option[[]A] {
return SequenceArrayG[[]A](ma)
}

21
option/array_test.go Normal file
View File

@@ -0,0 +1,21 @@
package option
import (
"testing"
F "github.com/ibm/fp-go/function"
"github.com/stretchr/testify/assert"
)
func TestSequenceArray(t *testing.T) {
one := Of(1)
two := Of(2)
res := F.Pipe1(
[]Option[int]{one, two},
SequenceArray[int],
)
assert.Equal(t, res, Of([]int{1, 2}))
}

22
option/eq.go Normal file
View File

@@ -0,0 +1,22 @@
package option
import (
EQ "github.com/ibm/fp-go/eq"
F "github.com/ibm/fp-go/function"
)
// Constructs an equal predicate for an `Option`
func Eq[A any](a EQ.Eq[A]) EQ.Eq[Option[A]] {
// some convenient shortcuts
fld := Fold(
F.Constant(Fold(F.ConstTrue, F.Constant1[A](false))),
F.Flow2(F.Curry2(a.Equals), F.Bind1st(Fold[A, bool], F.ConstFalse)),
)
// convert to an equals predicate
return EQ.FromEquals(F.Uncurry2(fld))
}
// FromStrictEquals constructs an `Eq` from the canonical comparison function
func FromStrictEquals[A comparable]() EQ.Eq[Option[A]] {
return Eq(EQ.FromStrictEquals[A]())
}

26
option/eq_test.go Normal file
View File

@@ -0,0 +1,26 @@
package option
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestEq(t *testing.T) {
r1 := Of(1)
r2 := Of(1)
r3 := Of(2)
n1 := None[int]()
eq := FromStrictEquals[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, n1))
assert.True(t, eq.Equals(n1, n1))
assert.False(t, eq.Equals(n1, r2))
}

58
option/legacy.go Normal file
View File

@@ -0,0 +1,58 @@
package option
import "fmt"
type OptionTag int
const (
NoneTag OptionTag = iota
SomeTag
)
// Option defines a data structure that logically holds a value or not
type Option[A any] struct {
Tag OptionTag
Value A
}
// String prints some debug info for the object
func (s Option[A]) String() string {
switch s.Tag {
case NoneTag:
return fmt.Sprintf("None[%T]", s.Value)
case SomeTag:
return fmt.Sprintf("Some[%T](%v)", s.Value, s.Value)
}
return "Invalid"
}
func IsNone[T any](val Option[T]) bool {
return val.Tag == NoneTag
}
func Some[T any](value T) Option[T] {
return Option[T]{Tag: SomeTag, Value: value}
}
func Of[T any](value T) Option[T] {
return Some(value)
}
func None[T any]() Option[T] {
return Option[T]{Tag: NoneTag}
}
func IsSome[T any](val Option[T]) bool {
return val.Tag == SomeTag
}
func MonadFold[A, B any](ma Option[A], onNone func() B, onSome func(A) B) B {
if IsNone(ma) {
return onNone()
}
return onSome(ma.Value)
}
func Unwrap[A any](ma Option[A]) (A, bool) {
return ma.Value, ma.Tag == SomeTag
}

33
option/logger.go Normal file
View File

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

61
option/modern._go Normal file
View File

@@ -0,0 +1,61 @@
//go:build disabled
package option
// Option will be of type T or None
type Option[T any] interface {
fmt.Stringer
}
type none struct {
}
var const_none = none{}
func (none) String() string {
return "None"
}
type some[T any] struct {
v T
}
func (s some[T]) String() string {
return fmt.Sprintf("Some[%v]", s.v)
}
func IsNone[T any](val Option[T]) bool {
return val == const_none
}
func Some[T any](value T) Option[T] {
return some[T]{v: value}
}
func Of[T any](value T) Option[T] {
return Some(value)
}
func None[T any]() Option[T] {
return const_none
}
func IsSome[T any](val Option[T]) bool {
return !IsNone(val)
}
func MonadFold[A, B any](ma Option[A], onNone func() B, onSome func(A) B) B {
if IsNone(ma) {
return onNone()
}
return onSome(ma.(some[A]).v)
}
func Unwrap[A any](a A) func(Option[A]) (A, bool) {
return func(ma Option[A]) (A, bool) {
if IsNone(ma) {
return a, false
}
return ma.(some[A]).v, true
}
}

38
option/monoid.go Normal file
View File

@@ -0,0 +1,38 @@
package option
import (
F "github.com/ibm/fp-go/function"
M "github.com/ibm/fp-go/monoid"
S "github.com/ibm/fp-go/semigroup"
)
func Semigroup[A any]() func(S.Semigroup[A]) S.Semigroup[Option[A]] {
return func(s S.Semigroup[A]) S.Semigroup[Option[A]] {
concat := s.Concat
return S.MakeSemigroup(
func(x, y Option[A]) Option[A] {
return MonadFold(x, F.Constant(y), func(left A) Option[A] {
return MonadFold(y, F.Constant(x), func(right A) Option[A] {
return Some(concat(left, right))
})
})
},
)
}
}
// Monoid returning the left-most non-`None` value. If both operands are `Some`s then the inner values are
// concatenated using the provided `Semigroup`
//
// | x | y | concat(x, y) |
// | ------- | ------- | ------------------ |
// | none | none | none |
// | some(a) | none | some(a) |
// | none | some(b) | some(b) |
// | some(a) | some(b) | some(concat(a, b)) |
func Monoid[A any]() func(S.Semigroup[A]) M.Monoid[Option[A]] {
sg := Semigroup[A]()
return func(s S.Semigroup[A]) M.Monoid[Option[A]] {
return M.MakeMonoid(sg(s).Concat, None[A]())
}
}

20
option/number/number.go Normal file
View File

@@ -0,0 +1,20 @@
package number
import (
"strconv"
F "github.com/ibm/fp-go/function"
O "github.com/ibm/fp-go/option"
)
func atoi(value string) (int, bool) {
data, err := strconv.Atoi(value)
return data, err == nil
}
var (
// Atoi converts a string to an integer
Atoi = O.Optionize1(atoi)
// Itoa converts an integer to a string
Itoa = F.Flow2(strconv.Itoa, O.Of[string])
)

205
option/option.go Normal file
View File

@@ -0,0 +1,205 @@
// package option implements the Option monad, a data type that can have a defined value or none
package option
import (
F "github.com/ibm/fp-go/function"
)
func fromPredicate[A any](a A, pred func(A) bool) Option[A] {
if pred(a) {
return Some(a)
}
return None[A]()
}
func FromPredicate[A any](pred func(A) bool) func(A) Option[A] {
return F.Bind2nd(fromPredicate[A], pred)
}
func FromNillable[A any](a *A) Option[*A] {
return fromPredicate(a, F.IsNonNil[A])
}
func fromValidation[A, B any](a A, f func(A) (B, bool)) Option[B] {
b, ok := f(a)
if ok {
return Some(b)
}
return None[B]()
}
func fromValidation0[A any](f func() (A, bool)) Option[A] {
a, ok := f()
if ok {
return Some(a)
}
return None[A]()
}
func FromValidation[A, B any](f func(A) (B, bool)) func(A) Option[B] {
return F.Bind2nd(fromValidation[A, B], f)
}
// MonadAp is the applicative functor of Option
func MonadAp[A, B any](fab Option[func(A) B], fa Option[A]) Option[B] {
return MonadFold(fab, None[B], func(ab func(A) B) Option[B] {
return MonadFold(fa, None[B], F.Flow2(ab, Some[B]))
})
}
func Ap[A, B any](fa Option[A]) func(Option[func(A) B]) Option[B] {
return F.Bind2nd(MonadAp[A, B], fa)
}
func MonadMap[A, B any](fa Option[A], f func(A) B) Option[B] {
return MonadChain(fa, F.Flow2(f, Some[B]))
}
func Map[A, B any](f func(a A) B) func(Option[A]) Option[B] {
return Chain(F.Flow2(f, Some[B]))
}
func MonadMapTo[A, B any](fa Option[A], b B) Option[B] {
return MonadMap(fa, F.Constant1[A](b))
}
func MapTo[A, B any](b B) func(Option[A]) Option[B] {
return F.Bind2nd(MonadMapTo[A, B], b)
}
func TryCatch[A any](f func() (A, error)) Option[A] {
val, err := f()
if err != nil {
return None[A]()
}
return Some(val)
}
func Fold[A, B any](onNone func() B, onSome func(a A) B) func(ma Option[A]) B {
return func(ma Option[A]) B {
return MonadFold(ma, onNone, onSome)
}
}
func GetOrElse[A any](onNone func() A) func(Option[A]) A {
return Fold(onNone, F.Identity[A])
}
func MonadChain[A, B any](fa Option[A], f func(A) Option[B]) Option[B] {
return MonadFold(fa, None[B], f)
}
func Chain[A, B any](f func(A) Option[B]) func(Option[A]) Option[B] {
return F.Bind2nd(MonadChain[A, B], f)
}
func MonadChainTo[A, B any](ma Option[A], mb Option[B]) Option[B] {
return mb
}
func ChainTo[A, B any](mb Option[B]) func(Option[A]) Option[B] {
return F.Bind2nd(MonadChainTo[A, B], mb)
}
func MonadChainFirst[A, B any](ma Option[A], f func(A) Option[B]) Option[A] {
return MonadChain(ma, func(a A) Option[A] {
return MonadMap(f(a), F.Constant1[B](a))
})
}
func ChainFirst[A, B any](f func(A) Option[B]) func(Option[A]) Option[A] {
return F.Bind2nd(MonadChainFirst[A, B], f)
}
func Flatten[A any](mma Option[Option[A]]) Option[A] {
return MonadChain(mma, F.Identity[Option[A]])
}
func Alt[A any](that func() Option[A]) func(Option[A]) Option[A] {
return Fold(that, Of[A])
}
func MonadSequence2[T1, T2, R any](o1 Option[T1], o2 Option[T2], f func(T1, T2) Option[R]) Option[R] {
return MonadFold(o1, None[R], func(t1 T1) Option[R] {
return MonadFold(o2, None[R], func(t2 T2) Option[R] {
return f(t1, t2)
})
})
}
func Sequence2[T1, T2, R any](f func(T1, T2) Option[R]) func(Option[T1], Option[T2]) Option[R] {
return func(o1 Option[T1], o2 Option[T2]) Option[R] {
return MonadSequence2(o1, o2, f)
}
}
func Reduce[A, B any](f func(B, A) B, initial B) func(Option[A]) B {
return Fold(F.Constant(initial), F.Bind1st(f, initial))
}
// Filter converts an optional onto itself if it is some and the predicate is true
func Filter[A any](pred func(A) bool) func(Option[A]) Option[A] {
return Fold(None[A], F.Ternary(pred, Of[A], F.Ignore1[A](None[A])))
}
func Optionize0[R any](f func() (R, bool)) func() Option[R] {
return func() Option[R] {
return fromValidation0(f)
}
}
func Optionize1[T1, R any](f func(T1) (R, bool)) func(T1) Option[R] {
return func(t1 T1) Option[R] {
return fromValidation0(func() (R, bool) {
return f(t1)
})
}
}
func Unoptionize1[T1, R any](f func(T1) Option[R]) func(T1) (R, bool) {
return func(t1 T1) (R, bool) {
return Unwrap(f(t1))
}
}
func Optionize2[T1, T2, R any](f func(t1 T1, t2 T2) (R, bool)) func(t1 T1, t2 T2) Option[R] {
return func(t1 T1, t2 T2) Option[R] {
return fromValidation0(func() (R, bool) {
return f(t1, t2)
})
}
}
func Unoptionize2[T1, T2, R any](f func(T1, T2) Option[R]) func(T1, T2) (R, bool) {
return func(t1 T1, t2 T2) (R, bool) {
return Unwrap(f(t1, t2))
}
}
func Optionize3[T1, T2, T3, R any](f func(t1 T1, t2 T2, t3 T3) (R, bool)) func(t1 T1, t2 T2, t3 T3) Option[R] {
return func(t1 T1, t2 T2, t3 T3) Option[R] {
return fromValidation0(func() (R, bool) {
return f(t1, t2, t3)
})
}
}
func Unoptionize3[T1, T2, T3, R any](f func(T1, T2, T3) Option[R]) func(T1, T2, T3) (R, bool) {
return func(t1 T1, t2 T2, t3 T3) (R, bool) {
return Unwrap(f(t1, t2, t3))
}
}
func Optionize4[T1, T2, T3, T4, R any](f func(t1 T1, t2 T2, t3 T3, t4 T4) (R, bool)) func(t1 T1, t2 T2, t3 T3, t4 T4) Option[R] {
return func(t1 T1, t2 T2, t3 T3, t4 T4) Option[R] {
return fromValidation0(func() (R, bool) {
return f(t1, t2, t3, t4)
})
}
}
func Unoptionize4[T1, T2, T3, T4, R any](f func(T1, T2, T3, T4) Option[R]) func(T1, T2, T3, T4) (R, bool) {
return func(t1 T1, t2 T2, t3 T3, t4 T4) (R, bool) {
return Unwrap(f(t1, t2, t3, t4))
}
}

116
option/option_test.go Normal file
View File

@@ -0,0 +1,116 @@
package option
import (
"fmt"
"testing"
F "github.com/ibm/fp-go/function"
"github.com/ibm/fp-go/internal/utils"
"github.com/stretchr/testify/assert"
)
func TestReduce(t *testing.T) {
assert.Equal(t, 2, F.Pipe1(None[int](), Reduce(utils.Sum, 2)))
assert.Equal(t, 5, F.Pipe1(Some(3), Reduce(utils.Sum, 2)))
}
func TestIsNone(t *testing.T) {
assert.True(t, IsNone(None[int]()))
assert.False(t, IsNone(Of(1)))
}
func TestIsSome(t *testing.T) {
assert.True(t, IsSome(Of(1)))
assert.False(t, IsSome(None[int]()))
}
func TestMapOption(t *testing.T) {
assert.Equal(t, F.Pipe1(Some(2), Map(utils.Double)), Some(4))
assert.Equal(t, F.Pipe1(None[int](), Map(utils.Double)), None[int]())
}
func TestTryCachOption(t *testing.T) {
res := TryCatch(utils.Error)
assert.Equal(t, None[int](), res)
}
func TestAp(t *testing.T) {
assert.Equal(t, Some(4), F.Pipe1(
Some(utils.Double),
Ap[int, int](Some(2)),
))
assert.Equal(t, None[int](), F.Pipe1(
Some(utils.Double),
Ap[int, int](None[int]()),
))
assert.Equal(t, None[int](), F.Pipe1(
None[func(int) int](),
Ap[int, int](Some(2)),
))
assert.Equal(t, None[int](), F.Pipe1(
None[func(int) int](),
Ap[int, int](None[int]()),
))
}
func TestChain(t *testing.T) {
f := func(n int) Option[int] { return Some(n * 2) }
g := func(_ int) Option[int] { return None[int]() }
assert.Equal(t, Some(2), F.Pipe1(
Some(1),
Chain(f),
))
assert.Equal(t, None[int](), F.Pipe1(
None[int](),
Chain(f),
))
assert.Equal(t, None[int](), F.Pipe1(
Some(1),
Chain(g),
))
assert.Equal(t, None[int](), F.Pipe1(
None[int](),
Chain(g),
))
}
func TestFlatten(t *testing.T) {
assert.Equal(t, Of(1), F.Pipe1(Of(Of(1)), Flatten[int]))
}
func TestFold(t *testing.T) {
f := F.Constant("none")
g := func(s string) string { return fmt.Sprintf("some%d", len(s)) }
fold := Fold(f, g)
assert.Equal(t, "none", fold(None[string]()))
assert.Equal(t, "some3", fold(Some("abc")))
}
func TestFromPredicate(t *testing.T) {
p := func(n int) bool { return n > 2 }
f := FromPredicate(p)
assert.Equal(t, None[int](), f(1))
assert.Equal(t, Some(3), f(3))
}
func TestAlt(t *testing.T) {
assert.Equal(t, Some(1), F.Pipe1(Some(1), Alt(F.Constant(Some(2)))))
assert.Equal(t, Some(2), F.Pipe1(Some(2), Alt(F.Constant(None[int]()))))
assert.Equal(t, Some(1), F.Pipe1(None[int](), Alt(F.Constant(Some(1)))))
assert.Equal(t, None[int](), F.Pipe1(None[int](), Alt(F.Constant(None[int]()))))
}

31
option/record.go Normal file
View File

@@ -0,0 +1,31 @@
package option
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, A, B any](f func(A) Option[B]) func(GA) Option[GB] {
return RR.Traverse[GA](
Of[GB],
MonadMap[GB, func(B) GB],
MonadAp[B, GB],
f,
)
}
// TraverseRecord transforms a record of options into an option of a record
func TraverseRecord[K comparable, A, B any](f func(A) Option[B]) func(map[K]A) Option[map[K]B] {
return TraverseRecordG[map[K]A, map[K]B](f)
}
func SequenceRecordG[GA ~map[K]A, GOA ~map[K]Option[A], K comparable, A any](ma GOA) Option[GA] {
return TraverseRecordG[GOA, GA](F.Identity[Option[A]])(ma)
}
// SequenceRecord converts a homogeneous sequence of either into an either of sequence
func SequenceRecord[K comparable, A any](ma map[K]Option[A]) Option[map[K]A] {
return SequenceRecordG[map[K]A](ma)
}

17
option/record_test.go Normal file
View File

@@ -0,0 +1,17 @@
package option
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSequenceRecord(t *testing.T) {
assert.Equal(t, Of(map[string]string{
"a": "A",
"b": "B",
}), SequenceRecord(map[string]Option[string]{
"a": Of("A"),
"b": Of("B"),
}))
}

58
option/sequence.go Normal file
View File

@@ -0,0 +1,58 @@
package option
import (
Apply "github.com/ibm/fp-go/apply"
F "github.com/ibm/fp-go/function"
T "github.com/ibm/fp-go/tuple"
)
// HKTA = HKT<A>
// HKTOA = HKT<Option<A>>
//
// Sequence converts an option of some higher kinded type into the higher kinded type of an option
func Sequence[A, HKTA, HKTOA any](
_of func(Option[A]) HKTOA,
_map func(HKTA, func(A) Option[A]) HKTOA,
) func(Option[HKTA]) HKTOA {
return Fold(F.Nullary2(None[A], _of), F.Bind2nd(_map, Some[A]))
}
// SequenceT converts n inputs of higher kinded types into a higher kinded types of n strongly typed values, represented as a tuple
func SequenceT1[A any](a Option[A]) Option[T.Tuple1[A]] {
return Apply.SequenceT1(
MonadMap[A, T.Tuple1[A]],
a,
)
}
func SequenceT2[A, B any](a Option[A], b Option[B]) Option[T.Tuple2[A, B]] {
return Apply.SequenceT2(
MonadMap[A, func(B) T.Tuple2[A, B]],
MonadAp[B, T.Tuple2[A, B]],
a, b,
)
}
func SequenceT3[A, B, C any](a Option[A], b Option[B], c Option[C]) Option[T.Tuple3[A, B, C]] {
return Apply.SequenceT3(
MonadMap[A, func(B) func(C) T.Tuple3[A, B, C]],
MonadAp[B, func(C) T.Tuple3[A, B, C]],
MonadAp[C, T.Tuple3[A, B, C]],
a, b, c,
)
}
func SequenceT4[A, B, C, D any](a Option[A], b Option[B], c Option[C], d Option[D]) Option[T.Tuple4[A, B, C, D]] {
return Apply.SequenceT4(
MonadMap[A, func(B) func(C) func(D) T.Tuple4[A, B, C, D]],
MonadAp[B, func(C) func(D) T.Tuple4[A, B, C, D]],
MonadAp[C, func(D) T.Tuple4[A, B, C, D]],
MonadAp[D, T.Tuple4[A, B, C, D]],
a, b, c, d,
)
}

29
option/sequence_test.go Normal file
View File

@@ -0,0 +1,29 @@
package option
import (
"testing"
T "github.com/ibm/fp-go/tuple"
"github.com/stretchr/testify/assert"
)
func TestSequenceT(t *testing.T) {
// one argumemt
s1 := SequenceT1[int]
assert.Equal(t, Of(T.MakeTuple1(1)), s1(Of(1)))
// two arguments
s2 := SequenceT2[int, string]
assert.Equal(t, Of(T.MakeTuple2(1, "a")), s2(Of(1), Of("a")))
// three arguments
s3 := SequenceT3[int, string, bool]
assert.Equal(t, Of(T.MakeTuple3(1, "a", true)), s3(Of(1), Of("a"), Of(true)))
// four arguments
s4 := SequenceT4[int, string, bool, int]
assert.Equal(t, Of(T.MakeTuple4(1, "a", true, 2)), s4(Of(1), Of("a"), Of(true), Of(2)))
// three with one none
assert.Equal(t, None[T.Tuple3[int, string, bool]](), s3(Of(1), Of("a"), None[bool]()))
}

59
option/testing/laws.go Normal file
View File

@@ -0,0 +1,59 @@
package testing
import (
"testing"
EQ "github.com/ibm/fp-go/eq"
L "github.com/ibm/fp-go/internal/monad/testing"
O "github.com/ibm/fp-go/option"
)
// AssertLaws asserts the apply monad laws for the `Either` monad
func AssertLaws[A, B, C any](t *testing.T,
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,
O.Eq(eqa),
O.Eq(eqb),
O.Eq(eqc),
O.Of[A],
O.Of[B],
O.Of[C],
O.Of[func(A) A],
O.Of[func(A) B],
O.Of[func(B) C],
O.Of[func(func(A) B) B],
O.MonadMap[A, A],
O.MonadMap[A, B],
O.MonadMap[A, C],
O.MonadMap[B, C],
O.MonadMap[func(B) C, func(func(A) B) func(A) C],
O.MonadChain[A, A],
O.MonadChain[A, B],
O.MonadChain[A, C],
O.MonadChain[B, C],
O.MonadAp[A, A],
O.MonadAp[A, B],
O.MonadAp[B, C],
O.MonadAp[A, C],
O.MonadAp[func(A) B, B],
O.MonadAp[func(A) B, func(A) C],
ab,
bc,
)
}

View File

@@ -0,0 +1,32 @@
package testing
import (
"fmt"
"testing"
EQ "github.com/ibm/fp-go/eq"
"github.com/stretchr/testify/assert"
)
func TestMonadLaws(t *testing.T) {
// some comparison
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, eqa, eqb, eqc, ab, bc)
assert.True(t, laws(true))
assert.True(t, laws(false))
}

14
option/type.go Normal file
View File

@@ -0,0 +1,14 @@
package option
func toType[T any](a any) (T, bool) {
b, ok := a.(T)
return b, ok
}
func ToType[T any](src any) Option[T] {
return fromValidation(src, toType[T])
}
func ToAny[T any](src T) Option[any] {
return Of(any(src))
}

22
option/type_test.go Normal file
View File

@@ -0,0 +1,22 @@
package option
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestTypeConversion(t *testing.T) {
var src any = "Carsten"
dst := ToType[string](src)
assert.Equal(t, Some("Carsten"), dst)
}
func TestInvalidConversion(t *testing.T) {
var src any = make(map[string]string)
dst := ToType[int](src)
assert.Equal(t, None[int](), dst)
}