mirror of
https://github.com/IBM/fp-go.git
synced 2025-11-23 22:14:53 +02:00
552 lines
13 KiB
Go
552 lines
13 KiB
Go
// Copyright (c) 2024 - 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 pair
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"testing"
|
|
|
|
EQ "github.com/IBM/fp-go/v2/eq"
|
|
N "github.com/IBM/fp-go/v2/number"
|
|
S "github.com/IBM/fp-go/v2/string"
|
|
"github.com/IBM/fp-go/v2/tuple"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestOf(t *testing.T) {
|
|
p := Of(42)
|
|
assert.Equal(t, 42, Head(p))
|
|
assert.Equal(t, 42, Tail(p))
|
|
}
|
|
|
|
func TestMakePair(t *testing.T) {
|
|
p := MakePair("hello", 42)
|
|
assert.Equal(t, "hello", Head(p))
|
|
assert.Equal(t, 42, Tail(p))
|
|
}
|
|
|
|
func TestFromTuple(t *testing.T) {
|
|
tup := tuple.MakeTuple2("world", 100)
|
|
p := FromTuple(tup)
|
|
assert.Equal(t, "world", Head(p))
|
|
assert.Equal(t, 100, Tail(p))
|
|
}
|
|
|
|
func TestToTuple(t *testing.T) {
|
|
p := MakePair("hello", 42)
|
|
tup := ToTuple(p)
|
|
assert.Equal(t, "hello", tup.F1)
|
|
assert.Equal(t, 42, tup.F2)
|
|
}
|
|
|
|
func TestHeadAndTail(t *testing.T) {
|
|
p := MakePair("test", 123)
|
|
assert.Equal(t, "test", Head(p))
|
|
assert.Equal(t, 123, Tail(p))
|
|
}
|
|
|
|
func TestFirstAndSecond(t *testing.T) {
|
|
p := MakePair("first", "second")
|
|
assert.Equal(t, "first", First(p))
|
|
assert.Equal(t, "second", Second(p))
|
|
}
|
|
|
|
func TestMonadMapHead(t *testing.T) {
|
|
p := MakePair(5, "hello")
|
|
p2 := MonadMapHead(p, strconv.Itoa)
|
|
assert.Equal(t, "5", Head(p2))
|
|
assert.Equal(t, "hello", Tail(p2))
|
|
}
|
|
|
|
func TestMonadMapTail(t *testing.T) {
|
|
p := MakePair(5, "hello")
|
|
p2 := MonadMapTail(p, func(s string) int {
|
|
return len(s)
|
|
})
|
|
assert.Equal(t, 5, Head(p2))
|
|
assert.Equal(t, 5, Tail(p2))
|
|
}
|
|
|
|
func TestMonadMap(t *testing.T) {
|
|
p := MakePair(10, "test")
|
|
p2 := MonadMap(p, S.Format[int]("value: %d"))
|
|
assert.Equal(t, "value: 10", Head(p2))
|
|
assert.Equal(t, "test", Tail(p2))
|
|
}
|
|
|
|
func TestMonadBiMap(t *testing.T) {
|
|
p := MakePair(5, "hello")
|
|
p2 := MonadBiMap(p,
|
|
strconv.Itoa,
|
|
S.Size,
|
|
)
|
|
assert.Equal(t, "5", Head(p2))
|
|
assert.Equal(t, 5, Tail(p2))
|
|
}
|
|
|
|
func TestMapHead(t *testing.T) {
|
|
mapper := MapHead[string](strconv.Itoa)
|
|
p := MakePair(42, "world")
|
|
p2 := mapper(p)
|
|
assert.Equal(t, "42", Head(p2))
|
|
assert.Equal(t, "world", Tail(p2))
|
|
}
|
|
|
|
func TestMapTail(t *testing.T) {
|
|
mapper := MapTail[int](func(s string) int {
|
|
return len(s)
|
|
})
|
|
p := MakePair(10, "hello")
|
|
p2 := mapper(p)
|
|
assert.Equal(t, 10, Head(p2))
|
|
assert.Equal(t, 5, Tail(p2))
|
|
}
|
|
|
|
func TestMap(t *testing.T) {
|
|
mapper := Map[int](func(s string) int {
|
|
return len(s)
|
|
})
|
|
p := MakePair(10, "test")
|
|
p2 := mapper(p)
|
|
assert.Equal(t, 10, Head(p2))
|
|
assert.Equal(t, 4, Tail(p2))
|
|
}
|
|
|
|
func TestBiMap(t *testing.T) {
|
|
mapper := BiMap(
|
|
S.Format[int]("n=%d"),
|
|
S.Size,
|
|
)
|
|
p := MakePair(7, "hello")
|
|
p2 := mapper(p)
|
|
assert.Equal(t, "n=7", Head(p2))
|
|
assert.Equal(t, 5, Tail(p2))
|
|
}
|
|
|
|
func TestSwap(t *testing.T) {
|
|
p := MakePair("hello", 42)
|
|
swapped := Swap(p)
|
|
assert.Equal(t, 42, Head(swapped))
|
|
assert.Equal(t, "hello", Tail(swapped))
|
|
}
|
|
|
|
func TestMonadChainHead(t *testing.T) {
|
|
strConcat := S.Semigroup
|
|
p := MakePair(5, "hello")
|
|
p2 := MonadChainHead(strConcat, p, func(n int) Pair[string, string] {
|
|
return MakePair(fmt.Sprintf("%d", n), "!")
|
|
})
|
|
assert.Equal(t, "5", Head(p2))
|
|
assert.Equal(t, "hello!", Tail(p2))
|
|
}
|
|
|
|
func TestMonadChainTail(t *testing.T) {
|
|
intSum := N.SemigroupSum[int]()
|
|
p := MakePair(5, "hello")
|
|
p2 := MonadChainTail(intSum, p, func(s string) Pair[int, int] {
|
|
return MakePair(len(s), len(s)*2)
|
|
})
|
|
assert.Equal(t, 10, Head(p2)) // 5 + 5
|
|
assert.Equal(t, 10, Tail(p2))
|
|
}
|
|
|
|
func TestMonadChain(t *testing.T) {
|
|
intSum := N.SemigroupSum[int]()
|
|
p := MakePair(3, "test")
|
|
p2 := MonadChain(intSum, p, func(s string) Pair[int, int] {
|
|
return MakePair(len(s), len(s)*3)
|
|
})
|
|
assert.Equal(t, 7, Head(p2)) // 3 + 4
|
|
assert.Equal(t, 12, Tail(p2))
|
|
}
|
|
|
|
func TestChainHead(t *testing.T) {
|
|
strConcat := S.Semigroup
|
|
chain := ChainHead(strConcat, func(n int) Pair[string, string] {
|
|
return MakePair(fmt.Sprintf("%d", n), "!")
|
|
})
|
|
p := MakePair(42, "hello")
|
|
p2 := chain(p)
|
|
assert.Equal(t, "42", Head(p2))
|
|
assert.Equal(t, "hello!", Tail(p2))
|
|
}
|
|
|
|
func TestChainTail(t *testing.T) {
|
|
intSum := N.SemigroupSum[int]()
|
|
chain := ChainTail(intSum, func(s string) Pair[int, int] {
|
|
return MakePair(len(s), len(s)*2)
|
|
})
|
|
p := MakePair(10, "world")
|
|
p2 := chain(p)
|
|
assert.Equal(t, 15, Head(p2)) // 10 + 5
|
|
assert.Equal(t, 10, Tail(p2))
|
|
}
|
|
|
|
func TestChain(t *testing.T) {
|
|
intSum := N.SemigroupSum[int]()
|
|
chain := Chain(intSum, func(s string) Pair[int, int] {
|
|
return MakePair(len(s), len(s)*2)
|
|
})
|
|
p := MakePair(5, "hi")
|
|
p2 := chain(p)
|
|
assert.Equal(t, 7, Head(p2)) // 5 + 2
|
|
assert.Equal(t, 4, Tail(p2))
|
|
}
|
|
|
|
func TestMonadApHead(t *testing.T) {
|
|
strConcat := S.Semigroup
|
|
pf := MakePair(strconv.Itoa, "!")
|
|
pv := MakePair(42, "hello")
|
|
result := MonadApHead(strConcat, pf, pv)
|
|
assert.Equal(t, "42", Head(result))
|
|
assert.Equal(t, "hello!", Tail(result))
|
|
}
|
|
|
|
func TestMonadApTail(t *testing.T) {
|
|
intSum := N.SemigroupSum[int]()
|
|
pf := MakePair(10, S.Size)
|
|
pv := MakePair(5, "hello")
|
|
result := MonadApTail(intSum, pf, pv)
|
|
assert.Equal(t, 15, Head(result)) // 5 + 10
|
|
assert.Equal(t, 5, Tail(result))
|
|
}
|
|
|
|
func TestMonadAp(t *testing.T) {
|
|
intSum := N.SemigroupSum[int]()
|
|
pf := MakePair(7, func(s string) int { return len(s) * 2 })
|
|
pv := MakePair(3, "test")
|
|
result := MonadAp(intSum, pf, pv)
|
|
assert.Equal(t, 10, Head(result)) // 3 + 7
|
|
assert.Equal(t, 8, Tail(result)) // len("test") * 2
|
|
}
|
|
|
|
func TestApHead(t *testing.T) {
|
|
strConcat := S.Semigroup
|
|
pv := MakePair(100, "world")
|
|
ap := ApHead[string, int, string](strConcat, pv)
|
|
pf := MakePair(func(n int) string { return fmt.Sprintf("num=%d", n) }, "!")
|
|
result := ap(pf)
|
|
assert.Equal(t, "num=100", Head(result))
|
|
assert.Equal(t, "world!", Tail(result))
|
|
}
|
|
|
|
func TestApTail(t *testing.T) {
|
|
intSum := N.SemigroupSum[int]()
|
|
pv := MakePair(20, "hello")
|
|
ap := ApTail[int, string, int](intSum, pv)
|
|
pf := MakePair(5, S.Size)
|
|
result := ap(pf)
|
|
assert.Equal(t, 25, Head(result)) // 20 + 5
|
|
assert.Equal(t, 5, Tail(result))
|
|
}
|
|
|
|
func TestAp(t *testing.T) {
|
|
intSum := N.SemigroupSum[int]()
|
|
pv := MakePair(15, "test")
|
|
ap := Ap[int, string, int](intSum, pv)
|
|
pf := MakePair(10, func(s string) int { return len(s) * 3 })
|
|
result := ap(pf)
|
|
assert.Equal(t, 25, Head(result)) // 15 + 10
|
|
assert.Equal(t, 12, Tail(result)) // len("test") * 3
|
|
}
|
|
|
|
func TestPaired(t *testing.T) {
|
|
add := func(a, b int) int { return a + b }
|
|
pairedAdd := Paired(add)
|
|
result := pairedAdd(MakePair(3, 4))
|
|
assert.Equal(t, 7, result)
|
|
}
|
|
|
|
func TestUnpaired(t *testing.T) {
|
|
pairedAdd := func(p Pair[int, int]) int {
|
|
return Head(p) + Tail(p)
|
|
}
|
|
add := Unpaired(pairedAdd)
|
|
result := add(5, 7)
|
|
assert.Equal(t, 12, result)
|
|
}
|
|
|
|
func TestMerge(t *testing.T) {
|
|
add := N.Add[int]
|
|
merge := Merge(add)
|
|
result := merge(MakePair(3, 4))
|
|
assert.Equal(t, 7, result)
|
|
}
|
|
|
|
func TestEq(t *testing.T) {
|
|
pairEq := Eq(
|
|
EQ.FromStrictEquals[string](),
|
|
EQ.FromStrictEquals[int](),
|
|
)
|
|
p1 := MakePair("hello", 42)
|
|
p2 := MakePair("hello", 42)
|
|
p3 := MakePair("world", 42)
|
|
p4 := MakePair("hello", 100)
|
|
|
|
assert.True(t, pairEq.Equals(p1, p2))
|
|
assert.False(t, pairEq.Equals(p1, p3))
|
|
assert.False(t, pairEq.Equals(p1, p4))
|
|
}
|
|
|
|
func TestFromStrictEquals(t *testing.T) {
|
|
pairEq := FromStrictEquals[string, int]()
|
|
p1 := MakePair("test", 123)
|
|
p2 := MakePair("test", 123)
|
|
p3 := MakePair("test", 456)
|
|
|
|
assert.True(t, pairEq.Equals(p1, p2))
|
|
assert.False(t, pairEq.Equals(p1, p3))
|
|
}
|
|
|
|
func TestString(t *testing.T) {
|
|
p := MakePair("hello", 42)
|
|
str := p.String()
|
|
assert.Contains(t, str, "Pair")
|
|
assert.Contains(t, str, "hello")
|
|
assert.Contains(t, str, "42")
|
|
}
|
|
|
|
func TestFormat(t *testing.T) {
|
|
p := MakePair("test", 100)
|
|
str := fmt.Sprintf("%s", p)
|
|
assert.Contains(t, str, "Pair")
|
|
assert.Contains(t, str, "test")
|
|
assert.Contains(t, str, "100")
|
|
}
|
|
|
|
func TestMonadHead(t *testing.T) {
|
|
stringMonoid := S.Monoid
|
|
monad := MonadHead[int, string, string](stringMonoid)
|
|
|
|
// Test Of
|
|
p := monad.Of(42)
|
|
assert.Equal(t, 42, Head(p))
|
|
assert.Equal(t, "", Tail(p))
|
|
|
|
// Test Map
|
|
mapper := monad.Map(strconv.Itoa)
|
|
p2 := mapper(MakePair(100, "!"))
|
|
assert.Equal(t, "100", Head(p2))
|
|
assert.Equal(t, "!", Tail(p2))
|
|
|
|
// Test Chain
|
|
chain := monad.Chain(func(n int) Pair[string, string] {
|
|
return MakePair(fmt.Sprintf("n=%d", n), "!")
|
|
})
|
|
p3 := chain(MakePair(7, "hello"))
|
|
assert.Equal(t, "n=7", Head(p3))
|
|
assert.Equal(t, "hello!", Tail(p3))
|
|
|
|
// Test Ap
|
|
pv := MakePair(5, "world")
|
|
ap := monad.Ap(pv)
|
|
pf := MakePair(func(n int) string { return fmt.Sprintf("%d", n*2) }, "!")
|
|
p4 := ap(pf)
|
|
assert.Equal(t, "10", Head(p4))
|
|
assert.Equal(t, "world!", Tail(p4))
|
|
}
|
|
|
|
func TestPointedHead(t *testing.T) {
|
|
stringMonoid := S.Monoid
|
|
pointed := PointedHead[int](stringMonoid)
|
|
p := pointed.Of(42)
|
|
assert.Equal(t, 42, Head(p))
|
|
assert.Equal(t, "", Tail(p))
|
|
}
|
|
|
|
func TestFunctorHead(t *testing.T) {
|
|
functor := FunctorHead[int, string, string]()
|
|
mapper := functor.Map(func(n int) string { return fmt.Sprintf("value=%d", n) })
|
|
p := MakePair(42, "test")
|
|
p2 := mapper(p)
|
|
assert.Equal(t, "value=42", Head(p2))
|
|
assert.Equal(t, "test", Tail(p2))
|
|
}
|
|
|
|
func TestApplicativeHead(t *testing.T) {
|
|
stringMonoid := S.Monoid
|
|
applicative := ApplicativeHead[int, string, string](stringMonoid)
|
|
|
|
// Test Of
|
|
p := applicative.Of(100)
|
|
assert.Equal(t, 100, Head(p))
|
|
assert.Equal(t, "", Tail(p))
|
|
|
|
// Test Map
|
|
mapper := applicative.Map(strconv.Itoa)
|
|
p2 := mapper(MakePair(42, "!"))
|
|
assert.Equal(t, "42", Head(p2))
|
|
assert.Equal(t, "!", Tail(p2))
|
|
|
|
// Test Ap
|
|
pv := MakePair(7, "hello")
|
|
ap := applicative.Ap(pv)
|
|
pf := MakePair(func(n int) string { return fmt.Sprintf("n=%d", n) }, "!")
|
|
p3 := ap(pf)
|
|
assert.Equal(t, "n=7", Head(p3))
|
|
assert.Equal(t, "hello!", Tail(p3))
|
|
}
|
|
|
|
func TestMonadTail(t *testing.T) {
|
|
intSum := N.MonoidSum[int]()
|
|
monad := MonadTail[string, int, int](intSum)
|
|
|
|
// Test Of
|
|
p := monad.Of("hello")
|
|
assert.Equal(t, 0, Head(p))
|
|
assert.Equal(t, "hello", Tail(p))
|
|
|
|
// Test Map
|
|
mapper := monad.Map(S.Size)
|
|
p2 := mapper(MakePair(5, "world"))
|
|
assert.Equal(t, 5, Head(p2))
|
|
assert.Equal(t, 5, Tail(p2))
|
|
|
|
// Test Chain
|
|
chain := monad.Chain(func(s string) Pair[int, int] {
|
|
return MakePair(len(s), len(s)*2)
|
|
})
|
|
p3 := chain(MakePair(10, "test"))
|
|
assert.Equal(t, 14, Head(p3)) // 10 + 4
|
|
assert.Equal(t, 8, Tail(p3))
|
|
|
|
// Test Ap
|
|
pv := MakePair(5, "hello")
|
|
ap := monad.Ap(pv)
|
|
pf := MakePair(10, S.Size)
|
|
p4 := ap(pf)
|
|
assert.Equal(t, 15, Head(p4)) // 5 + 10
|
|
assert.Equal(t, 5, Tail(p4))
|
|
}
|
|
|
|
func TestPointedTail(t *testing.T) {
|
|
intSum := N.MonoidSum[int]()
|
|
pointed := PointedTail[string](intSum)
|
|
p := pointed.Of("test")
|
|
assert.Equal(t, 0, Head(p))
|
|
assert.Equal(t, "test", Tail(p))
|
|
}
|
|
|
|
func TestFunctorTail(t *testing.T) {
|
|
functor := FunctorTail[string, int, int]()
|
|
mapper := functor.Map(func(s string) int { return len(s) * 2 })
|
|
p := MakePair(10, "hello")
|
|
p2 := mapper(p)
|
|
assert.Equal(t, 10, Head(p2))
|
|
assert.Equal(t, 10, Tail(p2))
|
|
}
|
|
|
|
func TestApplicativeTail(t *testing.T) {
|
|
intSum := N.MonoidSum[int]()
|
|
applicative := ApplicativeTail[string, int, int](intSum)
|
|
|
|
// Test Of
|
|
p := applicative.Of("world")
|
|
assert.Equal(t, 0, Head(p))
|
|
assert.Equal(t, "world", Tail(p))
|
|
|
|
// Test Map
|
|
mapper := applicative.Map(S.Size)
|
|
p2 := mapper(MakePair(5, "test"))
|
|
assert.Equal(t, 5, Head(p2))
|
|
assert.Equal(t, 4, Tail(p2))
|
|
|
|
// Test Ap
|
|
pv := MakePair(10, "hello")
|
|
ap := applicative.Ap(pv)
|
|
pf := MakePair(5, func(s string) int { return len(s) * 2 })
|
|
p3 := ap(pf)
|
|
assert.Equal(t, 15, Head(p3)) // 10 + 5
|
|
assert.Equal(t, 10, Tail(p3))
|
|
}
|
|
|
|
func TestMonad(t *testing.T) {
|
|
intSum := N.MonoidSum[int]()
|
|
monad := Monad[string, int, int](intSum)
|
|
|
|
p := monad.Of("test")
|
|
assert.Equal(t, 0, Head(p))
|
|
assert.Equal(t, "test", Tail(p))
|
|
}
|
|
|
|
func TestPointed(t *testing.T) {
|
|
intSum := N.MonoidSum[int]()
|
|
pointed := Pointed[string](intSum)
|
|
|
|
p := pointed.Of("hello")
|
|
assert.Equal(t, 0, Head(p))
|
|
assert.Equal(t, "hello", Tail(p))
|
|
}
|
|
|
|
func TestFunctor(t *testing.T) {
|
|
functor := Functor[string, int, int]()
|
|
mapper := functor.Map(S.Size)
|
|
p := MakePair(7, "world")
|
|
p2 := mapper(p)
|
|
assert.Equal(t, 7, Head(p2))
|
|
assert.Equal(t, 5, Tail(p2))
|
|
}
|
|
|
|
func TestApplicative(t *testing.T) {
|
|
intSum := N.MonoidSum[int]()
|
|
applicative := Applicative[string, int, int](intSum)
|
|
|
|
p := applicative.Of("test")
|
|
assert.Equal(t, 0, Head(p))
|
|
assert.Equal(t, "test", Tail(p))
|
|
}
|
|
|
|
// Test edge cases and complex scenarios
|
|
func TestComplexChaining(t *testing.T) {
|
|
intSum := N.SemigroupSum[int]()
|
|
|
|
// Chain multiple operations
|
|
p := MakePair(1, "a")
|
|
p2 := MonadChainTail(intSum, p, func(s string) Pair[int, string] {
|
|
return MakePair(len(s), s+"b")
|
|
})
|
|
p3 := MonadChainTail(intSum, p2, func(s string) Pair[int, string] {
|
|
return MakePair(len(s), s+"c")
|
|
})
|
|
|
|
assert.Equal(t, 4, Head(p3)) // 1 + 1 + 2
|
|
assert.Equal(t, "abc", Tail(p3))
|
|
}
|
|
|
|
func TestBiMapWithDifferentTypes(t *testing.T) {
|
|
p := MakePair(3.14, true)
|
|
p2 := MonadBiMap(p,
|
|
func(f float64) int { return int(f * 10) },
|
|
func(b bool) string {
|
|
if b {
|
|
return "yes"
|
|
}
|
|
return "no"
|
|
},
|
|
)
|
|
assert.Equal(t, 31, Head(p2))
|
|
assert.Equal(t, "yes", Tail(p2))
|
|
}
|
|
|
|
func TestSwapTwice(t *testing.T) {
|
|
p := MakePair("original", 999)
|
|
swapped := Swap(p)
|
|
swappedBack := Swap(swapped)
|
|
assert.Equal(t, "original", Head(swappedBack))
|
|
assert.Equal(t, 999, Tail(swappedBack))
|
|
}
|