mirror of
https://github.com/IBM/fp-go.git
synced 2025-11-23 22:14:53 +02:00
559 lines
14 KiB
Go
559 lines
14 KiB
Go
// Copyright (c) 2025 IBM Corp.
|
|
// All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package option
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"testing"
|
|
|
|
F "github.com/IBM/fp-go/v2/function"
|
|
M "github.com/IBM/fp-go/v2/monoid"
|
|
N "github.com/IBM/fp-go/v2/number"
|
|
P "github.com/IBM/fp-go/v2/pair"
|
|
S "github.com/IBM/fp-go/v2/semigroup"
|
|
T "github.com/IBM/fp-go/v2/tuple"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
// Test FromNillable
|
|
func TestFromNillable(t *testing.T) {
|
|
var nilPtr *int = nil
|
|
assert.Equal(t, None[*int](), FromNillable(nilPtr))
|
|
|
|
val := 42
|
|
ptr := &val
|
|
result := FromNillable(ptr)
|
|
assert.True(t, IsSome(result))
|
|
unwrapped, ok := Unwrap(result)
|
|
assert.True(t, ok)
|
|
assert.Equal(t, &val, unwrapped)
|
|
}
|
|
|
|
// Test FromValidation
|
|
func TestFromValidation(t *testing.T) {
|
|
validate := func(x int) (int, bool) {
|
|
if x > 0 {
|
|
return x * 2, true
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
f := FromValidation(validate)
|
|
assert.Equal(t, Some(10), f(5))
|
|
assert.Equal(t, None[int](), f(-1))
|
|
}
|
|
|
|
// Test MonadAp
|
|
func TestMonadAp(t *testing.T) {
|
|
double := N.Mul(2)
|
|
|
|
assert.Equal(t, Some(10), MonadAp(Some(double), Some(5)))
|
|
assert.Equal(t, None[int](), MonadAp(Some(double), None[int]()))
|
|
assert.Equal(t, None[int](), MonadAp(None[func(int) int](), Some(5)))
|
|
assert.Equal(t, None[int](), MonadAp(None[func(int) int](), None[int]()))
|
|
}
|
|
|
|
// Test MonadMap
|
|
func TestMonadMap(t *testing.T) {
|
|
double := N.Mul(2)
|
|
|
|
assert.Equal(t, Some(10), MonadMap(Some(5), double))
|
|
assert.Equal(t, None[int](), MonadMap(None[int](), double))
|
|
}
|
|
|
|
// Test MonadMapTo
|
|
func TestMonadMapTo(t *testing.T) {
|
|
assert.Equal(t, Some("hello"), MonadMapTo(Some(42), "hello"))
|
|
assert.Equal(t, None[string](), MonadMapTo(None[int](), "hello"))
|
|
}
|
|
|
|
// Test MapTo
|
|
func TestMapTo(t *testing.T) {
|
|
replaceWith42 := MapTo[string](42)
|
|
assert.Equal(t, Some(42), replaceWith42(Some("hello")))
|
|
assert.Equal(t, None[int](), replaceWith42(None[string]()))
|
|
}
|
|
|
|
// Test MonadGetOrElse
|
|
func TestMonadGetOrElse(t *testing.T) {
|
|
defaultVal := func() int { return 0 }
|
|
|
|
assert.Equal(t, 42, MonadGetOrElse(Some(42), defaultVal))
|
|
assert.Equal(t, 0, MonadGetOrElse(None[int](), defaultVal))
|
|
}
|
|
|
|
// Test GetOrElse
|
|
func TestGetOrElse(t *testing.T) {
|
|
getOrZero := GetOrElse(func() int { return 0 })
|
|
|
|
assert.Equal(t, 42, getOrZero(Some(42)))
|
|
assert.Equal(t, 0, getOrZero(None[int]()))
|
|
}
|
|
|
|
// Test MonadChain
|
|
func TestMonadChain(t *testing.T) {
|
|
validate := func(x int) Option[int] {
|
|
if x > 0 {
|
|
return Some(x * 2)
|
|
}
|
|
return None[int]()
|
|
}
|
|
|
|
assert.Equal(t, Some(10), MonadChain(Some(5), validate))
|
|
assert.Equal(t, None[int](), MonadChain(Some(-1), validate))
|
|
assert.Equal(t, None[int](), MonadChain(None[int](), validate))
|
|
}
|
|
|
|
// Test MonadChainTo
|
|
func TestMonadChainTo(t *testing.T) {
|
|
assert.Equal(t, Some("hello"), MonadChainTo(Some(42), Some("hello")))
|
|
assert.Equal(t, None[string](), MonadChainTo(Some(42), None[string]()))
|
|
assert.Equal(t, None[string](), MonadChainTo(None[int](), Some("hello")))
|
|
}
|
|
|
|
// Test ChainTo
|
|
func TestChainTo(t *testing.T) {
|
|
replaceWith := ChainTo[int](Some("hello"))
|
|
assert.Equal(t, Some("hello"), replaceWith(Some(42)))
|
|
assert.Equal(t, None[string](), replaceWith(None[int]()))
|
|
}
|
|
|
|
// Test MonadChainFirst
|
|
func TestMonadChainFirst(t *testing.T) {
|
|
sideEffect := func(x int) Option[string] {
|
|
return Some(fmt.Sprintf("%d", x))
|
|
}
|
|
|
|
assert.Equal(t, Some(5), MonadChainFirst(Some(5), sideEffect))
|
|
assert.Equal(t, None[int](), MonadChainFirst(None[int](), sideEffect))
|
|
}
|
|
|
|
// Test ChainFirst
|
|
func TestChainFirst(t *testing.T) {
|
|
sideEffect := func(x int) Option[string] {
|
|
return Some(fmt.Sprintf("%d", x))
|
|
}
|
|
chainFirst := ChainFirst(sideEffect)
|
|
|
|
assert.Equal(t, Some(5), chainFirst(Some(5)))
|
|
assert.Equal(t, None[int](), chainFirst(None[int]()))
|
|
}
|
|
|
|
// Test MonadAlt
|
|
func TestMonadAlt(t *testing.T) {
|
|
alternative := func() Option[int] { return Some(10) }
|
|
|
|
assert.Equal(t, Some(5), MonadAlt(Some(5), alternative))
|
|
assert.Equal(t, Some(10), MonadAlt(None[int](), alternative))
|
|
}
|
|
|
|
// Test MonadSequence2
|
|
func TestMonadSequence2(t *testing.T) {
|
|
combine := func(a, b int) Option[int] {
|
|
return Some(a + b)
|
|
}
|
|
|
|
assert.Equal(t, Some(5), MonadSequence2(Some(2), Some(3), combine))
|
|
assert.Equal(t, None[int](), MonadSequence2(None[int](), Some(3), combine))
|
|
assert.Equal(t, None[int](), MonadSequence2(Some(2), None[int](), combine))
|
|
}
|
|
|
|
// Test Sequence2
|
|
func TestSequence2(t *testing.T) {
|
|
add := Sequence2(func(a, b int) Option[int] { return Some(a + b) })
|
|
|
|
assert.Equal(t, Some(5), add(Some(2), Some(3)))
|
|
assert.Equal(t, None[int](), add(None[int](), Some(3)))
|
|
}
|
|
|
|
// Test Filter
|
|
func TestFilter(t *testing.T) {
|
|
isPositive := Filter(func(x int) bool { return x > 0 })
|
|
|
|
assert.Equal(t, Some(5), isPositive(Some(5)))
|
|
assert.Equal(t, None[int](), isPositive(Some(-1)))
|
|
assert.Equal(t, None[int](), isPositive(None[int]()))
|
|
}
|
|
|
|
// Test MonadFlap
|
|
func TestMonadFlap(t *testing.T) {
|
|
double := N.Mul(2)
|
|
|
|
assert.Equal(t, Some(10), MonadFlap(Some(double), 5))
|
|
assert.Equal(t, None[int](), MonadFlap(None[func(int) int](), 5))
|
|
}
|
|
|
|
// Test Flap
|
|
func TestFlap(t *testing.T) {
|
|
applyFive := Flap[int](5)
|
|
double := N.Mul(2)
|
|
|
|
assert.Equal(t, Some(10), applyFive(Some(double)))
|
|
assert.Equal(t, None[int](), applyFive(None[func(int) int]()))
|
|
}
|
|
|
|
// Test Unwrap
|
|
func TestUnwrap(t *testing.T) {
|
|
val, ok := Unwrap(Some(42))
|
|
assert.True(t, ok)
|
|
assert.Equal(t, 42, val)
|
|
|
|
val, ok = Unwrap(None[int]())
|
|
assert.False(t, ok)
|
|
assert.Equal(t, 0, val)
|
|
}
|
|
|
|
// Test String and Format
|
|
func TestStringFormat(t *testing.T) {
|
|
opt := Some(42)
|
|
str := opt.String()
|
|
assert.Contains(t, str, "Some")
|
|
assert.Contains(t, str, "42")
|
|
|
|
none := None[int]()
|
|
str = none.String()
|
|
assert.Contains(t, str, "None")
|
|
}
|
|
|
|
// Test Semigroup
|
|
func TestSemigroup(t *testing.T) {
|
|
intSemigroup := S.MakeSemigroup(func(a, b int) int { return a + b })
|
|
optSemigroup := Semigroup[int]()(intSemigroup)
|
|
|
|
assert.Equal(t, Some(5), optSemigroup.Concat(Some(2), Some(3)))
|
|
assert.Equal(t, Some(2), optSemigroup.Concat(Some(2), None[int]()))
|
|
assert.Equal(t, Some(3), optSemigroup.Concat(None[int](), Some(3)))
|
|
assert.Equal(t, None[int](), optSemigroup.Concat(None[int](), None[int]()))
|
|
}
|
|
|
|
// Test Monoid
|
|
func TestMonoid(t *testing.T) {
|
|
intSemigroup := S.MakeSemigroup(func(a, b int) int { return a + b })
|
|
optMonoid := Monoid[int]()(intSemigroup)
|
|
|
|
assert.Equal(t, Some(5), optMonoid.Concat(Some(2), Some(3)))
|
|
assert.Equal(t, None[int](), optMonoid.Empty())
|
|
}
|
|
|
|
// Test ApplySemigroup
|
|
func TestApplySemigroup(t *testing.T) {
|
|
intSemigroup := S.MakeSemigroup(func(a, b int) int { return a + b })
|
|
optSemigroup := ApplySemigroup(intSemigroup)
|
|
|
|
assert.Equal(t, Some(5), optSemigroup.Concat(Some(2), Some(3)))
|
|
assert.Equal(t, None[int](), optSemigroup.Concat(Some(2), None[int]()))
|
|
}
|
|
|
|
// Test ApplicativeMonoid
|
|
func TestApplicativeMonoid(t *testing.T) {
|
|
intMonoid := M.MakeMonoid(func(a, b int) int { return a + b }, 0)
|
|
optMonoid := ApplicativeMonoid(intMonoid)
|
|
|
|
assert.Equal(t, Some(5), optMonoid.Concat(Some(2), Some(3)))
|
|
assert.Equal(t, Some(0), optMonoid.Empty())
|
|
}
|
|
|
|
// Test AlternativeMonoid
|
|
func TestAlternativeMonoid(t *testing.T) {
|
|
intMonoid := M.MakeMonoid(func(a, b int) int { return a + b }, 0)
|
|
optMonoid := AlternativeMonoid(intMonoid)
|
|
|
|
// AlternativeMonoid uses applicative semantics, so it combines values
|
|
assert.Equal(t, Some(5), optMonoid.Concat(Some(2), Some(3)))
|
|
assert.Equal(t, Some(3), optMonoid.Concat(None[int](), Some(3)))
|
|
assert.Equal(t, Some(0), optMonoid.Empty())
|
|
}
|
|
|
|
// Test AltMonoid
|
|
func TestAltMonoid(t *testing.T) {
|
|
optMonoid := AltMonoid[int]()
|
|
|
|
assert.Equal(t, Some(2), optMonoid.Concat(Some(2), Some(3)))
|
|
assert.Equal(t, Some(3), optMonoid.Concat(None[int](), Some(3)))
|
|
assert.Equal(t, None[int](), optMonoid.Empty())
|
|
}
|
|
|
|
// Test Do, Let, LetTo, BindTo
|
|
func TestDoLetLetToBindTo(t *testing.T) {
|
|
type State struct {
|
|
x int
|
|
y int
|
|
computed int
|
|
name string
|
|
}
|
|
|
|
result := F.Pipe4(
|
|
Do(State{}),
|
|
Let(func(c int) func(State) State {
|
|
return func(s State) State { s.x = c; return s }
|
|
}, func(s State) int { return 5 }),
|
|
LetTo(func(n string) func(State) State {
|
|
return func(s State) State { s.name = n; return s }
|
|
}, "test"),
|
|
Bind(func(y int) func(State) State {
|
|
return func(s State) State { s.y = y; return s }
|
|
}, func(s State) Option[int] { return Some(10) }),
|
|
Map(func(s State) State {
|
|
s.computed = s.x + s.y
|
|
return s
|
|
}),
|
|
)
|
|
|
|
expected := Some(State{x: 5, y: 10, computed: 15, name: "test"})
|
|
assert.Equal(t, expected, result)
|
|
}
|
|
|
|
// Test BindTo
|
|
func TestBindToFunction(t *testing.T) {
|
|
type State struct {
|
|
value int
|
|
}
|
|
|
|
result := F.Pipe1(
|
|
Some(42),
|
|
BindTo(func(x int) State { return State{value: x} }),
|
|
)
|
|
|
|
assert.Equal(t, Some(State{value: 42}), result)
|
|
}
|
|
|
|
// Test Functor
|
|
func TestFunctor(t *testing.T) {
|
|
f := Functor[int, string]()
|
|
mapper := f.Map(strconv.Itoa)
|
|
|
|
assert.Equal(t, Some("42"), mapper(Some(42)))
|
|
assert.Equal(t, None[string](), mapper(None[int]()))
|
|
}
|
|
|
|
// Test Monad
|
|
func TestMonad(t *testing.T) {
|
|
m := Monad[int, string]()
|
|
|
|
// Test Of
|
|
assert.Equal(t, Some(42), m.Of(42))
|
|
|
|
// Test Map
|
|
mapper := m.Map(strconv.Itoa)
|
|
assert.Equal(t, Some("42"), mapper(Some(42)))
|
|
|
|
// Test Chain
|
|
chainer := m.Chain(func(x int) Option[string] {
|
|
if x > 0 {
|
|
return Some(fmt.Sprintf("%d", x))
|
|
}
|
|
return None[string]()
|
|
})
|
|
assert.Equal(t, Some("42"), chainer(Some(42)))
|
|
|
|
// Test Ap
|
|
double := func(x int) string { return fmt.Sprintf("%d", x*2) }
|
|
ap := m.Ap(Some(5))
|
|
assert.Equal(t, Some("10"), ap(Some(double)))
|
|
}
|
|
|
|
// Test Pointed
|
|
func TestPointed(t *testing.T) {
|
|
p := Pointed[int]()
|
|
assert.Equal(t, Some(42), p.Of(42))
|
|
}
|
|
|
|
// Test ToAny
|
|
func TestToAny(t *testing.T) {
|
|
result := ToAny(42)
|
|
assert.True(t, IsSome(result))
|
|
|
|
val, ok := Unwrap(result)
|
|
assert.True(t, ok)
|
|
assert.Equal(t, 42, val)
|
|
}
|
|
|
|
// Test TraverseArray
|
|
func TestTraverseArray(t *testing.T) {
|
|
validate := func(x int) Option[int] {
|
|
if x > 0 {
|
|
return Some(x * 2)
|
|
}
|
|
return None[int]()
|
|
}
|
|
|
|
result := TraverseArray(validate)([]int{1, 2, 3})
|
|
assert.Equal(t, Some([]int{2, 4, 6}), result)
|
|
|
|
result = TraverseArray(validate)([]int{1, -1, 3})
|
|
assert.Equal(t, None[[]int](), result)
|
|
}
|
|
|
|
// Test TraverseArrayWithIndex
|
|
func TestTraverseArrayWithIndex(t *testing.T) {
|
|
f := func(i int, x int) Option[int] {
|
|
if x > i {
|
|
return Some(x + i)
|
|
}
|
|
return None[int]()
|
|
}
|
|
|
|
result := TraverseArrayWithIndex(f)([]int{1, 2, 3})
|
|
assert.Equal(t, Some([]int{1, 3, 5}), result)
|
|
}
|
|
|
|
// Test TraverseRecord
|
|
func TestTraverseRecord(t *testing.T) {
|
|
validate := func(x int) Option[string] {
|
|
if x > 0 {
|
|
return Some(fmt.Sprintf("%d", x))
|
|
}
|
|
return None[string]()
|
|
}
|
|
|
|
input := map[string]int{"a": 1, "b": 2}
|
|
result := TraverseRecord[string](validate)(input)
|
|
|
|
expected := Some(map[string]string{"a": "1", "b": "2"})
|
|
assert.Equal(t, expected, result)
|
|
}
|
|
|
|
// Test TraverseRecordWithIndex
|
|
func TestTraverseRecordWithIndex(t *testing.T) {
|
|
f := func(k string, v int) Option[string] {
|
|
return Some(fmt.Sprintf("%s:%d", k, v))
|
|
}
|
|
|
|
input := map[string]int{"a": 1, "b": 2}
|
|
result := TraverseRecordWithIndex(f)(input)
|
|
|
|
assert.True(t, IsSome(result))
|
|
}
|
|
|
|
// Test SequencePair
|
|
func TestSequencePair(t *testing.T) {
|
|
pair := P.MakePair(Some(1), Some("hello"))
|
|
result := SequencePair(pair)
|
|
|
|
assert.True(t, IsSome(result))
|
|
|
|
pair2 := P.MakePair(Some(1), None[string]())
|
|
result2 := SequencePair(pair2)
|
|
assert.True(t, IsNone(result2))
|
|
}
|
|
|
|
// Test Optionize functions
|
|
func TestOptionize0(t *testing.T) {
|
|
f := func() (int, bool) {
|
|
return 42, true
|
|
}
|
|
|
|
optF := Optionize0(f)
|
|
assert.Equal(t, Some(42), optF())
|
|
}
|
|
|
|
func TestOptionize1(t *testing.T) {
|
|
f := func(x int) (int, bool) {
|
|
if x > 0 {
|
|
return x * 2, true
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
optF := Optionize1(f)
|
|
assert.Equal(t, Some(10), optF(5))
|
|
assert.Equal(t, None[int](), optF(-1))
|
|
}
|
|
|
|
func TestOptionize2(t *testing.T) {
|
|
f := func(x, y int) (int, bool) {
|
|
if x > 0 && y > 0 {
|
|
return x + y, true
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
optF := Optionize2(f)
|
|
assert.Equal(t, Some(5), optF(2, 3))
|
|
assert.Equal(t, None[int](), optF(-1, 3))
|
|
}
|
|
|
|
// Test Unoptionize functions
|
|
func TestUnoptionize0(t *testing.T) {
|
|
f := func() Option[int] {
|
|
return Some(42)
|
|
}
|
|
|
|
unoptF := Unoptionize0(f)
|
|
val, ok := unoptF()
|
|
assert.True(t, ok)
|
|
assert.Equal(t, 42, val)
|
|
}
|
|
|
|
func TestUnoptionize1(t *testing.T) {
|
|
f := func(x int) Option[int] {
|
|
if x > 0 {
|
|
return Some(x * 2)
|
|
}
|
|
return None[int]()
|
|
}
|
|
|
|
unoptF := Unoptionize1(f)
|
|
val, ok := unoptF(5)
|
|
assert.True(t, ok)
|
|
assert.Equal(t, 10, val)
|
|
|
|
_, ok = unoptF(-1)
|
|
assert.False(t, ok)
|
|
}
|
|
|
|
// Test SequenceTuple functions
|
|
func TestSequenceTuple2(t *testing.T) {
|
|
tuple := T.MakeTuple2(Some(1), Some("hello"))
|
|
result := SequenceTuple2(tuple)
|
|
|
|
expected := Some(T.MakeTuple2(1, "hello"))
|
|
assert.Equal(t, expected, result)
|
|
}
|
|
|
|
func TestSequenceTuple3(t *testing.T) {
|
|
tuple := T.MakeTuple3(Some(1), Some("hello"), Some(true))
|
|
result := SequenceTuple3(tuple)
|
|
|
|
expected := Some(T.MakeTuple3(1, "hello", true))
|
|
assert.Equal(t, expected, result)
|
|
}
|
|
|
|
// Test TraverseTuple functions
|
|
func TestTraverseTuple2(t *testing.T) {
|
|
f1 := func(x int) Option[int] { return Some(x * 2) }
|
|
f2 := func(s string) Option[string] { return Some(s + "!") }
|
|
|
|
traverse := TraverseTuple2(f1, f2)
|
|
tuple := T.MakeTuple2(5, "hello")
|
|
result := traverse(tuple)
|
|
|
|
expected := Some(T.MakeTuple2(10, "hello!"))
|
|
assert.Equal(t, expected, result)
|
|
}
|
|
|
|
// Test FromStrictCompare
|
|
func TestFromStrictCompare(t *testing.T) {
|
|
optOrd := FromStrictCompare[int]()
|
|
|
|
assert.Equal(t, 0, optOrd.Compare(Some(5), Some(5)))
|
|
assert.Equal(t, -1, optOrd.Compare(Some(3), Some(5)))
|
|
assert.Equal(t, 1, optOrd.Compare(Some(5), Some(3)))
|
|
assert.Equal(t, -1, optOrd.Compare(None[int](), Some(5)))
|
|
assert.Equal(t, 1, optOrd.Compare(Some(5), None[int]()))
|
|
}
|