1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-11-23 22:14:53 +02:00
Files
fp-go/v2/identity/identity_test.go
Carsten Leue 3385c705dc Implement v2 using type aliases (#141)
* fix: initial checkin of v2

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>

* fix: slowly migrate IO

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>

* fix: migrate MonadTraverseArray and TraverseArray

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>

* fix: migrate traversal

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>

* fix: complete migration of IO

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>

* fix: migrate ioeither

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>

* fix: refactorY

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>

* fix: next step in migration

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>

* fix: adjust IO generation code

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>

* fix: get rid of more IO methods

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>

* fix: get rid of more IO

* fix: convert iooption

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>

* fix: convert reader

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>

* fix: convert a bit of reader

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>

* fix: new build script

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>

* fix: cleanup

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>

* fix: reformat

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>

* fix: simplify

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>

* fix: some cleanup

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>

* fix: adjust Pair to Haskell semantic

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>

* fix: documentation and testcases

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>

* fix: some performance optimizations

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>

* fix: remove coverage

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>

* fix: better doc

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>

---------

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-06 09:27:00 +01:00

741 lines
18 KiB
Go

// Copyright (c) 2023 - 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 identity
import (
"fmt"
"testing"
F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/internal/utils"
T "github.com/IBM/fp-go/v2/tuple"
"github.com/stretchr/testify/assert"
)
func TestOf(t *testing.T) {
t.Run("wraps int", func(t *testing.T) {
result := Of(42)
assert.Equal(t, 42, result)
})
t.Run("wraps string", func(t *testing.T) {
result := Of("hello")
assert.Equal(t, "hello", result)
})
t.Run("wraps struct", func(t *testing.T) {
type Person struct{ Name string }
p := Person{Name: "Alice"}
result := Of(p)
assert.Equal(t, p, result)
})
}
func TestMap(t *testing.T) {
t.Run("transforms int", func(t *testing.T) {
result := F.Pipe1(1, Map(utils.Double))
assert.Equal(t, 2, result)
})
t.Run("transforms string", func(t *testing.T) {
result := F.Pipe1("hello", Map(func(s string) int {
return len(s)
}))
assert.Equal(t, 5, result)
})
t.Run("chains multiple maps", func(t *testing.T) {
result := F.Pipe2(
5,
Map(func(n int) int { return n * 2 }),
Map(func(n int) int { return n + 3 }),
)
assert.Equal(t, 13, result)
})
}
func TestMonadMap(t *testing.T) {
t.Run("transforms value", func(t *testing.T) {
result := MonadMap(10, func(n int) int { return n * 3 })
assert.Equal(t, 30, result)
})
t.Run("changes type", func(t *testing.T) {
result := MonadMap(42, func(n int) string {
return fmt.Sprintf("Number: %d", n)
})
assert.Equal(t, "Number: 42", result)
})
}
func TestMapTo(t *testing.T) {
t.Run("replaces with constant int", func(t *testing.T) {
result := F.Pipe1("ignored", MapTo[string, int](100))
assert.Equal(t, 100, result)
})
t.Run("replaces with constant string", func(t *testing.T) {
result := F.Pipe1(42, MapTo[int, string]("constant"))
assert.Equal(t, "constant", result)
})
}
func TestMonadMapTo(t *testing.T) {
t.Run("replaces value", func(t *testing.T) {
result := MonadMapTo("anything", 999)
assert.Equal(t, 999, result)
})
}
func TestChain(t *testing.T) {
t.Run("chains computation", func(t *testing.T) {
result := F.Pipe1(1, Chain(utils.Double))
assert.Equal(t, 2, result)
})
t.Run("chains multiple operations", func(t *testing.T) {
result := F.Pipe2(
10,
Chain(func(n int) int { return n * 2 }),
Chain(func(n int) int { return n + 5 }),
)
assert.Equal(t, 25, result)
})
t.Run("changes type", func(t *testing.T) {
result := F.Pipe1(5, Chain(func(n int) string {
return fmt.Sprintf("Value: %d", n)
}))
assert.Equal(t, "Value: 5", result)
})
}
func TestMonadChain(t *testing.T) {
t.Run("chains computation", func(t *testing.T) {
result := MonadChain(7, func(n int) int { return n * 7 })
assert.Equal(t, 49, result)
})
}
func TestChainFirst(t *testing.T) {
t.Run("executes but keeps original", func(t *testing.T) {
sideEffect := ""
result := F.Pipe1(
42,
ChainFirst(func(n int) string {
sideEffect = fmt.Sprintf("Processed: %d", n)
return sideEffect
}),
)
assert.Equal(t, 42, result)
assert.Equal(t, "Processed: 42", sideEffect)
})
t.Run("chains with other operations", func(t *testing.T) {
result := F.Pipe2(
10,
ChainFirst(func(n int) string { return "ignored" }),
Map(func(n int) int { return n * 2 }),
)
assert.Equal(t, 20, result)
})
}
func TestMonadChainFirst(t *testing.T) {
t.Run("keeps original value", func(t *testing.T) {
result := MonadChainFirst(100, func(n int) string {
return fmt.Sprintf("%d", n)
})
assert.Equal(t, 100, result)
})
}
func TestAp(t *testing.T) {
t.Run("applies function", func(t *testing.T) {
result := F.Pipe1(utils.Double, Ap[int, int](1))
assert.Equal(t, 2, result)
})
t.Run("applies curried function", func(t *testing.T) {
add := func(a int) func(int) int {
return func(b int) int { return a + b }
}
result := F.Pipe1(add(10), Ap[int, int](5))
assert.Equal(t, 15, result)
})
t.Run("changes type", func(t *testing.T) {
toString := func(n int) string {
return fmt.Sprintf("Number: %d", n)
}
result := F.Pipe1(toString, Ap[string, int](42))
assert.Equal(t, "Number: 42", result)
})
}
func TestMonadAp(t *testing.T) {
t.Run("applies function to value", func(t *testing.T) {
result := MonadAp(func(n int) int { return n * 3 }, 7)
assert.Equal(t, 21, result)
})
}
func TestFlap(t *testing.T) {
t.Run("flips application", func(t *testing.T) {
double := func(n int) int { return n * 2 }
result := F.Pipe1(double, Flap[int, int](5))
assert.Equal(t, 10, result)
})
t.Run("with multiple functions", func(t *testing.T) {
funcs := []func(int) int{
func(n int) int { return n * 2 },
func(n int) int { return n + 10 },
func(n int) int { return n * n },
}
results := make([]int, len(funcs))
for i, f := range funcs {
results[i] = Flap[int, int](5)(f)
}
assert.Equal(t, []int{10, 15, 25}, results)
})
}
func TestMonadFlap(t *testing.T) {
t.Run("applies value to function", func(t *testing.T) {
result := MonadFlap(func(n int) string {
return fmt.Sprintf("Value: %d", n)
}, 42)
assert.Equal(t, "Value: 42", result)
})
}
func TestDo(t *testing.T) {
t.Run("initializes context", func(t *testing.T) {
type State struct{ Value int }
result := Do(State{Value: 10})
assert.Equal(t, State{Value: 10}, result)
})
}
func TestBind(t *testing.T) {
t.Run("binds computation result", func(t *testing.T) {
type State struct {
X int
Y int
}
result := F.Pipe2(
Do(State{}),
Bind(
func(x int) func(State) State {
return func(s State) State {
s.X = x
return s
}
},
func(State) int { return 10 },
),
Bind(
func(y int) func(State) State {
return func(s State) State {
s.Y = y
return s
}
},
func(State) int { return 20 },
),
)
assert.Equal(t, State{X: 10, Y: 20}, result)
})
}
func TestLet(t *testing.T) {
t.Run("attaches computed value", func(t *testing.T) {
type State struct {
X int
Sum int
}
result := F.Pipe2(
Do(State{X: 5}),
Let(
func(sum int) func(State) State {
return func(s State) State {
s.Sum = sum
return s
}
},
func(s State) int { return s.X * 2 },
),
Map(func(s State) State {
s.Sum += 10
return s
}),
)
assert.Equal(t, State{X: 5, Sum: 20}, result)
})
}
func TestLetTo(t *testing.T) {
t.Run("attaches constant value", func(t *testing.T) {
type State struct {
Name string
}
result := F.Pipe1(
Do(State{}),
LetTo(
func(name string) func(State) State {
return func(s State) State {
s.Name = name
return s
}
},
"Alice",
),
)
assert.Equal(t, State{Name: "Alice"}, result)
})
}
func TestBindTo(t *testing.T) {
t.Run("initializes state from value", func(t *testing.T) {
type State struct{ Value int }
result := F.Pipe1(
42,
BindTo(func(v int) State {
return State{Value: v}
}),
)
assert.Equal(t, State{Value: 42}, result)
})
}
func TestApS(t *testing.T) {
t.Run("applies value in context", func(t *testing.T) {
type State struct {
X int
Y int
}
result := F.Pipe1(
Do(State{X: 10}),
ApS(
func(y int) func(State) State {
return func(s State) State {
s.Y = y
return s
}
},
20,
),
)
assert.Equal(t, State{X: 10, Y: 20}, result)
})
}
func TestSequenceT(t *testing.T) {
t.Run("SequenceT2", func(t *testing.T) {
result := SequenceT2(1, 2)
assert.Equal(t, T.MakeTuple2(1, 2), result)
})
t.Run("SequenceT3", func(t *testing.T) {
result := SequenceT3("a", "b", "c")
assert.Equal(t, T.MakeTuple3("a", "b", "c"), result)
})
t.Run("SequenceT4", func(t *testing.T) {
result := SequenceT4(1, 2, 3, 4)
assert.Equal(t, T.MakeTuple4(1, 2, 3, 4), result)
})
}
func TestSequenceTuple(t *testing.T) {
t.Run("SequenceTuple2", func(t *testing.T) {
tuple := T.MakeTuple2(10, 20)
result := SequenceTuple2(tuple)
assert.Equal(t, tuple, result)
})
t.Run("SequenceTuple3", func(t *testing.T) {
tuple := T.MakeTuple3(1, 2, 3)
result := SequenceTuple3(tuple)
assert.Equal(t, tuple, result)
})
}
func TestTraverseTuple(t *testing.T) {
t.Run("TraverseTuple2", func(t *testing.T) {
tuple := T.MakeTuple2(1, 2)
result := TraverseTuple2(
func(n int) int { return n * 2 },
func(n int) int { return n * 3 },
)(tuple)
assert.Equal(t, T.MakeTuple2(2, 6), result)
})
t.Run("TraverseTuple3", func(t *testing.T) {
tuple := T.MakeTuple3(1, 2, 3)
result := TraverseTuple3(
func(n int) int { return n + 10 },
func(n int) int { return n + 20 },
func(n int) int { return n + 30 },
)(tuple)
assert.Equal(t, T.MakeTuple3(11, 22, 33), result)
})
t.Run("TraverseTuple2 with type change", func(t *testing.T) {
tuple := T.MakeTuple2(5, 10)
result := TraverseTuple2(
func(n int) string { return fmt.Sprintf("A%d", n) },
func(n int) string { return fmt.Sprintf("B%d", n) },
)(tuple)
assert.Equal(t, T.MakeTuple2("A5", "B10"), result)
})
}
func TestMonad(t *testing.T) {
t.Run("monad interface", func(t *testing.T) {
m := Monad[int, string]()
// Test Of
value := m.Of(42)
assert.Equal(t, 42, value)
// Test Map
mapped := m.Map(func(n int) string {
return fmt.Sprintf("Number: %d", n)
})(value)
assert.Equal(t, "Number: 42", mapped)
// Test Chain
chained := m.Chain(func(n int) string {
return fmt.Sprintf("Value: %d", n)
})(value)
assert.Equal(t, "Value: 42", chained)
// Test Ap
applied := m.Ap(10)(func(n int) string {
return fmt.Sprintf("Result: %d", n)
})
assert.Equal(t, "Result: 10", applied)
})
}
// Test monad laws
func TestMonadLaws(t *testing.T) {
t.Run("left identity", func(t *testing.T) {
// Of(a).Chain(f) === f(a)
a := 42
f := func(n int) int { return n * 2 }
left := F.Pipe1(Of(a), Chain(f))
right := f(a)
assert.Equal(t, right, left)
})
t.Run("right identity", func(t *testing.T) {
// m.Chain(Of) === m
m := 42
result := F.Pipe1(m, Chain(Of[int]))
assert.Equal(t, m, result)
})
t.Run("associativity", func(t *testing.T) {
// m.Chain(f).Chain(g) === m.Chain(x => f(x).Chain(g))
m := 5
f := func(n int) int { return n * 2 }
g := func(n int) int { return n + 10 }
left := F.Pipe2(m, Chain(f), Chain(g))
right := F.Pipe1(m, Chain(func(x int) int {
return F.Pipe1(f(x), Chain(g))
}))
assert.Equal(t, right, left)
})
}
// Test functor laws
func TestFunctorLaws(t *testing.T) {
t.Run("identity", func(t *testing.T) {
// Map(id) === id
value := 42
result := F.Pipe1(value, Map(F.Identity[int]))
assert.Equal(t, value, result)
})
t.Run("composition", func(t *testing.T) {
// Map(f).Map(g) === Map(g ∘ f)
value := 5
f := func(n int) int { return n * 2 }
g := func(n int) int { return n + 10 }
left := F.Pipe2(value, Map(f), Map(g))
right := F.Pipe1(value, Map(F.Flow2(f, g)))
assert.Equal(t, right, left)
})
}
func TestSequenceT1(t *testing.T) {
t.Run("sequences single value", func(t *testing.T) {
result := SequenceT1(42)
assert.Equal(t, T.MakeTuple1(42), result)
})
}
func TestSequenceTuple1(t *testing.T) {
t.Run("sequences tuple1", func(t *testing.T) {
tuple := T.MakeTuple1("hello")
result := SequenceTuple1(tuple)
assert.Equal(t, tuple, result)
})
}
func TestTraverseTuple1(t *testing.T) {
t.Run("traverses tuple1", func(t *testing.T) {
tuple := T.MakeTuple1(5)
result := TraverseTuple1(func(n int) int { return n * 10 })(tuple)
assert.Equal(t, T.MakeTuple1(50), result)
})
}
func TestSequenceTuple4(t *testing.T) {
t.Run("sequences tuple4", func(t *testing.T) {
tuple := T.MakeTuple4(1, 2, 3, 4)
result := SequenceTuple4(tuple)
assert.Equal(t, tuple, result)
})
}
func TestTraverseTuple4(t *testing.T) {
t.Run("traverses tuple4", func(t *testing.T) {
tuple := T.MakeTuple4(1, 2, 3, 4)
result := TraverseTuple4(
func(n int) int { return n + 10 },
func(n int) int { return n + 20 },
func(n int) int { return n + 30 },
func(n int) int { return n + 40 },
)(tuple)
assert.Equal(t, T.MakeTuple4(11, 22, 33, 44), result)
})
}
func TestSequenceT5(t *testing.T) {
t.Run("sequences 5 values", func(t *testing.T) {
result := SequenceT5(1, 2, 3, 4, 5)
assert.Equal(t, T.MakeTuple5(1, 2, 3, 4, 5), result)
})
}
func TestSequenceTuple5(t *testing.T) {
t.Run("sequences tuple5", func(t *testing.T) {
tuple := T.MakeTuple5(1, 2, 3, 4, 5)
result := SequenceTuple5(tuple)
assert.Equal(t, tuple, result)
})
}
func TestTraverseTuple5(t *testing.T) {
t.Run("traverses tuple5", func(t *testing.T) {
tuple := T.MakeTuple5(1, 2, 3, 4, 5)
result := TraverseTuple5(
func(n int) int { return n * 1 },
func(n int) int { return n * 2 },
func(n int) int { return n * 3 },
func(n int) int { return n * 4 },
func(n int) int { return n * 5 },
)(tuple)
assert.Equal(t, T.MakeTuple5(1, 4, 9, 16, 25), result)
})
}
func TestSequenceT6(t *testing.T) {
t.Run("sequences 6 values", func(t *testing.T) {
result := SequenceT6(1, 2, 3, 4, 5, 6)
assert.Equal(t, T.MakeTuple6(1, 2, 3, 4, 5, 6), result)
})
}
func TestSequenceTuple6(t *testing.T) {
t.Run("sequences tuple6", func(t *testing.T) {
tuple := T.MakeTuple6(1, 2, 3, 4, 5, 6)
result := SequenceTuple6(tuple)
assert.Equal(t, tuple, result)
})
}
func TestTraverseTuple6(t *testing.T) {
t.Run("traverses tuple6", func(t *testing.T) {
tuple := T.MakeTuple6(1, 2, 3, 4, 5, 6)
result := TraverseTuple6(
func(n int) int { return n + 1 },
func(n int) int { return n + 2 },
func(n int) int { return n + 3 },
func(n int) int { return n + 4 },
func(n int) int { return n + 5 },
func(n int) int { return n + 6 },
)(tuple)
assert.Equal(t, T.MakeTuple6(2, 4, 6, 8, 10, 12), result)
})
}
func TestSequenceT7(t *testing.T) {
t.Run("sequences 7 values", func(t *testing.T) {
result := SequenceT7(1, 2, 3, 4, 5, 6, 7)
assert.Equal(t, T.MakeTuple7(1, 2, 3, 4, 5, 6, 7), result)
})
}
func TestSequenceTuple7(t *testing.T) {
t.Run("sequences tuple7", func(t *testing.T) {
tuple := T.MakeTuple7(1, 2, 3, 4, 5, 6, 7)
result := SequenceTuple7(tuple)
assert.Equal(t, tuple, result)
})
}
func TestTraverseTuple7(t *testing.T) {
t.Run("traverses tuple7", func(t *testing.T) {
tuple := T.MakeTuple7(1, 2, 3, 4, 5, 6, 7)
result := TraverseTuple7(
func(n int) int { return n * 10 },
func(n int) int { return n * 10 },
func(n int) int { return n * 10 },
func(n int) int { return n * 10 },
func(n int) int { return n * 10 },
func(n int) int { return n * 10 },
func(n int) int { return n * 10 },
)(tuple)
assert.Equal(t, T.MakeTuple7(10, 20, 30, 40, 50, 60, 70), result)
})
}
func TestSequenceT8(t *testing.T) {
t.Run("sequences 8 values", func(t *testing.T) {
result := SequenceT8(1, 2, 3, 4, 5, 6, 7, 8)
assert.Equal(t, T.MakeTuple8(1, 2, 3, 4, 5, 6, 7, 8), result)
})
}
func TestSequenceTuple8(t *testing.T) {
t.Run("sequences tuple8", func(t *testing.T) {
tuple := T.MakeTuple8(1, 2, 3, 4, 5, 6, 7, 8)
result := SequenceTuple8(tuple)
assert.Equal(t, tuple, result)
})
}
func TestTraverseTuple8(t *testing.T) {
t.Run("traverses tuple8", func(t *testing.T) {
tuple := T.MakeTuple8(1, 2, 3, 4, 5, 6, 7, 8)
result := TraverseTuple8(
func(n int) int { return n },
func(n int) int { return n },
func(n int) int { return n },
func(n int) int { return n },
func(n int) int { return n },
func(n int) int { return n },
func(n int) int { return n },
func(n int) int { return n },
)(tuple)
assert.Equal(t, tuple, result)
})
}
func TestSequenceT9(t *testing.T) {
t.Run("sequences 9 values", func(t *testing.T) {
result := SequenceT9(1, 2, 3, 4, 5, 6, 7, 8, 9)
assert.Equal(t, T.MakeTuple9(1, 2, 3, 4, 5, 6, 7, 8, 9), result)
})
}
func TestSequenceTuple9(t *testing.T) {
t.Run("sequences tuple9", func(t *testing.T) {
tuple := T.MakeTuple9(1, 2, 3, 4, 5, 6, 7, 8, 9)
result := SequenceTuple9(tuple)
assert.Equal(t, tuple, result)
})
}
func TestTraverseTuple9(t *testing.T) {
t.Run("traverses tuple9", func(t *testing.T) {
tuple := T.MakeTuple9(1, 2, 3, 4, 5, 6, 7, 8, 9)
result := TraverseTuple9(
func(n int) int { return n + 1 },
func(n int) int { return n + 1 },
func(n int) int { return n + 1 },
func(n int) int { return n + 1 },
func(n int) int { return n + 1 },
func(n int) int { return n + 1 },
func(n int) int { return n + 1 },
func(n int) int { return n + 1 },
func(n int) int { return n + 1 },
)(tuple)
assert.Equal(t, T.MakeTuple9(2, 3, 4, 5, 6, 7, 8, 9, 10), result)
})
}
func TestSequenceT10(t *testing.T) {
t.Run("sequences 10 values", func(t *testing.T) {
result := SequenceT10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
assert.Equal(t, T.MakeTuple10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), result)
})
}
func TestSequenceTuple10(t *testing.T) {
t.Run("sequences tuple10", func(t *testing.T) {
tuple := T.MakeTuple10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
result := SequenceTuple10(tuple)
assert.Equal(t, tuple, result)
})
}
func TestTraverseTuple10(t *testing.T) {
t.Run("traverses tuple10", func(t *testing.T) {
tuple := T.MakeTuple10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
result := TraverseTuple10(
func(n int) int { return n * 2 },
func(n int) int { return n * 2 },
func(n int) int { return n * 2 },
func(n int) int { return n * 2 },
func(n int) int { return n * 2 },
func(n int) int { return n * 2 },
func(n int) int { return n * 2 },
func(n int) int { return n * 2 },
func(n int) int { return n * 2 },
func(n int) int { return n * 2 },
)(tuple)
assert.Equal(t, T.MakeTuple10(2, 4, 6, 8, 10, 12, 14, 16, 18, 20), result)
})
}