1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-11-23 22:14:53 +02:00
Files
fp-go/v2/option/option_extended_test.go
Dr. Carsten Leue 03d9720a29 fix: optimize performance for option
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-17 12:19:24 +01:00

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]()))
}