mirror of
https://github.com/IBM/fp-go.git
synced 2025-11-23 22:14:53 +02:00
506 lines
11 KiB
Go
506 lines
11 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 lazy
|
||
|
|
|
||
|
|
import (
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
EQ "github.com/IBM/fp-go/v2/eq"
|
||
|
|
F "github.com/IBM/fp-go/v2/function"
|
||
|
|
"github.com/IBM/fp-go/v2/internal/utils"
|
||
|
|
M "github.com/IBM/fp-go/v2/monoid"
|
||
|
|
L "github.com/IBM/fp-go/v2/optics/lens"
|
||
|
|
"github.com/stretchr/testify/assert"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestOf(t *testing.T) {
|
||
|
|
result := Of(42)
|
||
|
|
assert.Equal(t, 42, result())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestFromLazy(t *testing.T) {
|
||
|
|
original := func() int { return 42 }
|
||
|
|
wrapped := FromLazy(original)
|
||
|
|
assert.Equal(t, 42, wrapped())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestFromImpure(t *testing.T) {
|
||
|
|
counter := 0
|
||
|
|
impure := func() {
|
||
|
|
counter++
|
||
|
|
}
|
||
|
|
lazy := FromImpure(impure)
|
||
|
|
lazy()
|
||
|
|
assert.Equal(t, 1, counter)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestMonadOf(t *testing.T) {
|
||
|
|
result := MonadOf(42)
|
||
|
|
assert.Equal(t, 42, result())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestMonadMap(t *testing.T) {
|
||
|
|
result := MonadMap(Of(5), func(x int) int { return x * 2 })
|
||
|
|
assert.Equal(t, 10, result())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestMonadMapTo(t *testing.T) {
|
||
|
|
result := MonadMapTo(Of("ignored"), 42)
|
||
|
|
assert.Equal(t, 42, result())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestMapTo(t *testing.T) {
|
||
|
|
mapper := MapTo[string](42)
|
||
|
|
result := mapper(Of("ignored"))
|
||
|
|
assert.Equal(t, 42, result())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestMonadChain(t *testing.T) {
|
||
|
|
result := MonadChain(Of(5), func(x int) Lazy[int] {
|
||
|
|
return Of(x * 2)
|
||
|
|
})
|
||
|
|
assert.Equal(t, 10, result())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestMonadChainFirst(t *testing.T) {
|
||
|
|
result := MonadChainFirst(Of(5), func(x int) Lazy[string] {
|
||
|
|
return Of("ignored")
|
||
|
|
})
|
||
|
|
assert.Equal(t, 5, result())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestChainFirst(t *testing.T) {
|
||
|
|
chainer := ChainFirst(func(x int) Lazy[string] {
|
||
|
|
return Of("ignored")
|
||
|
|
})
|
||
|
|
result := chainer(Of(5))
|
||
|
|
assert.Equal(t, 5, result())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestMonadChainTo(t *testing.T) {
|
||
|
|
result := MonadChainTo(Of(5), Of(10))
|
||
|
|
assert.Equal(t, 10, result())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestChainTo(t *testing.T) {
|
||
|
|
chainer := ChainTo[int](Of(10))
|
||
|
|
result := chainer(Of(5))
|
||
|
|
assert.Equal(t, 10, result())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestMonadAp(t *testing.T) {
|
||
|
|
lazyFunc := Of(func(x int) int { return x * 2 })
|
||
|
|
lazyValue := Of(5)
|
||
|
|
result := MonadAp(lazyFunc, lazyValue)
|
||
|
|
assert.Equal(t, 10, result())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestMonadApFirst(t *testing.T) {
|
||
|
|
result := MonadApFirst(Of(5), Of(10))
|
||
|
|
assert.Equal(t, 5, result())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestMonadApSecond(t *testing.T) {
|
||
|
|
result := MonadApSecond(Of(5), Of(10))
|
||
|
|
assert.Equal(t, 10, result())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestNow(t *testing.T) {
|
||
|
|
before := time.Now()
|
||
|
|
result := Now()
|
||
|
|
after := time.Now()
|
||
|
|
|
||
|
|
assert.True(t, result.After(before) || result.Equal(before))
|
||
|
|
assert.True(t, result.Before(after) || result.Equal(after))
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestDefer(t *testing.T) {
|
||
|
|
counter := 0
|
||
|
|
deferred := Defer(func() Lazy[int] {
|
||
|
|
counter++
|
||
|
|
return Of(counter)
|
||
|
|
})
|
||
|
|
|
||
|
|
// First execution
|
||
|
|
result1 := deferred()
|
||
|
|
assert.Equal(t, 1, result1)
|
||
|
|
|
||
|
|
// Second execution should generate a new computation
|
||
|
|
result2 := deferred()
|
||
|
|
assert.Equal(t, 2, result2)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestDo(t *testing.T) {
|
||
|
|
type State struct {
|
||
|
|
Value int
|
||
|
|
}
|
||
|
|
result := Do(State{Value: 42})
|
||
|
|
assert.Equal(t, State{Value: 42}, result())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLet(t *testing.T) {
|
||
|
|
type State struct {
|
||
|
|
Value int
|
||
|
|
}
|
||
|
|
|
||
|
|
result := F.Pipe2(
|
||
|
|
Do(State{}),
|
||
|
|
Let(
|
||
|
|
func(v int) func(State) State {
|
||
|
|
return func(s State) State { s.Value = v; return s }
|
||
|
|
},
|
||
|
|
func(s State) int { return 42 },
|
||
|
|
),
|
||
|
|
Map(func(s State) int { return s.Value }),
|
||
|
|
)
|
||
|
|
|
||
|
|
assert.Equal(t, 42, result())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLetTo(t *testing.T) {
|
||
|
|
type State struct {
|
||
|
|
Value int
|
||
|
|
}
|
||
|
|
|
||
|
|
result := F.Pipe2(
|
||
|
|
Do(State{}),
|
||
|
|
LetTo(
|
||
|
|
func(v int) func(State) State {
|
||
|
|
return func(s State) State { s.Value = v; return s }
|
||
|
|
},
|
||
|
|
42,
|
||
|
|
),
|
||
|
|
Map(func(s State) int { return s.Value }),
|
||
|
|
)
|
||
|
|
|
||
|
|
assert.Equal(t, 42, result())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBindTo(t *testing.T) {
|
||
|
|
type State struct {
|
||
|
|
Value int
|
||
|
|
}
|
||
|
|
|
||
|
|
result := F.Pipe2(
|
||
|
|
Of(42),
|
||
|
|
BindTo(func(v int) State { return State{Value: v} }),
|
||
|
|
Map(func(s State) int { return s.Value }),
|
||
|
|
)
|
||
|
|
|
||
|
|
assert.Equal(t, 42, result())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBindL(t *testing.T) {
|
||
|
|
type Config struct {
|
||
|
|
Port int
|
||
|
|
}
|
||
|
|
type State struct {
|
||
|
|
Config Config
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create a lens manually
|
||
|
|
configLens := L.MakeLens(
|
||
|
|
func(s State) Config { return s.Config },
|
||
|
|
func(s State, cfg Config) State { s.Config = cfg; return s },
|
||
|
|
)
|
||
|
|
|
||
|
|
result := F.Pipe2(
|
||
|
|
Do(State{Config: Config{Port: 8080}}),
|
||
|
|
BindL(configLens, func(cfg Config) Lazy[Config] {
|
||
|
|
return Of(Config{Port: cfg.Port + 1})
|
||
|
|
}),
|
||
|
|
Map(func(s State) int { return s.Config.Port }),
|
||
|
|
)
|
||
|
|
|
||
|
|
assert.Equal(t, 8081, result())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLetL(t *testing.T) {
|
||
|
|
type Config struct {
|
||
|
|
Port int
|
||
|
|
}
|
||
|
|
type State struct {
|
||
|
|
Config Config
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create a lens manually
|
||
|
|
configLens := L.MakeLens(
|
||
|
|
func(s State) Config { return s.Config },
|
||
|
|
func(s State, cfg Config) State { s.Config = cfg; return s },
|
||
|
|
)
|
||
|
|
|
||
|
|
result := F.Pipe2(
|
||
|
|
Do(State{Config: Config{Port: 8080}}),
|
||
|
|
LetL(configLens, func(cfg Config) Config {
|
||
|
|
return Config{Port: cfg.Port + 1}
|
||
|
|
}),
|
||
|
|
Map(func(s State) int { return s.Config.Port }),
|
||
|
|
)
|
||
|
|
|
||
|
|
assert.Equal(t, 8081, result())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLetToL(t *testing.T) {
|
||
|
|
type Config struct {
|
||
|
|
Port int
|
||
|
|
}
|
||
|
|
type State struct {
|
||
|
|
Config Config
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create a lens manually
|
||
|
|
configLens := L.MakeLens(
|
||
|
|
func(s State) Config { return s.Config },
|
||
|
|
func(s State, cfg Config) State { s.Config = cfg; return s },
|
||
|
|
)
|
||
|
|
|
||
|
|
result := F.Pipe2(
|
||
|
|
Do(State{}),
|
||
|
|
LetToL(configLens, Config{Port: 8080}),
|
||
|
|
Map(func(s State) int { return s.Config.Port }),
|
||
|
|
)
|
||
|
|
|
||
|
|
assert.Equal(t, 8080, result())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestApSL(t *testing.T) {
|
||
|
|
type Config struct {
|
||
|
|
Port int
|
||
|
|
}
|
||
|
|
type State struct {
|
||
|
|
Config Config
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create a lens manually
|
||
|
|
configLens := L.MakeLens(
|
||
|
|
func(s State) Config { return s.Config },
|
||
|
|
func(s State, cfg Config) State { s.Config = cfg; return s },
|
||
|
|
)
|
||
|
|
|
||
|
|
result := F.Pipe2(
|
||
|
|
Do(State{}),
|
||
|
|
ApSL(configLens, Of(Config{Port: 8080})),
|
||
|
|
Map(func(s State) int { return s.Config.Port }),
|
||
|
|
)
|
||
|
|
|
||
|
|
assert.Equal(t, 8080, result())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestSequenceT1(t *testing.T) {
|
||
|
|
result := SequenceT1(Of(42))
|
||
|
|
tuple := result()
|
||
|
|
assert.Equal(t, 42, tuple.F1)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestSequenceT2(t *testing.T) {
|
||
|
|
result := SequenceT2(Of(42), Of("hello"))
|
||
|
|
tuple := result()
|
||
|
|
assert.Equal(t, 42, tuple.F1)
|
||
|
|
assert.Equal(t, "hello", tuple.F2)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestSequenceT3(t *testing.T) {
|
||
|
|
result := SequenceT3(Of(42), Of("hello"), Of(true))
|
||
|
|
tuple := result()
|
||
|
|
assert.Equal(t, 42, tuple.F1)
|
||
|
|
assert.Equal(t, "hello", tuple.F2)
|
||
|
|
assert.Equal(t, true, tuple.F3)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestSequenceT4(t *testing.T) {
|
||
|
|
result := SequenceT4(Of(42), Of("hello"), Of(true), Of(3.14))
|
||
|
|
tuple := result()
|
||
|
|
assert.Equal(t, 42, tuple.F1)
|
||
|
|
assert.Equal(t, "hello", tuple.F2)
|
||
|
|
assert.Equal(t, true, tuple.F3)
|
||
|
|
assert.Equal(t, 3.14, tuple.F4)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestTraverseArray(t *testing.T) {
|
||
|
|
numbers := []int{1, 2, 3}
|
||
|
|
result := F.Pipe1(
|
||
|
|
numbers,
|
||
|
|
TraverseArray(func(x int) Lazy[int] {
|
||
|
|
return Of(x * 2)
|
||
|
|
}),
|
||
|
|
)
|
||
|
|
assert.Equal(t, []int{2, 4, 6}, result())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestTraverseArrayWithIndex(t *testing.T) {
|
||
|
|
numbers := []int{10, 20, 30}
|
||
|
|
result := F.Pipe1(
|
||
|
|
numbers,
|
||
|
|
TraverseArrayWithIndex(func(i int, x int) Lazy[int] {
|
||
|
|
return Of(x + i)
|
||
|
|
}),
|
||
|
|
)
|
||
|
|
assert.Equal(t, []int{10, 21, 32}, result())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestSequenceArray(t *testing.T) {
|
||
|
|
lazies := []Lazy[int]{Of(1), Of(2), Of(3)}
|
||
|
|
result := SequenceArray(lazies)
|
||
|
|
assert.Equal(t, []int{1, 2, 3}, result())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestMonadTraverseArray(t *testing.T) {
|
||
|
|
numbers := []int{1, 2, 3}
|
||
|
|
result := MonadTraverseArray(numbers, func(x int) Lazy[int] {
|
||
|
|
return Of(x * 2)
|
||
|
|
})
|
||
|
|
assert.Equal(t, []int{2, 4, 6}, result())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestTraverseRecord(t *testing.T) {
|
||
|
|
record := map[string]int{"a": 1, "b": 2}
|
||
|
|
result := F.Pipe1(
|
||
|
|
record,
|
||
|
|
TraverseRecord[string](func(x int) Lazy[int] {
|
||
|
|
return Of(x * 2)
|
||
|
|
}),
|
||
|
|
)
|
||
|
|
resultMap := result()
|
||
|
|
assert.Equal(t, 2, resultMap["a"])
|
||
|
|
assert.Equal(t, 4, resultMap["b"])
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestTraverseRecordWithIndex(t *testing.T) {
|
||
|
|
record := map[string]int{"a": 10, "b": 20}
|
||
|
|
result := F.Pipe1(
|
||
|
|
record,
|
||
|
|
TraverseRecordWithIndex(func(k string, x int) Lazy[int] {
|
||
|
|
if k == "a" {
|
||
|
|
return Of(x + 1)
|
||
|
|
}
|
||
|
|
return Of(x + 2)
|
||
|
|
}),
|
||
|
|
)
|
||
|
|
resultMap := result()
|
||
|
|
assert.Equal(t, 11, resultMap["a"])
|
||
|
|
assert.Equal(t, 22, resultMap["b"])
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestSequenceRecord(t *testing.T) {
|
||
|
|
record := map[string]Lazy[int]{
|
||
|
|
"a": Of(1),
|
||
|
|
"b": Of(2),
|
||
|
|
}
|
||
|
|
result := SequenceRecord(record)
|
||
|
|
resultMap := result()
|
||
|
|
assert.Equal(t, 1, resultMap["a"])
|
||
|
|
assert.Equal(t, 2, resultMap["b"])
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestMonadTraverseRecord(t *testing.T) {
|
||
|
|
record := map[string]int{"a": 1, "b": 2}
|
||
|
|
result := MonadTraverseRecord(record, func(x int) Lazy[int] {
|
||
|
|
return Of(x * 2)
|
||
|
|
})
|
||
|
|
resultMap := result()
|
||
|
|
assert.Equal(t, 2, resultMap["a"])
|
||
|
|
assert.Equal(t, 4, resultMap["b"])
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestApplySemigroup(t *testing.T) {
|
||
|
|
sg := ApplySemigroup(M.MakeMonoid(
|
||
|
|
func(a, b int) int { return a + b },
|
||
|
|
0,
|
||
|
|
))
|
||
|
|
|
||
|
|
result := sg.Concat(Of(5), Of(10))
|
||
|
|
assert.Equal(t, 15, result())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestApplicativeMonoid(t *testing.T) {
|
||
|
|
mon := ApplicativeMonoid(M.MakeMonoid(
|
||
|
|
func(a, b int) int { return a + b },
|
||
|
|
0,
|
||
|
|
))
|
||
|
|
|
||
|
|
// Test Empty
|
||
|
|
empty := mon.Empty()
|
||
|
|
assert.Equal(t, 0, empty())
|
||
|
|
|
||
|
|
// Test Concat
|
||
|
|
result := mon.Concat(Of(5), Of(10))
|
||
|
|
assert.Equal(t, 15, result())
|
||
|
|
|
||
|
|
// Test identity laws
|
||
|
|
left := mon.Concat(mon.Empty(), Of(5))
|
||
|
|
assert.Equal(t, 5, left())
|
||
|
|
|
||
|
|
right := mon.Concat(Of(5), mon.Empty())
|
||
|
|
assert.Equal(t, 5, right())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestEq(t *testing.T) {
|
||
|
|
eq := Eq(EQ.FromEquals(func(a, b int) bool { return a == b }))
|
||
|
|
|
||
|
|
assert.True(t, eq.Equals(Of(42), Of(42)))
|
||
|
|
assert.False(t, eq.Equals(Of(42), Of(43)))
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestComplexDoNotation(t *testing.T) {
|
||
|
|
// Test a more complex do-notation scenario
|
||
|
|
result := F.Pipe3(
|
||
|
|
Do(utils.Empty),
|
||
|
|
Bind(utils.SetLastName, func(s utils.Initial) Lazy[string] {
|
||
|
|
return Of("Doe")
|
||
|
|
}),
|
||
|
|
Bind(utils.SetGivenName, func(s utils.WithLastName) Lazy[string] {
|
||
|
|
return Of("John")
|
||
|
|
}),
|
||
|
|
Map(utils.GetFullName),
|
||
|
|
)
|
||
|
|
|
||
|
|
assert.Equal(t, "John Doe", result())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestChainComposition(t *testing.T) {
|
||
|
|
// Test chaining multiple operations
|
||
|
|
double := func(x int) Lazy[int] {
|
||
|
|
return Of(x * 2)
|
||
|
|
}
|
||
|
|
|
||
|
|
addTen := func(x int) Lazy[int] {
|
||
|
|
return Of(x + 10)
|
||
|
|
}
|
||
|
|
|
||
|
|
result := F.Pipe2(
|
||
|
|
Of(5),
|
||
|
|
Chain(double),
|
||
|
|
Chain(addTen),
|
||
|
|
)
|
||
|
|
|
||
|
|
assert.Equal(t, 20, result())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestMapComposition(t *testing.T) {
|
||
|
|
// Test mapping multiple transformations
|
||
|
|
result := F.Pipe3(
|
||
|
|
Of(5),
|
||
|
|
Map(func(x int) int { return x * 2 }),
|
||
|
|
Map(func(x int) int { return x + 10 }),
|
||
|
|
Map(func(x int) int { return x }),
|
||
|
|
)
|
||
|
|
|
||
|
|
assert.Equal(t, 20, result())
|
||
|
|
}
|
||
|
|
|
||
|
|
// Made with Bob
|