2025-11-21 15:39:41 +01:00
|
|
|
// 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 readerresult
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"errors"
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
F "github.com/IBM/fp-go/v2/function"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type BenchContext struct {
|
|
|
|
|
Value int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var benchError = errors.New("benchmark error")
|
|
|
|
|
|
|
|
|
|
// Benchmark basic operations
|
|
|
|
|
|
|
|
|
|
func BenchmarkOf(b *testing.B) {
|
|
|
|
|
ctx := BenchContext{Value: 42}
|
|
|
|
|
b.ResetTimer()
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
|
rr := Of[BenchContext](i)
|
|
|
|
|
_ = rr(ctx)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkLeft(b *testing.B) {
|
|
|
|
|
ctx := BenchContext{Value: 42}
|
|
|
|
|
err := benchError
|
|
|
|
|
b.ResetTimer()
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
|
rr := Left[BenchContext, int](err)
|
|
|
|
|
_ = rr(ctx)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkMap(b *testing.B) {
|
|
|
|
|
ctx := BenchContext{Value: 42}
|
|
|
|
|
rr := Of[BenchContext](10)
|
|
|
|
|
double := func(x int) int { return x * 2 }
|
|
|
|
|
b.ResetTimer()
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
|
mapped := F.Pipe1(rr, Map[BenchContext](double))
|
|
|
|
|
_ = mapped(ctx)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkMapChain(b *testing.B) {
|
|
|
|
|
ctx := BenchContext{Value: 42}
|
|
|
|
|
rr := Of[BenchContext](1)
|
|
|
|
|
double := func(x int) int { return x * 2 }
|
|
|
|
|
b.ResetTimer()
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
|
result := F.Pipe3(
|
|
|
|
|
rr,
|
|
|
|
|
Map[BenchContext](double),
|
|
|
|
|
Map[BenchContext](double),
|
|
|
|
|
Map[BenchContext](double),
|
|
|
|
|
)
|
|
|
|
|
_ = result(ctx)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkChain(b *testing.B) {
|
|
|
|
|
ctx := BenchContext{Value: 42}
|
|
|
|
|
rr := Of[BenchContext](10)
|
|
|
|
|
addOne := func(x int) ReaderResult[BenchContext, int] {
|
|
|
|
|
return Of[BenchContext](x + 1)
|
|
|
|
|
}
|
|
|
|
|
b.ResetTimer()
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
|
chained := F.Pipe1(rr, Chain(addOne))
|
|
|
|
|
_ = chained(ctx)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkChainDeep(b *testing.B) {
|
|
|
|
|
ctx := BenchContext{Value: 42}
|
|
|
|
|
rr := Of[BenchContext](1)
|
|
|
|
|
addOne := func(x int) ReaderResult[BenchContext, int] {
|
|
|
|
|
return Of[BenchContext](x + 1)
|
|
|
|
|
}
|
|
|
|
|
b.ResetTimer()
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
|
result := F.Pipe5(
|
|
|
|
|
rr,
|
|
|
|
|
Chain(addOne),
|
|
|
|
|
Chain(addOne),
|
|
|
|
|
Chain(addOne),
|
|
|
|
|
Chain(addOne),
|
|
|
|
|
Chain(addOne),
|
|
|
|
|
)
|
|
|
|
|
_ = result(ctx)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkAp(b *testing.B) {
|
|
|
|
|
ctx := BenchContext{Value: 42}
|
|
|
|
|
fab := Of[BenchContext](func(x int) int { return x * 2 })
|
|
|
|
|
fa := Of[BenchContext](21)
|
|
|
|
|
b.ResetTimer()
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
|
result := MonadAp(fab, fa)
|
|
|
|
|
_ = result(ctx)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkSequenceT2(b *testing.B) {
|
|
|
|
|
ctx := BenchContext{Value: 42}
|
|
|
|
|
rr1 := Of[BenchContext](10)
|
|
|
|
|
rr2 := Of[BenchContext](20)
|
|
|
|
|
b.ResetTimer()
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
|
result := SequenceT2(rr1, rr2)
|
|
|
|
|
_ = result(ctx)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkSequenceT4(b *testing.B) {
|
|
|
|
|
ctx := BenchContext{Value: 42}
|
|
|
|
|
rr1 := Of[BenchContext](10)
|
|
|
|
|
rr2 := Of[BenchContext](20)
|
|
|
|
|
rr3 := Of[BenchContext](30)
|
|
|
|
|
rr4 := Of[BenchContext](40)
|
|
|
|
|
b.ResetTimer()
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
|
result := SequenceT4(rr1, rr2, rr3, rr4)
|
|
|
|
|
_ = result(ctx)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkDoNotation(b *testing.B) {
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
type State struct {
|
|
|
|
|
A int
|
|
|
|
|
B int
|
|
|
|
|
C int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
b.ResetTimer()
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
|
result := F.Pipe3(
|
|
|
|
|
Do[context.Context](State{}),
|
|
|
|
|
Bind(
|
|
|
|
|
func(a int) func(State) State {
|
|
|
|
|
return func(s State) State { s.A = a; return s }
|
|
|
|
|
},
|
|
|
|
|
func(s State) ReaderResult[context.Context, int] {
|
|
|
|
|
return Of[context.Context](10)
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
Bind(
|
|
|
|
|
func(b int) func(State) State {
|
|
|
|
|
return func(s State) State { s.B = b; return s }
|
|
|
|
|
},
|
|
|
|
|
func(s State) ReaderResult[context.Context, int] {
|
|
|
|
|
return Of[context.Context](s.A * 2)
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
Bind(
|
|
|
|
|
func(c int) func(State) State {
|
|
|
|
|
return func(s State) State { s.C = c; return s }
|
|
|
|
|
},
|
|
|
|
|
func(s State) ReaderResult[context.Context, int] {
|
|
|
|
|
return Of[context.Context](s.A + s.B)
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
_ = result(ctx)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkErrorPropagation(b *testing.B) {
|
|
|
|
|
ctx := BenchContext{Value: 42}
|
|
|
|
|
err := benchError
|
|
|
|
|
rr := Left[BenchContext, int](err)
|
|
|
|
|
double := func(x int) int { return x * 2 }
|
|
|
|
|
|
|
|
|
|
b.ResetTimer()
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
|
result := F.Pipe5(
|
|
|
|
|
rr,
|
|
|
|
|
Map[BenchContext](double),
|
|
|
|
|
Map[BenchContext](double),
|
|
|
|
|
Map[BenchContext](double),
|
|
|
|
|
Map[BenchContext](double),
|
|
|
|
|
Map[BenchContext](double),
|
|
|
|
|
)
|
|
|
|
|
_ = result(ctx)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkTraverseArray(b *testing.B) {
|
|
|
|
|
ctx := BenchContext{Value: 42}
|
|
|
|
|
arr := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
|
|
|
|
|
kleisli := func(x int) ReaderResult[BenchContext, int] {
|
|
|
|
|
return Of[BenchContext](x * 2)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
b.ResetTimer()
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
2025-11-24 17:28:48 +01:00
|
|
|
traversed := TraverseArray(kleisli)
|
2025-11-21 15:39:41 +01:00
|
|
|
result := traversed(arr)
|
|
|
|
|
_ = result(ctx)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func BenchmarkSequenceArray(b *testing.B) {
|
|
|
|
|
ctx := BenchContext{Value: 42}
|
|
|
|
|
arr := []ReaderResult[BenchContext, int]{
|
|
|
|
|
Of[BenchContext](1),
|
|
|
|
|
Of[BenchContext](2),
|
|
|
|
|
Of[BenchContext](3),
|
|
|
|
|
Of[BenchContext](4),
|
|
|
|
|
Of[BenchContext](5),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
b.ResetTimer()
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
|
result := SequenceArray(arr)
|
|
|
|
|
_ = result(ctx)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Real-world scenario benchmarks
|
|
|
|
|
|
|
|
|
|
func BenchmarkRealWorldPipeline(b *testing.B) {
|
|
|
|
|
type Config struct {
|
|
|
|
|
Multiplier int
|
|
|
|
|
Offset int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx := Config{Multiplier: 5, Offset: 10}
|
|
|
|
|
|
|
|
|
|
type State struct {
|
|
|
|
|
Input int
|
|
|
|
|
Result int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getMultiplier := func(cfg Config) int { return cfg.Multiplier }
|
|
|
|
|
getOffset := func(cfg Config) int { return cfg.Offset }
|
|
|
|
|
|
|
|
|
|
b.ResetTimer()
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
|
step1 := Bind(
|
|
|
|
|
func(m int) func(State) State {
|
|
|
|
|
return func(s State) State { s.Result = s.Input * m; return s }
|
|
|
|
|
},
|
|
|
|
|
func(s State) ReaderResult[Config, int] {
|
|
|
|
|
return Asks(getMultiplier)
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
step2 := Bind(
|
|
|
|
|
func(off int) func(State) State {
|
|
|
|
|
return func(s State) State { s.Result += off; return s }
|
|
|
|
|
},
|
|
|
|
|
func(s State) ReaderResult[Config, int] {
|
|
|
|
|
return Asks(getOffset)
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
result := F.Pipe3(
|
|
|
|
|
Do[Config](State{Input: 10}),
|
|
|
|
|
step1,
|
|
|
|
|
step2,
|
|
|
|
|
Map[Config](func(s State) int { return s.Result }),
|
|
|
|
|
)
|
|
|
|
|
_ = result(ctx)
|
|
|
|
|
}
|
|
|
|
|
}
|