├── HTK └── HKT.go ├── magma ├── magma.go └── magma_test.go ├── monad └── monad.go ├── apply ├── apply_test.go └── apply.go ├── identity ├── identity_test.go └── identity.go ├── go.mod ├── img └── gomad-logo.png ├── eq ├── eq.go └── eq_test.go ├── .github └── workflows │ ├── qodana.yml │ ├── test.yml │ └── lint.yml ├── fold ├── fold.go └── fold_test.go ├── functor ├── functor.go └── functor_test.go ├── io ├── io.go └── io_test.go ├── either ├── left.go ├── right.go ├── left_test.go ├── right_test.go ├── either.go └── either_test.go ├── ord ├── ord.go └── ord_test.go ├── maybe ├── maybe.go └── maybe_test.go ├── result ├── result.go └── result_test.go ├── .gitignore ├── README.md └── LICENSE /HTK/HKT.go: -------------------------------------------------------------------------------- 1 | package HTK 2 | -------------------------------------------------------------------------------- /magma/magma.go: -------------------------------------------------------------------------------- 1 | package magma 2 | -------------------------------------------------------------------------------- /monad/monad.go: -------------------------------------------------------------------------------- 1 | package monad 2 | -------------------------------------------------------------------------------- /apply/apply_test.go: -------------------------------------------------------------------------------- 1 | package apply 2 | -------------------------------------------------------------------------------- /magma/magma_test.go: -------------------------------------------------------------------------------- 1 | package magma 2 | -------------------------------------------------------------------------------- /identity/identity_test.go: -------------------------------------------------------------------------------- 1 | package identity 2 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/medmouine/gomad 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /apply/apply.go: -------------------------------------------------------------------------------- 1 | package apply 2 | 3 | type IApply[T any] interface { 4 | Ap() 5 | } 6 | -------------------------------------------------------------------------------- /img/gomad-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medmouine/gomad/HEAD/img/gomad-logo.png -------------------------------------------------------------------------------- /identity/identity.go: -------------------------------------------------------------------------------- 1 | package identity 2 | 3 | func Identity[T any](i T) T { 4 | return i 5 | } 6 | -------------------------------------------------------------------------------- /eq/eq.go: -------------------------------------------------------------------------------- 1 | package eq 2 | 3 | type Eq[T comparable] interface { 4 | Equals(T, T) bool 5 | } 6 | 7 | type eq[T comparable] struct { 8 | equals func(T, T) bool 9 | } 10 | 11 | func FromEquals[T comparable](e func(T, T) bool) Eq[T] { 12 | return &eq[T]{e} 13 | } 14 | 15 | func (e eq[T]) Equals(x T, y T) bool { 16 | return e.equals(x, y) 17 | } 18 | -------------------------------------------------------------------------------- /.github/workflows/qodana.yml: -------------------------------------------------------------------------------- 1 | name: Qodana 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | - 'releases/*' 9 | 10 | jobs: 11 | qodana: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | with: 16 | fetch-depth: 0 17 | - name: 'Qodana Scan' 18 | uses: JetBrains/qodana-action@v2022.2.3 19 | -------------------------------------------------------------------------------- /fold/fold.go: -------------------------------------------------------------------------------- 1 | package collection_utils 2 | 3 | func FoldLeft[T any](coll []T, op func(T, T) T, initial T) T { 4 | for _, x := range coll { 5 | initial = op(initial, x) 6 | } 7 | 8 | // return the final result 9 | return initial 10 | } 11 | 12 | func FoldRight[T any](coll []T, op func(T, T) T, initial T) T { 13 | for i := len(coll) - 1; i >= 0; i-- { 14 | initial = op(coll[i], initial) 15 | } 16 | 17 | return initial 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-go@v2 15 | with: 16 | stable: 'false' 17 | go-version: '1.18.4' 18 | - name: Test 19 | run: | 20 | go test -v ./... -coverprofile=coverage.txt -covermode=atomic 21 | - name: Upload coverage to Codecov 22 | run: bash <(curl -s https://codecov.io/bash) 23 | -------------------------------------------------------------------------------- /functor/functor.go: -------------------------------------------------------------------------------- 1 | package functor 2 | 3 | type IFunctor[T any] interface { 4 | Map(T) T 5 | } 6 | 7 | type Functor[T any] struct { 8 | Val T 9 | } 10 | 11 | func Lift[T any, U any](f func(T) U) func(Functor[T]) Functor[U] { 12 | return func(fa Functor[T]) Functor[U] { 13 | return Functor[U]{ 14 | Val: f(fa.Val), 15 | } 16 | } 17 | } 18 | 19 | func Map[T any, U any](fa Functor[T], f func(T) U) Functor[U] { 20 | return Functor[U]{ 21 | Val: f(fa.Val), 22 | } 23 | } 24 | 25 | func (fa Functor[T]) Map(f func(T) T) Functor[T] { 26 | return Functor[T]{ 27 | Val: f(fa.Val), 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | lint: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-go@v2 15 | with: 16 | stable: 'false' 17 | go-version: '1.18.4' 18 | - name: Prepare 19 | run: | 20 | go get -u golang.org/x/lint/golint 21 | go install golang.org/x/lint/golint 22 | - name: Check 23 | run: | 24 | gofmt -s -d -e . 25 | go vet ./... 26 | golint ./... 27 | -------------------------------------------------------------------------------- /io/io.go: -------------------------------------------------------------------------------- 1 | package io 2 | 3 | type IO[T any] interface { 4 | Call() T 5 | Map(f func(T) T) IO[T] 6 | } 7 | 8 | type io[T any] struct { 9 | IO[T] 10 | f func() T 11 | } 12 | 13 | func Of[T any](t T) IO[T] { 14 | return io[T]{ 15 | f: func() T { 16 | return t 17 | }, 18 | } 19 | } 20 | 21 | func From[T any](f func() T) IO[T] { 22 | return io[T]{ 23 | f: f, 24 | } 25 | } 26 | 27 | func Map[T any, U any](io IO[T], fn func(T) U) IO[U] { 28 | f := func() U { 29 | return fn(io.Call()) 30 | } 31 | return From(f) 32 | } 33 | 34 | func (i io[T]) Call() T { 35 | return i.f() 36 | } 37 | 38 | func (i io[T]) Map(fn func(T) T) IO[T] { 39 | f := func() T { 40 | return fn(i.Call()) 41 | } 42 | return From(f) 43 | } 44 | -------------------------------------------------------------------------------- /either/left.go: -------------------------------------------------------------------------------- 1 | package either 2 | 3 | type left[L, R any] struct { 4 | Either[L, R] 5 | val L 6 | } 7 | 8 | func newL[L, R any](val L) Either[L, R] { 9 | l := new(left[L, R]) 10 | l.val = val 11 | l.Either = &either[L, R]{ 12 | Either: l, 13 | } 14 | 15 | return l 16 | } 17 | 18 | /* 19 | Left returns a new Either value with Left as the passed argument. 20 | By default, the Right Type is the same as the Left Type. 21 | */ 22 | func Left[L any](value L) Either[L, L] { 23 | return newL[L, L](value) 24 | } 25 | 26 | func (l left[L, R]) Left() *L { 27 | return &l.val 28 | } 29 | 30 | func (left[L, R]) Right() *R { 31 | panic(any("called Right on Left")) 32 | } 33 | 34 | func (left[L, R]) IsLeft() bool { 35 | return true 36 | } 37 | 38 | func (left[L, R]) IsRight() bool { 39 | return false 40 | } 41 | -------------------------------------------------------------------------------- /either/right.go: -------------------------------------------------------------------------------- 1 | package either 2 | 3 | type right[L, R any] struct { 4 | Either[L, R] 5 | val R 6 | } 7 | 8 | func newR[L, R any](val R) *right[L, R] { 9 | r := new(right[L, R]) 10 | r.val = val 11 | r.Either = &either[L, R]{ 12 | Either: r, 13 | } 14 | 15 | return r 16 | } 17 | 18 | /* 19 | Right returns a new Either value with Right as the passed argument. 20 | By default, the Left Type is the same as the Right Type. 21 | */ 22 | func Right[R any](value R) Either[R, R] { 23 | return newR[R, R](value) 24 | } 25 | 26 | func (r right[L, R]) Right() *R { 27 | return &r.val 28 | } 29 | 30 | func (right[L, R]) Left() *L { 31 | panic(any("called Left on Right")) 32 | } 33 | 34 | func (right[L, R]) IsLeft() bool { 35 | return false 36 | } 37 | 38 | func (right[L, R]) IsRight() bool { 39 | return true 40 | } 41 | -------------------------------------------------------------------------------- /either/left_test.go: -------------------------------------------------------------------------------- 1 | package either_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/medmouine/gomad/either" 8 | ) 9 | 10 | func TestLeft(t *testing.T) { 11 | t.Parallel() 12 | 13 | got := either.Left("foo") 14 | if !reflect.DeepEqual(got.IsLeft(), true) { 15 | t.Errorf("Left() = %v, want %v", got.IsLeft(), true) 16 | } 17 | 18 | if !reflect.DeepEqual(*got.Left(), "foo") { 19 | t.Errorf("Left() = %v, want %v", *got.Left(), "foo") 20 | } 21 | 22 | if !reflect.DeepEqual(got.IsRight(), false) { 23 | t.Errorf("Left() = %v, want %v", got.IsRight(), false) 24 | } 25 | 26 | defer func() { 27 | err := recover() 28 | if err == nil { 29 | t.Errorf("Right() on Left did not panic") 30 | } 31 | 32 | t.Logf("PASS: Right() on left panic msg: %v", err) 33 | }() 34 | either.Left("foo").Right() 35 | } 36 | -------------------------------------------------------------------------------- /either/right_test.go: -------------------------------------------------------------------------------- 1 | package either_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/medmouine/gomad/either" 8 | ) 9 | 10 | func TestRight(t *testing.T) { 11 | t.Parallel() 12 | 13 | got := either.Right("foo") 14 | if !reflect.DeepEqual(got.IsLeft(), false) { 15 | t.Errorf("Right() = %v, want %v", got.IsLeft(), false) 16 | } 17 | 18 | if !reflect.DeepEqual(*got.Right(), "foo") { 19 | t.Errorf("Right() = %v, want %v", *got.Right(), "foo") 20 | } 21 | 22 | if !reflect.DeepEqual(got.IsRight(), true) { 23 | t.Errorf("Right() = %v, want %v", got.IsRight(), true) 24 | } 25 | 26 | defer func() { 27 | err := recover() 28 | if err == nil { 29 | t.Errorf("Left() on Right did not panic") 30 | } 31 | 32 | t.Logf("PASS: Left() on Right panic msg: %v", err) 33 | }() 34 | either.Right("foo").Left() 35 | } 36 | -------------------------------------------------------------------------------- /io/io_test.go: -------------------------------------------------------------------------------- 1 | package io 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestOf(t *testing.T) { 9 | got := Of("foo") 10 | 11 | if !reflect.DeepEqual(got.Call(), "foo") { 12 | t.Errorf("Of(\"foo\") = %v, want %v", got.Call(), "foo") 13 | } 14 | } 15 | 16 | func TestFrom(t *testing.T) { 17 | fn := func() string { 18 | return "foo" 19 | } 20 | got := From(fn) 21 | 22 | if !reflect.DeepEqual(got.Call(), "foo") { 23 | t.Errorf("From() = %v, want %v", got, "foo") 24 | } 25 | } 26 | 27 | func TestMap(t *testing.T) { 28 | fn1 := func() string { 29 | return "foo" 30 | } 31 | fn2 := func(s string) int { 32 | return len(s) 33 | } 34 | got := Map(From(fn1), fn2) 35 | 36 | if !reflect.DeepEqual(got.Call(), 3) { 37 | t.Errorf("Map() = %v, want %v", got.Call(), 3) 38 | } 39 | } 40 | 41 | func Test_io_Map(t *testing.T) { 42 | fn1 := func() string { 43 | return "foo" 44 | } 45 | fn2 := func(s string) string { 46 | return s + "bar" 47 | } 48 | got := From(fn1).Map(fn2) 49 | 50 | if !reflect.DeepEqual(got.Call(), "foobar") { 51 | t.Errorf("Map() = %v, want %v", got.Call(), "foobar") 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /functor/functor_test.go: -------------------------------------------------------------------------------- 1 | package functor_test 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | 7 | . "github.com/medmouine/gomad/functor" 8 | "github.com/medmouine/gomad/identity" 9 | ) 10 | 11 | func TestLift(t *testing.T) { 12 | t.Parallel() 13 | 14 | intToString := strconv.Itoa 15 | got := Lift(intToString) 16 | 17 | intFunctor := Functor[int]{ 18 | Val: 1, 19 | } 20 | 21 | if got(intFunctor).Val != "1" { 22 | t.Errorf("Lift(intToString) failed") 23 | } 24 | 25 | got2 := Lift(identity.Identity[int]) 26 | 27 | if got2(intFunctor).Val != 1 { 28 | t.Errorf("Lift(identity.Identity) failed") 29 | } 30 | } 31 | 32 | func TestMap(t *testing.T) { 33 | t.Parallel() 34 | 35 | intToString := strconv.Itoa 36 | 37 | intFunctor := Functor[int]{ 38 | Val: 1, 39 | } 40 | 41 | got := Map(intFunctor, intToString) 42 | 43 | if got.Val != "1" { 44 | t.Errorf("Map(intToString) failed") 45 | } 46 | 47 | got2 := Map(intFunctor, identity.Identity[int]) 48 | 49 | if got2.Val != 1 { 50 | t.Errorf("Map(identity.Identity) failed") 51 | } 52 | } 53 | 54 | func TestFunctor_Map(t *testing.T) { 55 | t.Parallel() 56 | 57 | intFunctor := Functor[int]{ 58 | Val: 1, 59 | } 60 | 61 | got := intFunctor.Map(func(i int) int { 62 | return 5 63 | }) 64 | 65 | if got.Val != 5 { 66 | t.Errorf("Map(intToString) failed") 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /eq/eq_test.go: -------------------------------------------------------------------------------- 1 | package eq_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/medmouine/gomad/eq" 7 | ) 8 | 9 | func TestFromEquals(t *testing.T) { 10 | t.Parallel() 11 | 12 | intEq := func(x int, y int) bool { return x == y } 13 | 14 | got := FromEquals(intEq) 15 | 16 | if got.Equals(1, 1) != true { 17 | t.Errorf("Expected 1 == 1 to be true") 18 | } 19 | } 20 | 21 | type Person struct { 22 | Name string 23 | } 24 | 25 | func Test_eq_Equals(t *testing.T) { 26 | t.Parallel() 27 | 28 | intEq := func(x int, y int) bool { return x == y } 29 | got := FromEquals(intEq) 30 | 31 | if got.Equals(1, 2) { 32 | t.Errorf("eq.Equals() = %v, want %v", got.Equals(1, 2), false) 33 | } 34 | 35 | if !got.Equals(1, 1) { 36 | t.Errorf("eq.Equals() = %v, want %v", got.Equals(1, 1), true) 37 | } 38 | 39 | strEq := func(x string, y string) bool { return x == y } 40 | got2 := FromEquals(strEq) 41 | 42 | if got2.Equals("a", "b") { 43 | t.Errorf("eq.Equals() = %v, want %v", got2.Equals("a", "b"), false) 44 | } 45 | 46 | if !got2.Equals("a", "a") { 47 | t.Errorf("eq.Equals() = %v, want %v", got2.Equals("a", "a"), true) 48 | } 49 | 50 | personEq := func(x Person, y Person) bool { return x == y || x.Name == y.Name } 51 | got3 := FromEquals(personEq) 52 | 53 | if got3.Equals(Person{"a"}, Person{"b"}) { 54 | t.Errorf("eq.Equals() = %v, want %v", got3.Equals(Person{"a"}, Person{"b"}), false) 55 | } 56 | 57 | if !got3.Equals(Person{"a"}, Person{"a"}) { 58 | t.Errorf("eq.Equals() = %v, want %v", got3.Equals(Person{"a"}, Person{"a"}), true) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /fold/fold_test.go: -------------------------------------------------------------------------------- 1 | package collection_utils 2 | 3 | import "testing" 4 | 5 | func TestFoldLeft(t *testing.T) { 6 | op := func(x, y int) int { 7 | return x + y 8 | } 9 | 10 | testCases := []struct { 11 | desc string 12 | coll []int 13 | x int 14 | want int 15 | }{ 16 | { 17 | desc: "empty collection", 18 | coll: []int{}, 19 | x: 0, 20 | want: 0, 21 | }, 22 | { 23 | desc: "single-element collection", 24 | coll: []int{1}, 25 | x: 0, 26 | want: 1, 27 | }, 28 | { 29 | desc: "multi-element collection", 30 | coll: []int{1, 2, 3, 4, 5}, 31 | x: 0, 32 | want: 15, 33 | }, 34 | } 35 | 36 | for _, tc := range testCases { 37 | t.Run(tc.desc, func(t *testing.T) { 38 | got := FoldLeft(tc.coll, op, tc.x) 39 | if got != tc.want { 40 | t.Errorf("FoldLeft(%v, %v, %v) = %v; want %v", tc.coll, "op", tc.x, got, tc.want) 41 | } 42 | }) 43 | } 44 | } 45 | 46 | func TestFoldRight(t *testing.T) { 47 | op := func(x, y int) int { 48 | return x + y 49 | } 50 | 51 | testCases := []struct { 52 | desc string 53 | coll []int 54 | x int 55 | want int 56 | }{ 57 | { 58 | desc: "empty collection", 59 | coll: []int{}, 60 | x: 0, 61 | want: 0, 62 | }, 63 | { 64 | desc: "single-element collection", 65 | coll: []int{1}, 66 | x: 0, 67 | want: 1, 68 | }, 69 | { 70 | desc: "multi-element collection", 71 | coll: []int{1, 2, 3, 4, 5}, 72 | x: 0, 73 | want: 15, 74 | }, 75 | } 76 | 77 | for _, tc := range testCases { 78 | t.Run(tc.desc, func(t *testing.T) { 79 | got := FoldRight(tc.coll, op, tc.x) 80 | if got != tc.want { 81 | t.Errorf("FoldRight(%v, %v, %v) = %v; want %v", tc.coll, "op", tc.x, got, tc.want) 82 | } 83 | }) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /ord/ord.go: -------------------------------------------------------------------------------- 1 | package ord 2 | 3 | import "github.com/medmouine/gomad/eq" 4 | 5 | type Ord[T comparable] interface { 6 | Compare(T, T) int 7 | } 8 | 9 | type ord[T comparable] struct { 10 | compare func(T, T) int 11 | } 12 | 13 | func FromCompare[T comparable](compare func(T, T) int) Ord[T] { 14 | return &ord[T]{compare} 15 | } 16 | 17 | func Min[T comparable](o Ord[T]) func(T, T) T { 18 | return func(a, b T) T { 19 | if o.Compare(a, b) < 0 { 20 | return a 21 | } 22 | return b 23 | } 24 | } 25 | 26 | func Max[T comparable](o Ord[T]) func(T, T) T { 27 | return func(a, b T) T { 28 | if o.Compare(a, b) > 0 { 29 | return a 30 | } 31 | return b 32 | } 33 | } 34 | 35 | func Leq[T comparable](o Ord[T]) func(T, T) bool { 36 | return func(a, b T) bool { 37 | return o.Compare(a, b) <= 0 38 | } 39 | } 40 | 41 | func Geq[T comparable](o Ord[T]) func(T, T) bool { 42 | return func(a, b T) bool { 43 | return o.Compare(a, b) >= 0 44 | } 45 | } 46 | 47 | func Equals[T comparable](o Ord[T]) eq.Eq[T] { 48 | return eq.FromEquals(func(a, b T) bool { 49 | return o.Compare(a, b) == 0 50 | }) 51 | } 52 | 53 | func Between[T comparable](o Ord[T]) func(T, T) func(T) bool { 54 | return func(a, b T) func(T) bool { 55 | return func(x T) bool { 56 | return o.Compare(a, x) <= 0 && o.Compare(x, b) <= 0 57 | } 58 | } 59 | } 60 | 61 | func Lt[T comparable](o Ord[T]) func(T, T) bool { 62 | return func(a, b T) bool { 63 | return o.Compare(a, b) < 0 64 | } 65 | } 66 | 67 | func Gt[T comparable](o Ord[T]) func(T, T) bool { 68 | return func(a, b T) bool { 69 | return o.Compare(a, b) > 0 70 | } 71 | } 72 | 73 | func (o ord[T]) Compare(a, b T) int { 74 | if o.compare(a, b) < 0 { 75 | return -1 76 | } 77 | if o.compare(a, b) > 0 { 78 | return 1 79 | } 80 | return 0 81 | } 82 | -------------------------------------------------------------------------------- /maybe/maybe.go: -------------------------------------------------------------------------------- 1 | package maybe 2 | 3 | import "go/types" 4 | 5 | type nillable interface { 6 | any | types.Nil 7 | } 8 | 9 | /* 10 | Maybe is a monadic pattern allowing for data manipulation while abstracting whether the value actually exists or is nil. 11 | For example, if we fetch data from an external API that could be nil, we can still perform manipulation on it while 12 | disregarding its actual state. The Maybe struct will take care of managing the value itself. This is similar to 13 | the Maybe interface in Elm or Haskell or Optional in Java. 14 | This is helpful for CRUD operations by simplifying the code and allowing for seamless manipulation of nullable data. 15 | */ 16 | type Maybe[T any] interface { 17 | Unwrap() *T 18 | Map(func(T) T) Maybe[T] 19 | Apply(func(T)) Maybe[T] 20 | Bind(func(T) Maybe[T]) Maybe[T] 21 | IsSome() bool 22 | IsNil() bool 23 | OrElse(func() T) *T 24 | Or(T) *T 25 | OrNil() *T 26 | } 27 | 28 | type maybe[T any] struct { 29 | Maybe[T] 30 | val *T 31 | } 32 | 33 | func Map[T2, T any](m Maybe[T], f func(T) T2) Maybe[T2] { 34 | if m.IsSome() { 35 | return Just(f(*m.Unwrap())) 36 | } 37 | 38 | return None[T2]() 39 | } 40 | 41 | /* 42 | Of returns a new Maybe based on a value that may or may not be nil. 43 | */ 44 | func Of[T nillable](val *T) Maybe[T] { 45 | if val == nil { 46 | return maybe[T]{val: nil} 47 | } 48 | 49 | return maybe[T]{val: val} 50 | } 51 | 52 | /* 53 | Just returns a new Maybe based on a value that we know is not nil. 54 | */ 55 | func Just[T nillable](val T) Maybe[T] { 56 | return maybe[T]{val: &val} 57 | } 58 | 59 | /* 60 | None returns a new Maybe with an empty value we know is nil. 61 | */ 62 | func None[T nillable]() Maybe[T] { 63 | return maybe[T]{val: nil} 64 | } 65 | 66 | func (m maybe[T]) Unwrap() *T { 67 | if m.IsSome() { 68 | return m.val 69 | } 70 | 71 | panic(any("unwrap of empty Maybe")) 72 | } 73 | 74 | func (m maybe[T]) Map(f func(T) T) Maybe[T] { 75 | if m.IsSome() { 76 | return Just(f(*m.val)) 77 | } 78 | 79 | return m 80 | } 81 | 82 | func (m maybe[T]) Apply(f func(x T)) Maybe[T] { 83 | if m.IsSome() { 84 | f(*m.Unwrap()) 85 | } 86 | 87 | return m 88 | } 89 | 90 | func (m maybe[T]) IsSome() bool { 91 | return m.val != nil 92 | } 93 | 94 | func (m maybe[T]) IsNil() bool { 95 | return m.val == nil 96 | } 97 | 98 | func (m maybe[T]) OrElse(f func() T) *T { 99 | if m.IsNil() { 100 | v := f() 101 | 102 | return &v 103 | } 104 | 105 | return m.Unwrap() 106 | } 107 | 108 | func (m maybe[T]) OrNil() *T { 109 | if m.IsNil() { 110 | return nil 111 | } 112 | 113 | return m.val 114 | } 115 | 116 | func (m maybe[T]) Or(val T) *T { 117 | if m.IsNil() { 118 | return &val 119 | } 120 | 121 | return m.Unwrap() 122 | } 123 | 124 | func (m maybe[T]) Bind(f func(T) Maybe[T]) Maybe[T] { 125 | if m.IsSome() { 126 | return f(*m.Unwrap()) 127 | } 128 | 129 | return None[T]() 130 | } 131 | -------------------------------------------------------------------------------- /result/result.go: -------------------------------------------------------------------------------- 1 | package result 2 | 3 | import "github.com/medmouine/gomad/maybe" 4 | 5 | /* 6 | Result aims at abstracting all logic related to operations susceptible to failures, such as external API calls, etc. 7 | It offers constructors and methods to safely manipulate the result in case of success and handle errors gracefully 8 | in case of failure. 9 | */ 10 | type Result[T any] interface { 11 | WithDefault(T) Result[T] 12 | 13 | Ok() *T 14 | IsOk() bool 15 | IfOk(f func(T)) Result[T] 16 | Map(func(T) T) Result[T] 17 | Or(T) *T 18 | 19 | Err() error 20 | IsErr() bool 21 | IfErr(f func(error)) Result[T] 22 | MapErr(func(error) error) Result[T] 23 | 24 | Maybe() maybe.Maybe[T] 25 | } 26 | 27 | /* 28 | Map applies a function to the value of the Result and returns a new Result of a new type. 29 | */ 30 | func Map[T2, T any](r Result[T], f func(T) T2) Result[T2] { 31 | if r.IsOk() { 32 | v := f(*r.Ok()) 33 | 34 | return Ok(v) 35 | } 36 | 37 | return Err[T2](r.Err()) 38 | } 39 | 40 | /* 41 | Ok creates a new Result from a valid value. 42 | */ 43 | func Ok[T any](val T) Result[T] { 44 | return result[T]{val: &val} 45 | } 46 | 47 | /* 48 | Err creates a new Result from an invalid value (error). 49 | */ 50 | func Err[T any](err error) Result[T] { 51 | return result[T]{err: err} 52 | } 53 | 54 | /* 55 | FromMaybe creates a new Result from a Maybe instance. 56 | */ 57 | func FromMaybe[T any](m maybe.Maybe[T], err error) Result[T] { 58 | if m.IsNil() { 59 | return Err[T](err) 60 | } 61 | 62 | v := m.Unwrap() 63 | 64 | return Ok[T](*v) 65 | } 66 | 67 | /* 68 | Of creates a new Result from a possibly valid value and an error. 69 | */ 70 | func Of[T any](val T, err error) Result[T] { 71 | if err != nil { 72 | return Err[T](err) 73 | } 74 | 75 | return Ok(val) 76 | } 77 | 78 | func (r result[T]) Ok() *T { 79 | if r.IsOk() { 80 | return r.val 81 | } 82 | 83 | panic(any("result.Ok() called on Err() result")) 84 | } 85 | 86 | func (r result[T]) Or(val T) *T { 87 | if r.IsOk() { 88 | return r.Ok() 89 | } 90 | 91 | return &val 92 | } 93 | 94 | func (r result[T]) Err() error { 95 | if r.IsErr() { 96 | return r.err 97 | } 98 | 99 | panic(any("result.Err() called on Ok() result")) 100 | } 101 | 102 | func (r result[T]) WithDefault(val T) Result[T] { 103 | if r.IsOk() { 104 | return r 105 | } 106 | 107 | return Ok(val) 108 | } 109 | 110 | func (r result[T]) Maybe() maybe.Maybe[T] { 111 | if r.IsErr() { 112 | return maybe.None[T]() 113 | } 114 | 115 | return maybe.Just[T](*r.Ok()) 116 | } 117 | 118 | func (r result[T]) MapErr(f func(error) error) Result[T] { 119 | if r.IsErr() { 120 | return Err[T](f(r.Err())) 121 | } 122 | 123 | return r 124 | } 125 | 126 | func (r result[T]) Map(f func(T) T) Result[T] { 127 | if r.IsOk() { 128 | return Ok(f(*r.Ok())) 129 | } 130 | 131 | return r 132 | } 133 | 134 | func (r result[T]) IfErr(f func(error)) Result[T] { 135 | if r.IsErr() { 136 | f(r.err) 137 | } 138 | 139 | return r 140 | } 141 | 142 | func (r result[T]) IfOk(f func(T)) Result[T] { 143 | if r.IsOk() { 144 | f(*r.Ok()) 145 | } 146 | 147 | return r 148 | } 149 | 150 | func (r result[T]) IsOk() bool { 151 | return !r.IsErr() 152 | } 153 | 154 | func (r result[T]) IsErr() bool { 155 | return r.err != nil 156 | } 157 | 158 | type result[T any] struct { 159 | Result[T] 160 | val *T 161 | err error 162 | } 163 | -------------------------------------------------------------------------------- /ord/ord_test.go: -------------------------------------------------------------------------------- 1 | package ord 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestFromCompare(t *testing.T) { 8 | intCompare := func(a int, b int) int { 9 | return a - b 10 | } 11 | 12 | got := FromCompare(intCompare) 13 | 14 | if got.Compare(1, 2) != -1 { 15 | t.Errorf("got %v, want %v", got.Compare(1, 2), -1) 16 | } 17 | 18 | if got.Compare(2, 1) != 1 { 19 | t.Errorf("got %v, want %v", got.Compare(2, 1), 1) 20 | } 21 | 22 | if got.Compare(1, 1) != 0 { 23 | t.Errorf("got %v, want %v", got.Compare(1, 1), 0) 24 | } 25 | } 26 | 27 | func TestMax(t *testing.T) { 28 | intCompare := func(a int, b int) int { 29 | return a - b 30 | } 31 | 32 | got := Max(FromCompare(intCompare)) 33 | 34 | if got(1, 2) != 2 { 35 | t.Errorf("got %v, want %v", got(1, 2), 2) 36 | } 37 | 38 | if got(2, 1) != 2 { 39 | t.Errorf("got %v, want %v", got(2, 1), 2) 40 | } 41 | } 42 | 43 | func TestBetween(t *testing.T) { 44 | intCompare := func(a int, b int) int { 45 | return a - b 46 | } 47 | 48 | got := Between(FromCompare(intCompare)) 49 | 50 | if got(1, 2)(3) { 51 | t.Errorf("got %v, want %v", got(1, 2)(3), false) 52 | } 53 | } 54 | 55 | func TestEquals(t *testing.T) { 56 | intCompare := func(a int, b int) int { 57 | return a - b 58 | } 59 | 60 | got := Equals(FromCompare(intCompare)) 61 | 62 | if got.Equals(1, 2) { 63 | t.Errorf("got %v, want %v", got.Equals(1, 2), false) 64 | } 65 | 66 | if !got.Equals(1, 1) { 67 | t.Errorf("got %v, want %v", got.Equals(1, 1), true) 68 | } 69 | } 70 | 71 | func TestGeq(t *testing.T) { 72 | intCompare := func(a int, b int) int { 73 | return a - b 74 | } 75 | 76 | got := Geq(FromCompare(intCompare)) 77 | 78 | if got(1, 2) { 79 | t.Errorf("got %v, want %v", got(1, 2), false) 80 | } 81 | 82 | if !got(2, 1) { 83 | t.Errorf("got %v, want %v", got(2, 1), true) 84 | } 85 | 86 | if !got(1, 1) { 87 | t.Errorf("got %v, want %v", got(1, 1), true) 88 | } 89 | } 90 | 91 | func TestGt(t *testing.T) { 92 | intCompare := func(a int, b int) int { 93 | return a - b 94 | } 95 | 96 | got := Gt(FromCompare(intCompare)) 97 | 98 | if got(1, 2) { 99 | t.Errorf("got %v, want %v", got(1, 2), false) 100 | } 101 | 102 | if !got(2, 1) { 103 | t.Errorf("got %v, want %v", got(2, 1), true) 104 | } 105 | 106 | if got(1, 1) { 107 | t.Errorf("got %v, want %v", got(1, 1), false) 108 | } 109 | } 110 | 111 | func TestLeq(t *testing.T) { 112 | intCompare := func(a int, b int) int { 113 | return a - b 114 | } 115 | 116 | got := Leq(FromCompare(intCompare)) 117 | 118 | if !got(1, 2) { 119 | t.Errorf("got %v, want %v", got(1, 2), true) 120 | } 121 | 122 | if got(2, 1) { 123 | t.Errorf("got %v, want %v", got(2, 1), false) 124 | } 125 | 126 | if !got(1, 1) { 127 | t.Errorf("got %v, want %v", got(1, 1), true) 128 | } 129 | } 130 | 131 | func TestLt(t *testing.T) { 132 | intCompare := func(a int, b int) int { 133 | return a - b 134 | } 135 | 136 | got := Lt(FromCompare(intCompare)) 137 | 138 | if !got(1, 2) { 139 | t.Errorf("got %v, want %v", got(1, 2), true) 140 | } 141 | 142 | if got(2, 1) { 143 | t.Errorf("got %v, want %v", got(2, 1), false) 144 | } 145 | 146 | if got(1, 1) { 147 | t.Errorf("got %v, want %v", got(1, 1), false) 148 | } 149 | } 150 | 151 | func TestMin(t *testing.T) { 152 | intCompare := func(a int, b int) int { 153 | return a - b 154 | } 155 | 156 | got := Min(FromCompare(intCompare)) 157 | 158 | if got(1, 2) != 1 { 159 | t.Errorf("got %v, want %v", got(1, 2), 1) 160 | } 161 | 162 | if got(2, 1) != 1 { 163 | t.Errorf("got %v, want %v", got(2, 1), 1) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /either/either.go: -------------------------------------------------------------------------------- 1 | package either 2 | 3 | import "github.com/medmouine/gomad/maybe" 4 | 5 | /* 6 | Either allows to manipulate pairs of mutually exclusive data. 7 | For example, if we would want to fall back to a value B if A answers to a specific predicate. 8 | This interface allows integrating this behavior seamlessly by abstracting all the underlying logic of 9 | managing both values. 10 | A common use case for this is form validation for front-end applications. 11 | */ 12 | type Either[L, R any] interface { 13 | IsRight() bool 14 | Right() *R 15 | RightOr(R) *R 16 | RightOrElse(func() R) *R 17 | IfRight(func(R)) Either[L, R] 18 | IfNotRight(func()) Either[L, R] 19 | MaybeRight() maybe.Maybe[R] 20 | 21 | IsLeft() bool 22 | Left() *L 23 | LeftOr(L) *L 24 | LeftOrElse(func() L) *L 25 | IfLeft(func(L)) Either[L, R] 26 | IfNotLeft(func()) Either[L, R] 27 | MaybeLeft() maybe.Maybe[L] 28 | 29 | Swap() Either[R, L] 30 | } 31 | 32 | type either[L any, R any] struct { 33 | Either[L, R] 34 | } 35 | 36 | /* 37 | FromPredicateC returns a function that creates a new Either based on a given predicate and a Left value in case of 38 | failure of the predicate. 39 | */ 40 | func FromPredicateC[L, R any](predicate func(R) bool, left L) func(R) Either[L, R] { 41 | return func(candidate R) Either[L, R] { 42 | if predicate(candidate) { 43 | return newR[L, R](candidate) 44 | } 45 | 46 | return newL[L, R](left) 47 | } 48 | } 49 | 50 | /* 51 | FromPredicate returns a new Either based on a given predicate and a Left value in case of failure of the predicate 52 | and a Right value which will be tested against the predicate. 53 | */ 54 | func FromPredicate[L, R any](predicate func(R) bool, right R, left L) Either[L, R] { 55 | return FromPredicateC[L, R](predicate, left)(right) 56 | } 57 | 58 | func MapRight[R2, L, R any](e Either[L, R], f func(R) R2) Either[L, R2] { 59 | if e.IsRight() { 60 | return newR[L, R2](f(*e.Right())) 61 | } 62 | 63 | return newL[L, R2](*e.Left()) 64 | } 65 | 66 | func MapLeft[L2, L, R any](e Either[L, R], f func(L) L2) Either[L2, R] { 67 | if e.IsLeft() { 68 | return newL[L2, R](f(*e.Left())) 69 | } 70 | 71 | return newR[L2, R](*e.Right()) 72 | } 73 | 74 | func (e either[L, R]) Swap() Either[R, L] { 75 | if e.IsRight() { 76 | return newL[R, L](*e.Right()) 77 | } 78 | 79 | return newR[R, L](*e.Left()) 80 | } 81 | 82 | // ################ 83 | // #### Left ##### 84 | // ################ 85 | 86 | func (e either[L, R]) LeftOr(or L) *L { 87 | if e.IsLeft() { 88 | return e.Left() 89 | } 90 | 91 | return &or 92 | } 93 | 94 | func (e either[L, R]) LeftOrElse(orElse func() L) *L { 95 | if e.IsLeft() { 96 | return e.Left() 97 | } 98 | 99 | v := orElse() 100 | 101 | return &v 102 | } 103 | 104 | func (e either[L, R]) IfLeft(f func(L)) Either[L, R] { 105 | if e.IsLeft() { 106 | f(*e.Left()) 107 | } 108 | 109 | return &e 110 | } 111 | 112 | func (e either[L, R]) IfNotLeft(f func()) Either[L, R] { 113 | if !e.IsLeft() { 114 | f() 115 | } 116 | 117 | return &e 118 | } 119 | 120 | func (e either[L, R]) MaybeLeft() maybe.Maybe[L] { 121 | if e.IsLeft() { 122 | return maybe.Just(*e.Left()) 123 | } 124 | 125 | return maybe.None[L]() 126 | } 127 | 128 | // ################ 129 | // #### Right ##### 130 | // ################ 131 | 132 | func (e either[L, R]) RightOr(or R) *R { 133 | if e.IsRight() { 134 | return e.Right() 135 | } 136 | 137 | return &or 138 | } 139 | 140 | func (e either[L, R]) RightOrElse(orElse func() R) *R { 141 | if e.IsRight() { 142 | return e.Right() 143 | } 144 | 145 | v := orElse() 146 | 147 | return &v 148 | } 149 | 150 | func (e either[L, R]) IfRight(f func(R)) Either[L, R] { 151 | if e.IsRight() { 152 | f(*e.Right()) 153 | } 154 | 155 | return &e 156 | } 157 | 158 | func (e either[L, R]) IfNotRight(f func()) Either[L, R] { 159 | if !e.IsRight() { 160 | f() 161 | } 162 | 163 | return &e 164 | } 165 | 166 | func (e either[L, R]) MaybeRight() maybe.Maybe[R] { 167 | if e.IsRight() { 168 | return maybe.Just(*e.Right()) 169 | } 170 | 171 | return maybe.None[R]() 172 | } 173 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/jetbrains,go,vim 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=jetbrains,go,vim 3 | 4 | ### Go ### 5 | # If you prefer the allow list template instead of the deny list, see community template: 6 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 7 | # 8 | # Binaries for programs and plugins 9 | *.exe 10 | *.exe~ 11 | *.dll 12 | *.so 13 | *.dylib 14 | 15 | # Test binary, built with `go test -c` 16 | *.test 17 | 18 | # Output of the go coverage tool, specifically when used with LiteIDE 19 | *.out 20 | 21 | # Dependency directories (remove the comment below to include it) 22 | # vendor/ 23 | 24 | # Go workspace file 25 | go.work 26 | 27 | ### Go Patch ### 28 | /vendor/ 29 | /Godeps/ 30 | 31 | ### JetBrains ### 32 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 33 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 34 | 35 | # User-specific stuff 36 | .idea/**/workspace.xml 37 | .idea/**/tasks.xml 38 | .idea/**/usage.statistics.xml 39 | .idea/**/dictionaries 40 | .idea/**/shelf 41 | 42 | # AWS User-specific 43 | .idea/**/aws.xml 44 | 45 | # Generated files 46 | .idea/**/contentModel.xml 47 | 48 | # Sensitive or high-churn files 49 | .idea/**/dataSources/ 50 | .idea/**/dataSources.ids 51 | .idea/**/dataSources.local.xml 52 | .idea/**/sqlDataSources.xml 53 | .idea/**/dynamic.xml 54 | .idea/**/uiDesigner.xml 55 | .idea/**/dbnavigator.xml 56 | 57 | # Gradle 58 | .idea/**/gradle.xml 59 | .idea/**/libraries 60 | 61 | # Gradle and Maven with auto-import 62 | # When using Gradle or Maven with auto-import, you should exclude module files, 63 | # since they will be recreated, and may cause churn. Uncomment if using 64 | # auto-import. 65 | # .idea/artifacts 66 | # .idea/compiler.xml 67 | # .idea/jarRepositories.xml 68 | # .idea/modules.xml 69 | # .idea/*.iml 70 | # .idea/modules 71 | # *.iml 72 | # *.ipr 73 | 74 | # CMake 75 | cmake-build-*/ 76 | 77 | # Mongo Explorer plugin 78 | .idea/**/mongoSettings.xml 79 | 80 | # File-based project format 81 | *.iws 82 | 83 | # IntelliJ 84 | out/ 85 | 86 | # mpeltonen/sbt-idea plugin 87 | .idea_modules/ 88 | 89 | # JIRA plugin 90 | atlassian-ide-plugin.xml 91 | 92 | # Cursive Clojure plugin 93 | .idea/replstate.xml 94 | 95 | # SonarLint plugin 96 | .idea/sonarlint/ 97 | 98 | # Crashlytics plugin (for Android Studio and IntelliJ) 99 | com_crashlytics_export_strings.xml 100 | crashlytics.properties 101 | crashlytics-build.properties 102 | fabric.properties 103 | 104 | # Editor-based Rest Client 105 | .idea/httpRequests 106 | 107 | # Android studio 3.1+ serialized cache file 108 | .idea/caches/build_file_checksums.ser 109 | 110 | ### JetBrains Patch ### 111 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 112 | 113 | # *.iml 114 | # modules.xml 115 | # .idea/misc.xml 116 | # *.ipr 117 | 118 | # Sonarlint plugin 119 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 120 | .idea/**/sonarlint/ 121 | 122 | # SonarQube Plugin 123 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 124 | .idea/**/sonarIssues.xml 125 | 126 | # Markdown Navigator plugin 127 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 128 | .idea/**/markdown-navigator.xml 129 | .idea/**/markdown-navigator-enh.xml 130 | .idea/**/markdown-navigator/ 131 | **/.idea 132 | 133 | # Cache file creation bug 134 | # See https://youtrack.jetbrains.com/issue/JBR-2257 135 | .idea/$CACHE_FILE$ 136 | 137 | # CodeStream plugin 138 | # https://plugins.jetbrains.com/plugin/12206-codestream 139 | .idea/codestream.xml 140 | 141 | # Azure Toolkit for IntelliJ plugin 142 | # https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij 143 | .idea/**/azureSettings.xml 144 | 145 | ### Vim ### 146 | # Swap 147 | [._]*.s[a-v][a-z] 148 | !*.svg # comment out if you don't need vector files 149 | [._]*.sw[a-p] 150 | [._]s[a-rt-v][a-z] 151 | [._]ss[a-gi-z] 152 | [._]sw[a-p] 153 | 154 | # Session 155 | Session.vim 156 | Sessionx.vim 157 | 158 | # Temporary 159 | .netrwhist 160 | *~ 161 | # Auto-generated tag files 162 | tags 163 | # Persistent undo 164 | [._]*.un~ 165 | 166 | # End of https://www.toptal.com/developers/gitignore/api/jetbrains,go,vim -------------------------------------------------------------------------------- /maybe/maybe_test.go: -------------------------------------------------------------------------------- 1 | package maybe_test 2 | 3 | import ( 4 | "reflect" 5 | "strconv" 6 | "testing" 7 | 8 | . "github.com/medmouine/gomad/maybe" 9 | ) 10 | 11 | func TestJust(t *testing.T) { 12 | t.Parallel() 13 | 14 | integer := 123 15 | gotInt := Just(integer) 16 | 17 | if !reflect.DeepEqual(*gotInt.Unwrap(), integer) { 18 | t.Errorf("Just() = %v, want %v", *gotInt.Unwrap(), integer) 19 | } 20 | 21 | if !reflect.DeepEqual(gotInt.IsNil(), false) { 22 | t.Errorf("Just() = %v, want %v", gotInt.IsNil(), false) 23 | } 24 | 25 | _string := "abc" 26 | gotStr := Just(_string) 27 | 28 | if !reflect.DeepEqual(*gotStr.Unwrap(), _string) { 29 | t.Errorf("Just() = %v, want %v", *gotStr.Unwrap(), _string) 30 | } 31 | 32 | _slice := []int{1, 2, 3} 33 | gotSlice := Just(_slice) 34 | 35 | if !reflect.DeepEqual(*gotSlice.Unwrap(), _slice) { 36 | t.Errorf("Just() = %v, want %v", *gotSlice.Unwrap(), _slice) 37 | } 38 | } 39 | 40 | func TestNone(t *testing.T) { 41 | t.Parallel() 42 | 43 | got := None[int]() 44 | if !got.IsNil() { 45 | t.Errorf("None() = %v, want %v", got.IsNil(), false) 46 | } 47 | } 48 | 49 | func TestMap(t *testing.T) { 50 | t.Parallel() 51 | 52 | got := Map(Just(1), func(v int) string { 53 | return strconv.Itoa(v) + "!" 54 | }) 55 | 56 | if !reflect.DeepEqual(*got.Unwrap(), "1!") { 57 | t.Errorf("Map() = %v, want %v", *got.Unwrap(), "1!") 58 | } 59 | 60 | got = Map(None[int](), func(v int) string { 61 | return strconv.Itoa(v) + "!" 62 | }) 63 | 64 | if !got.IsNil() { 65 | t.Errorf("Map() = %v, want %v", got.IsNil(), true) 66 | } 67 | } 68 | 69 | func TestOf(t *testing.T) { 70 | t.Parallel() 71 | 72 | integer := 123 73 | gotInt := Of(&integer) 74 | 75 | if !reflect.DeepEqual(*gotInt.Unwrap(), integer) { 76 | t.Errorf("Of() = %v, want %v", *gotInt.Unwrap(), integer) 77 | } 78 | 79 | if gotInt.IsNil() { 80 | t.Errorf("Of() = %v, want %v", gotInt.IsNil(), false) 81 | } 82 | 83 | gotNil := Of[int](nil) 84 | 85 | if !gotNil.IsNil() { 86 | t.Errorf("Of() = %v, want %v", gotNil.IsNil(), true) 87 | } 88 | } 89 | 90 | func TestMaybe_Apply(t *testing.T) { 91 | t.Parallel() 92 | 93 | integer := 1 94 | m := Just(integer) 95 | 96 | called := false 97 | 98 | m.Apply(func(v int) { 99 | called = true 100 | }) 101 | 102 | if !called { 103 | t.Errorf("Apply() called = %v, want %v", called, true) 104 | } 105 | 106 | if !reflect.DeepEqual(*m.Unwrap(), integer) { 107 | t.Errorf("Apply() = %v, want %v", *m.Unwrap(), integer) 108 | } 109 | 110 | called2 := false 111 | 112 | None[int]().Apply(func(v int) { 113 | called2 = true 114 | }) 115 | 116 | if called2 { 117 | t.Errorf("Apply() called = %v, want %v", called2, false) 118 | } 119 | } 120 | 121 | func TestMaybe_Map(t *testing.T) { 122 | t.Parallel() 123 | 124 | got := Just(1).Map(func(v int) int { 125 | return v + 3 126 | }) 127 | if !reflect.DeepEqual(*got.Unwrap(), 4) { 128 | t.Errorf("Map() = %v, want %v", *got.Unwrap(), 4) 129 | } 130 | 131 | if !got.IsSome() { 132 | t.Errorf("Map() = %v, want %v", got.IsSome(), true) 133 | } 134 | 135 | got2 := None[int]().Map(func(v int) int { 136 | return v + 3 137 | }) 138 | if !got2.IsNil() { 139 | t.Errorf("Map() = %v, want %v", got2.IsNil(), true) 140 | } 141 | } 142 | 143 | func TestMaybe_Unwrap(t *testing.T) { 144 | t.Parallel() 145 | 146 | if got := Just(1).Unwrap(); !reflect.DeepEqual(*got, 1) { 147 | t.Errorf("Unwrap() = %v, want %v", *got, 1) 148 | } 149 | 150 | defer func() { 151 | err := recover() 152 | if err == nil { 153 | t.Errorf("Unwrap() on Nil did not panic") 154 | } 155 | }() 156 | None[int]().Unwrap() 157 | } 158 | 159 | func TestMaybe_OrElse(t *testing.T) { 160 | t.Parallel() 161 | 162 | got := None[int]().OrElse(func() int { 163 | return 3 164 | }) 165 | if !reflect.DeepEqual(*got, 3) { 166 | t.Errorf("OrElse() = %v, want %v", *got, 3) 167 | } 168 | 169 | got2 := Just(1).OrElse(func() int { 170 | return 3 171 | }) 172 | if !reflect.DeepEqual(*got2, 1) { 173 | t.Errorf("OrElse() = %v, want %v", *got2, 1) 174 | } 175 | } 176 | 177 | func TestMaybe_Or(t *testing.T) { 178 | t.Parallel() 179 | 180 | if got := None[int]().Or(3); !reflect.DeepEqual(*got, 3) { 181 | t.Errorf("Or() = %v, want %v", *got, 3) 182 | } 183 | 184 | if got2 := Just(1).Or(3); !reflect.DeepEqual(*got2, 1) { 185 | t.Errorf("Or() = %v, want %v", *got2, 1) 186 | } 187 | } 188 | 189 | func TestMaybe_OrNil(t *testing.T) { 190 | t.Parallel() 191 | 192 | got := None[int]().OrNil() 193 | if got != nil { 194 | t.Errorf("OrNil() = %v, want %v", got, nil) 195 | } 196 | 197 | got2 := Just(4).OrNil() 198 | if !reflect.DeepEqual(*got2, 4) { 199 | t.Errorf("OrNil() = %v, want %v", *got2, 4) 200 | } 201 | } 202 | 203 | func TestMaybe_IsNone(t *testing.T) { 204 | t.Parallel() 205 | 206 | if got := None[int]().IsNil(); !got { 207 | t.Errorf("IsNil() = %v, want %v", got, true) 208 | } 209 | 210 | if got2 := Just(4).IsNil(); got2 { 211 | t.Errorf("IsNil() = %v, want %v", got2, false) 212 | } 213 | } 214 | 215 | func TestMaybe_IsSome(t *testing.T) { 216 | t.Parallel() 217 | 218 | if got := Just(1).IsSome(); !got { 219 | t.Errorf("IsSome() = %v, want %v", got, true) 220 | } 221 | 222 | if got2 := None[int]().IsSome(); got2 { 223 | t.Errorf("IsSome() = %v, want %v", got2, false) 224 | } 225 | } 226 | 227 | func TestMaybe_Bind(t *testing.T) { 228 | t.Parallel() 229 | 230 | got := Just(2).Bind(func(t int) Maybe[int] { 231 | return Just(t * t) 232 | }) 233 | if !reflect.DeepEqual(got, Just(4)) { 234 | t.Errorf("Bind() = %v, want %v", got, Just(4)) 235 | } 236 | 237 | got2 := None[int]().Bind(func(t int) Maybe[int] { 238 | return Just(t * t) 239 | }) 240 | if !reflect.DeepEqual(got2, None[int]()) { 241 | t.Errorf("Bind() = %v, want %v", got2, None[int]()) 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GitHub go.mod Go version of a Go module](https://img.shields.io/github/go-mod/go-version/medmouine/gomad.svg)](https://github.com/medmouine/gomad) 2 | [![Test](https://github.com/medmouine/gomad/workflows/Test/badge.svg)](https://github.com/medmouine/gomad/actions/workflows/test.yml) 3 | [![TLint](https://github.com/medmouine/gomad/workflows/Lint/badge.svg)](https://github.com/medmouine/gomad/actions/workflows/lint.yml) 4 | [![codecov](https://codecov.io/gh/medmouine/gomad/branch/main/graph/badge.svg?token=3DJBNCU1NG)](https://codecov.io/gh/medmouine/gomad) 5 | 7 | [![Maintainability](https://api.codeclimate.com/v1/badges/1eaa76225406955456d1/maintainability)](https://codeclimate.com/github/medmouine/gomad/maintainability) 8 | [![Total alerts](https://img.shields.io/lgtm/alerts/g/medmouine/gomad.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/medmouine/gomad/alerts/) 9 | [![TLicense](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 10 | 11 | drawing 12 | 13 | # **GOMAD**: Functional Patterns in Golang 14 | - [Prerequisites](#prerequisites) 15 | - [Install](#install) 16 | - [Modules](#modules) 17 | * [Maybe](#maybe) 18 | + [Usage](#usage) 19 | * [Either](#either) 20 | * [Result](#result) 21 | 22 | 23 | ### This package is still in an early stage of development. Feel free to open a PR and contribute or just open an issue to help me prioritize features. 24 | 25 | Following is a list of featured and upcoming modules in no specific order: 26 | 27 | - [X] Identity 28 | - [X] Eq 29 | - [X] Ord 30 | - [X] Semigroup 31 | - [X] Monoid 32 | - [X] IO 33 | - [X] Functor 34 | - [ ] Apply 35 | - [ ] Applicative 36 | - [ ] Monad 37 | - [ ] Chain 38 | - [X] Maybe 39 | - [X] Either 40 | - [X] Result 41 | - [ ] List 42 | - [ ] Pipe 43 | - [ ] HKT 44 | - [ ] Task 45 | - [ ] Reader 46 | 47 | ## Prerequisites 48 | All these modules use the newly added features of Golang v1.18 (Still in beta as of today) notably type and function generics. 49 | 50 | ## Install 51 | 52 | ```sh 53 | go get github.com/medmouine/gomad/ 54 | 55 | i.e 56 | go get github.com/medmouine/gomad/maybe 57 | ``` 58 | 59 | ### Modules 60 | ## Maybe 61 | ```sh 62 | go get github.com/medmouine/gomad/maybe 63 | ``` 64 | `Maybe` is a monadic pattern allowing for data manipulation while abstracting whether or not the value actually exists or is `nil`. For example, if we fetch data from an external API that could be `nil`, we can still perform manipulation on it while disregarding its actual state. The `Maybe` struct will take care of managing the value itself. This is similar to the Maybe interface in [Elm](https://package.elm-lang.org/packages/elm/core/latest/Maybe) or [Haskell](https://wiki.haskell.org/Maybe) or [Optional in Java](https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html). This is helpful for CRUD operations by simplifying the code and allowing for seamless manipulation of nullable data. 65 | 66 | You can use the functions `Just`, `None` or `Nillable` to instanciate a `Maybe` struct. The type parameter will be determined by the passed argument or by specifying it. For example: 67 | ```go 68 | maybeNilInteger := maybe.Nillable[int](nil) 69 | 70 | nilInteger := maybe.None[int]() 71 | 72 | someInteger := maybe.Just[int](1) 73 | ``` 74 | 75 | #### Usage 76 | 77 | - ##### Example 1 Nil Value 78 | ```go 79 | nilInt := maybe.None[int]() 80 | nilInt.Map(func(i int) int { return i + 1 }) 81 | ``` 82 | The `Map` call does nothing since myint is nil. We also can't unwrap it since it's a nil value (will panic otherwise). 83 | 84 | We can however return an alternative value: 85 | ```go 86 | nilInt.Or(99) // returns 99 (alternative value) 87 | ``` 88 | 89 | - ##### Example 2 Some Value (not `nil`) 90 | ```go 91 | someInt := maybe.Just(3) 92 | someInt. 93 | Map(func(i int) int { return i + 1 }). 94 | Unwrap() // returns 4 (3 + 1) 95 | ``` 96 | if we give an alternative value, we still get the actual value since it's not `nil`. 97 | 98 | ```go 99 | someInt. 100 | Map(func(i int) int { return i + 1 }). 101 | Or(99) // returns 4 (original value mapped) 102 | ``` 103 | 104 | - ##### Example 3 Nillable Value (unsure if `nil` or not) 105 | ```go 106 | nilPerson := new(Person) 107 | nilPerson = nil 108 | maybePerson := maybe.Nillable(nilPerson) 109 | maybePersonMapped := maybePerson.Map(func(p Person) Person { 110 | return Person{name: "john"} 111 | }) 112 | maybePersonMapped.IsNil() // true 113 | maybePersonMapped. 114 | Or(Person{name: "jane"}). 115 | name // returns "jane" 116 | ``` 117 | We get a `Person` with its `name` attribute equal to `jane` which is the alternative value we provided since the actual one is `nil`. 118 | 119 | - ##### Example 4: Nillable Value (unsure if `nil` or not) + Map existing value 120 | 121 | ```go 122 | somePerson := &Person{name: "foo"} 123 | maybePerson := maybe.Nillable(somePerson) maybePersonMapped := maybePerson. 124 | Map(func(p Person) Person { 125 | return Person{name: "bar"} 126 | }) 127 | maybePersonMapped.IsNil() // false 128 | maybePersonMapped.IsSome() // true 129 | maybePersonMapped. 130 | Or(Person{name: "jane"}). 131 | name // returns "bar" (original value mapped) 132 | ``` 133 | We get a `Person` with its `name` attribute equal to `bar` which is the original value after the `Map` call since the actual value is `Person{name: "foo"}` (not `nil`). 134 | 135 | ```go 136 | maybePersonMapped := maybePerson.map(func(p Person) Person { 137 | person2 := p 138 | person2.name := person2.name + "bar" 139 | return person2 140 | }) 141 | 142 | maybePersonMapped.IsNil() // false 143 | maybePersonMapped.IsSome() // true 144 | maybePersonMapped. 145 | Or(Person{name: "jane"}). 146 | name // returns "foobar" (original value mapped) 147 | ``` 148 | We get a `Person` with its `name` attribute equal to `foobar` which is the original value after the `Map` call since the actual value is `Person{name: "foobar"}` (not `nil`). 149 | 150 | ------ 151 | 152 | ## Either 153 | ```sh 154 | go get github.com/medmouine/gomad/either 155 | ``` 156 | Allows to manipulate pairs of mutually exclusive data. For example, if we would want to fall back to a value B if A answers to a specific predicate. This interface allows integrating this behavior seamlessly by abstracting all the underlying logic of managing both values. A common use case for this is form validation for front-end applications. 157 | 158 | WIP 🚧 159 | 160 | ------ 161 | 162 | ## Result 163 | ```sh 164 | go get github.com/medmouine/gomad/result 165 | ``` 166 | This interface aim at abstracting all logic related to operations susceptible to failures, such as external API calls, etc. It offers constructors and methods to safely manipulate the result in case of success and handle errors gracefully in case of failure. 167 | 168 | WIP 🚧 169 | -------------------------------------------------------------------------------- /result/result_test.go: -------------------------------------------------------------------------------- 1 | package result_test 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | "strconv" 7 | "testing" 8 | 9 | "github.com/medmouine/gomad/maybe" 10 | . "github.com/medmouine/gomad/result" 11 | ) 12 | 13 | var err = errors.New("error") 14 | 15 | func TestOk(t *testing.T) { 16 | t.Parallel() 17 | 18 | got := Ok(1) 19 | if !got.IsOk() { 20 | t.Errorf("Ok() = %v, want %v", got.IsOk(), true) 21 | } 22 | 23 | if got.IsErr() { 24 | t.Errorf("Ok() = %v, want %v", got.IsErr(), false) 25 | } 26 | 27 | if !reflect.DeepEqual(*got.Ok(), 1) { 28 | t.Errorf("Ok() = %v, want %v", *got.Ok(), 1) 29 | } 30 | } 31 | 32 | func TestErr(t *testing.T) { 33 | t.Parallel() 34 | 35 | got := Err[int](err) 36 | if got.IsOk() { 37 | t.Errorf("Err() = %v, want %v", got.IsOk(), false) 38 | } 39 | 40 | if !got.IsErr() { 41 | t.Errorf("Err() = %v, want %v", got.IsErr(), true) 42 | } 43 | 44 | if !reflect.DeepEqual(got.Err(), err) { 45 | t.Errorf("Err() = %v, want %v", got.Err(), err) 46 | } 47 | } 48 | 49 | func TestOf(t *testing.T) { 50 | t.Parallel() 51 | 52 | got := Of(1, nil) 53 | if !got.IsOk() { 54 | t.Errorf("Of() = %v, want %v", got.IsOk(), true) 55 | } 56 | 57 | if got.IsErr() { 58 | t.Errorf("Of() = %v, want %v", got.IsErr(), false) 59 | } 60 | 61 | if !reflect.DeepEqual(*got.Ok(), 1) { 62 | t.Errorf("Of() = %v, want %v", *got.Ok(), 1) 63 | } 64 | 65 | got2 := Of(1, err) 66 | if got2.IsOk() { 67 | t.Errorf("Of() = %v, want %v", got2.IsOk(), false) 68 | } 69 | 70 | if !got2.IsErr() { 71 | t.Errorf("Of() = %v, want %v", got2.IsErr(), true) 72 | } 73 | 74 | if !reflect.DeepEqual(got2.Err(), err) { 75 | t.Errorf("Of() = %v, want %v", got2.Err(), err) 76 | } 77 | } 78 | 79 | func TestMap(t *testing.T) { 80 | t.Parallel() 81 | 82 | got := Map(Ok(1), func(i int) string { 83 | return strconv.Itoa(i) + "!" 84 | }) 85 | 86 | if !reflect.DeepEqual(got, Ok("1!")) { 87 | t.Errorf("Map() = %v, want %v", got, Ok("1!")) 88 | } 89 | 90 | got2 := Map(Err[int](err), func(i int) string { 91 | return strconv.Itoa(i) + "!" 92 | }) 93 | 94 | if !reflect.DeepEqual(got2, Err[string](err)) { 95 | t.Errorf("Map() = %v, want %v", got2, Err[string](err)) 96 | } 97 | } 98 | 99 | func TestFromMaybe(t *testing.T) { 100 | t.Parallel() 101 | 102 | got := FromMaybe(maybe.Just(1), err) 103 | if !got.IsOk() { 104 | t.Errorf("FromMaybe() = %v, want %v", got.IsOk(), true) 105 | } 106 | 107 | if got.IsErr() { 108 | t.Errorf("FromMaybe() = %v, want %v", got.IsErr(), false) 109 | } 110 | 111 | if !reflect.DeepEqual(*got.Ok(), 1) { 112 | t.Errorf("FromMaybe() = %v, want %v", got.Ok(), 1) 113 | } 114 | 115 | got2 := FromMaybe(maybe.None[int](), err) 116 | if got2.IsOk() { 117 | t.Errorf("FromMaybe() = %v, want %v", got2.IsOk(), false) 118 | } 119 | 120 | if !got2.IsErr() { 121 | t.Errorf("FromMaybe() = %v, want %v", got2.IsErr(), true) 122 | } 123 | 124 | if !reflect.DeepEqual(got2.Err(), err) { 125 | t.Errorf("FromMaybe() = %v, want %v", got2.Err(), err) 126 | } 127 | } 128 | 129 | func TestResult_Err(t *testing.T) { 130 | t.Parallel() 131 | 132 | if got := Err[int](err).Err(); !reflect.DeepEqual(got, err) { 133 | t.Errorf("Err() = %v, want %v", got, err) 134 | } 135 | 136 | defer func() { 137 | if err := recover(); err == nil { 138 | t.Errorf("Err() on Ok did not panic") 139 | } 140 | }() 141 | Ok(1).Err() 142 | } 143 | 144 | func TestResult_IfErr(t *testing.T) { 145 | t.Parallel() 146 | 147 | called := false 148 | 149 | Err[int](err).IfErr(func(err error) { 150 | called = true 151 | }) 152 | 153 | if !called { 154 | t.Errorf("IfErr() = %v, want %v", called, true) 155 | } 156 | 157 | called2 := false 158 | 159 | Ok(1).IfErr(func(err error) { 160 | called2 = true 161 | }) 162 | 163 | if called2 { 164 | t.Errorf("IfErr() = %v, want %v", called2, false) 165 | } 166 | } 167 | 168 | func TestResult_IfOk(t *testing.T) { 169 | t.Parallel() 170 | 171 | called := false 172 | 173 | Err[int](err).IfOk(func(i int) { 174 | called = true 175 | }) 176 | 177 | if called { 178 | t.Errorf("IfOk() = %v, want %v", called, false) 179 | } 180 | 181 | called2 := false 182 | 183 | Ok(1).IfOk(func(i int) { 184 | called2 = true 185 | }) 186 | 187 | if !called2 { 188 | t.Errorf("IfOk() = %v, want %v", called2, true) 189 | } 190 | } 191 | 192 | func TestResult_IsErr(t *testing.T) { 193 | t.Parallel() 194 | 195 | if got := Err[int](err).IsErr(); !got { 196 | t.Errorf("IsErr() = %v, want %v", got, true) 197 | } 198 | 199 | if got2 := Ok(1).IsErr(); got2 { 200 | t.Errorf("IsErr() = %v, want %v", got2, false) 201 | } 202 | } 203 | 204 | func TestResult_IsOk(t *testing.T) { 205 | t.Parallel() 206 | 207 | got := Err[int](err).IsOk() 208 | if got { 209 | t.Errorf("IsOk() = %v, want %v", got, false) 210 | } 211 | 212 | got2 := Ok(1).IsOk() 213 | 214 | if !got2 { 215 | t.Errorf("IsOk() = %v, want %v", got2, true) 216 | } 217 | } 218 | 219 | func TestResult_Map(t *testing.T) { 220 | t.Parallel() 221 | 222 | got := Err[int](err).Map(func(t int) int { 223 | return 5 224 | }) 225 | 226 | if got.IsOk() { 227 | t.Errorf("Map() = %v, want %v", got.IsOk(), false) 228 | } 229 | 230 | got2 := Ok(1).Map(func(t int) int { 231 | return 5 232 | }) 233 | 234 | if !reflect.DeepEqual(*got2.Ok(), 5) { 235 | t.Errorf("Map() = %v, want %v", *got2.Ok(), 5) 236 | } 237 | } 238 | 239 | func TestResult_MapErr(t *testing.T) { 240 | t.Parallel() 241 | 242 | got := Err[int](err).MapErr(func(e error) error { 243 | return err 244 | }) 245 | 246 | if !reflect.DeepEqual(got.Err(), err) { 247 | t.Errorf("MapErr() = %v, want %v", got.Err(), err) 248 | } 249 | 250 | got2 := Ok(1).MapErr(func(err error) error { 251 | return err 252 | }) 253 | 254 | if !reflect.DeepEqual(*got2.Ok(), 1) { 255 | t.Errorf("MapErr() = %v, want %v", *got2.Ok(), 1) 256 | } 257 | } 258 | 259 | func TestResult_Maybe(t *testing.T) { 260 | t.Parallel() 261 | 262 | if got := Err[int](err).Maybe(); !reflect.DeepEqual(got, maybe.None[int]()) { 263 | t.Errorf("Maybe() = %v, want %v", got, maybe.None[int]()) 264 | } 265 | 266 | if got2 := Ok(1).Maybe(); !reflect.DeepEqual(got2, maybe.Just(1)) { 267 | t.Errorf("Maybe() = %v, want %v", got2, maybe.Just(1)) 268 | } 269 | } 270 | 271 | func TestResult_Ok(t *testing.T) { 272 | t.Parallel() 273 | 274 | if got := *Ok(1).Ok(); !reflect.DeepEqual(got, 1) { 275 | t.Errorf("Ok() = %v, want %v", got, 1) 276 | } 277 | 278 | defer func() { recover() }() 279 | Err[int](err).Ok() 280 | t.Errorf("Ok() on Err did not panic") 281 | } 282 | 283 | func TestResult_WithDefault(t *testing.T) { 284 | t.Parallel() 285 | 286 | if got := Ok(1).WithDefault(10); !reflect.DeepEqual(*got.Ok(), 1) { 287 | t.Errorf("WithDefault() = %v, want %v", *got.Ok(), 1) 288 | } 289 | 290 | got2 := Err[int](err).WithDefault(10) 291 | 292 | if !reflect.DeepEqual(*got2.Ok(), 10) { 293 | t.Errorf("WithDefault() = %v, want %v", got2, 10) 294 | } 295 | } 296 | 297 | func TestResult_Or(t *testing.T) { 298 | t.Parallel() 299 | 300 | if got := *Ok(1).Or(10); !reflect.DeepEqual(got, 1) { 301 | t.Errorf("Or() = %v, want %v", got, 1) 302 | } 303 | 304 | if got2 := *Err[int](err).Or(10); !reflect.DeepEqual(got2, 10) { 305 | t.Errorf("Or() = %v, want %v", got2, 10) 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /either/either_test.go: -------------------------------------------------------------------------------- 1 | package either_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | . "github.com/medmouine/gomad/either" 8 | ) 9 | 10 | func TestFromPredicate(t *testing.T) { 11 | t.Parallel() 12 | 13 | gotRight := FromPredicate(func(i int) bool { return i%2 == 0 }, 2, "not even") 14 | if gotRight.IsLeft() { 15 | t.Errorf("gotRight isLeft %v, want %v", gotRight.IsLeft(), false) 16 | } 17 | 18 | if !gotRight.IsRight() { 19 | t.Errorf("gotRight isRight %v, want %v", gotRight.IsRight(), true) 20 | } 21 | 22 | if !reflect.DeepEqual(*gotRight.Right(), 2) { 23 | t.Errorf("gotRight %v, want %v", *gotRight.Right(), 2) 24 | } 25 | 26 | gotLeft := FromPredicate(func(i int) bool { return i%2 == 0 }, 3, "not even") 27 | if !gotLeft.IsLeft() { 28 | t.Errorf("gotLeft isLeft %v, want %v", gotLeft.IsLeft(), true) 29 | } 30 | 31 | if gotLeft.IsRight() { 32 | t.Errorf("gotLeft isRight %v, want %v", gotLeft.IsRight(), false) 33 | } 34 | 35 | if !reflect.DeepEqual(*gotLeft.Left(), "not even") { 36 | t.Errorf("gotLeft %v, want %v", *gotLeft.Left(), "not even") 37 | } 38 | } 39 | 40 | func TestEither_MapLeft(t *testing.T) { 41 | t.Parallel() 42 | 43 | left := Left("foo") 44 | got := MapLeft(left, func(t string) string { 45 | return t + "bar" 46 | }) 47 | 48 | if !reflect.DeepEqual(*got.Left(), "foobar") { 49 | t.Errorf("MapLeft() = %v, want %v", *got.Left(), "foobar") 50 | } 51 | 52 | right := Right("foo") 53 | called := false 54 | 55 | MapLeft(right, func(t string) string { 56 | called = true 57 | 58 | return t 59 | }) 60 | 61 | if !reflect.DeepEqual(*right.Right(), "foo") { 62 | t.Errorf("MapLeft() = %v, want %v", *right.Right(), "foo") 63 | } 64 | 65 | if !reflect.DeepEqual(called, false) { 66 | t.Errorf("MapLeft() = %v, want %v", called, false) 67 | } 68 | } 69 | 70 | func TestEither_MapRight(t *testing.T) { 71 | t.Parallel() 72 | 73 | left := Left("foo") 74 | called := false 75 | 76 | MapRight(left, func(t string) string { 77 | called = true 78 | 79 | return t 80 | }) 81 | 82 | if !reflect.DeepEqual(*left.Left(), "foo") { 83 | t.Errorf("MapRight() = %v, want %v", *left.Left(), "foo") 84 | } 85 | 86 | if !reflect.DeepEqual(called, false) { 87 | t.Errorf("MapRight() = %v, want %v", called, false) 88 | } 89 | 90 | right := Right("foo") 91 | got := MapRight(right, func(t string) int { 92 | return 123 93 | }) 94 | 95 | if !reflect.DeepEqual(*got.Right(), 123) { 96 | t.Errorf("MapRight() = %v, want %v", *got.Right(), 123) 97 | } 98 | } 99 | 100 | func TestEither_IfLeft(t *testing.T) { 101 | t.Parallel() 102 | 103 | left := Left("foo") 104 | called := false 105 | 106 | left.IfLeft(func(t string) { 107 | called = true 108 | }) 109 | 110 | if !called { 111 | t.Errorf("IfLeft() called = %v, want %v", called, true) 112 | } 113 | 114 | called2 := false 115 | 116 | Right("foo").IfLeft(func(_ string) { 117 | called2 = true 118 | }) 119 | 120 | if called2 { 121 | t.Errorf("IfLeft() called2 = %v, want %v", called2, false) 122 | } 123 | } 124 | 125 | func TestEither_IfRight(t *testing.T) { 126 | t.Parallel() 127 | 128 | right := Right("foo") 129 | called := false 130 | 131 | right.IfRight(func(t string) { 132 | called = true 133 | }) 134 | 135 | if !called { 136 | t.Errorf("IfRight() called = %v, want %v", called, true) 137 | } 138 | 139 | called2 := false 140 | 141 | Left("foo").IfRight(func(t string) { 142 | called2 = true 143 | }) 144 | 145 | if called2 { 146 | t.Errorf("IfRight() called2 = %v, want %v", called2, false) 147 | } 148 | } 149 | 150 | func TestEither_LeftOr(t *testing.T) { 151 | t.Parallel() 152 | 153 | got := *(Left("foo").LeftOr("bar")) 154 | if !reflect.DeepEqual(got, "foo") { 155 | t.Errorf("LeftOr() = %v, want %v", got, "foo") 156 | } 157 | 158 | got2 := *(Right("foo").LeftOr("bar")) 159 | 160 | if !reflect.DeepEqual(got2, "bar") { 161 | t.Errorf("LeftOr() = %v, want %v", got2, "bar") 162 | } 163 | } 164 | 165 | func TestEither_RightOr(t *testing.T) { 166 | t.Parallel() 167 | 168 | got := *(Right("foo").RightOr("bar")) 169 | if !reflect.DeepEqual(got, "foo") { 170 | t.Errorf("RightOr() = %v, want %v", got, "foo") 171 | } 172 | 173 | got2 := *(Left("foo").RightOr("bar")) 174 | 175 | if !reflect.DeepEqual(got2, "bar") { 176 | t.Errorf("RightOr() = %v, want %v", got2, "bar") 177 | } 178 | } 179 | 180 | func TestEither_LeftOrElse(t *testing.T) { 181 | t.Parallel() 182 | 183 | left := Left("foo") 184 | 185 | got := *(left.LeftOrElse(func() string { 186 | return "bar" 187 | })) 188 | 189 | if !reflect.DeepEqual(got, "foo") { 190 | t.Errorf("LeftOrElse() = %v, want %v", got, "foo") 191 | } 192 | 193 | got2 := *(Right("foo").LeftOrElse(func() string { 194 | return "bar" 195 | })) 196 | 197 | if !reflect.DeepEqual(got2, "bar") { 198 | t.Errorf("LeftOrElse() = %v, want %v", got2, "bar") 199 | } 200 | } 201 | 202 | func TestEither_RightOrElse(t *testing.T) { 203 | t.Parallel() 204 | 205 | right := Right("foo") 206 | 207 | got := *(right.RightOrElse(func() string { 208 | return "bar" 209 | })) 210 | 211 | if !reflect.DeepEqual(got, "foo") { 212 | t.Errorf("RightOrElse() = %v, want %v", got, "foo") 213 | } 214 | 215 | got2 := *(Left("foo").RightOrElse(func() string { 216 | return "bar" 217 | })) 218 | 219 | if !reflect.DeepEqual(got2, "bar") { 220 | t.Errorf("RightOrElse() = %v, want %v", got2, "bar") 221 | } 222 | } 223 | 224 | func TestEither_IfNotLeft(t *testing.T) { 225 | t.Parallel() 226 | 227 | left := Left("foo") 228 | 229 | called := false 230 | 231 | left.IfNotLeft(func() { 232 | called = true 233 | }) 234 | 235 | if called { 236 | t.Errorf("IfNotLeft() called = %v, want %v", called, false) 237 | } 238 | 239 | called2 := false 240 | 241 | Right("foo").IfNotLeft(func() { 242 | called2 = true 243 | }) 244 | 245 | if !called2 { 246 | t.Errorf("IfNotLeft() called2 = %v, want %v", called2, true) 247 | } 248 | } 249 | 250 | func TestEither_IfNotRight(t *testing.T) { 251 | t.Parallel() 252 | 253 | right := Right("foo") 254 | 255 | called := false 256 | 257 | right.IfNotRight(func() { 258 | called = true 259 | }) 260 | 261 | if called { 262 | t.Errorf("IfNotRight() called = %v, want %v", called, false) 263 | } 264 | 265 | called2 := false 266 | 267 | Left("foo").IfNotRight(func() { 268 | called2 = true 269 | }) 270 | 271 | if !called2 { 272 | t.Errorf("IfNotRight() called2 = %v, want %v", called2, true) 273 | } 274 | } 275 | 276 | func TestEither_MaybeLeft(t *testing.T) { 277 | t.Parallel() 278 | 279 | got := Left("foo").MaybeLeft() 280 | 281 | if !got.IsSome() { 282 | t.Errorf("MaybeLeft() = %v, want %v", got.IsSome(), true) 283 | 284 | if !reflect.DeepEqual(*got.Unwrap(), "foo") { 285 | t.Errorf("MaybeLeft() = %v, want %v", *got.Unwrap(), "foo") 286 | } 287 | } 288 | 289 | got2 := Right("foo").MaybeLeft() 290 | 291 | if got2.IsSome() { 292 | t.Errorf("MaybeLeft() = %v, want %v", got.IsSome(), false) 293 | } 294 | } 295 | 296 | func TestEither_MaybeRight(t *testing.T) { 297 | t.Parallel() 298 | 299 | got := Right("foo").MaybeRight() 300 | 301 | if !got.IsSome() { 302 | t.Errorf("MaybeRight() = %v, want %v", got.IsSome(), true) 303 | 304 | if !reflect.DeepEqual(*got.Unwrap(), "foo") { 305 | t.Errorf("MaybeRight() = %v, want %v", *got.Unwrap(), "foo") 306 | } 307 | } 308 | 309 | got2 := Left("foo").MaybeRight() 310 | 311 | if got2.IsSome() { 312 | t.Errorf("MaybeRight() = %v, want %v", got2.IsSome(), false) 313 | } 314 | } 315 | 316 | func TestEither_Swap(t *testing.T) { 317 | t.Parallel() 318 | 319 | got := Right("foo").Swap() 320 | 321 | if !got.IsLeft() { 322 | t.Errorf("Swap() = %v, want %v", got.IsLeft(), true) 323 | 324 | if !reflect.DeepEqual(*got.Left(), "foo") { 325 | t.Errorf("Swap() = %v, want %v", *got.Left(), "foo") 326 | } 327 | } 328 | 329 | got2 := Left("foo").Swap() 330 | 331 | if !got2.IsRight() { 332 | t.Errorf("Swap() = %v, want %v", got2.IsRight(), true) 333 | 334 | if !reflect.DeepEqual(*got2.Right(), "foo") { 335 | t.Errorf("Swap() = %v, want %v", *got2.Right(), "foo") 336 | } 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | --------------------------------------------------------------------------------