mirror of
https://github.com/IBM/fp-go.git
synced 2025-11-23 22:14:53 +02:00
fix: run benchmarks
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,3 +3,4 @@ fp-go
|
||||
main.exe
|
||||
build/
|
||||
.idea
|
||||
*.exe
|
||||
@@ -21,8 +21,8 @@ import (
|
||||
|
||||
type (
|
||||
either struct {
|
||||
isLeft bool
|
||||
value any
|
||||
isLeft bool
|
||||
}
|
||||
|
||||
// Either defines a data structure that logically holds either an E or an A. The flag discriminates the cases
|
||||
@@ -73,12 +73,12 @@ func IsRight[E, A any](val Either[E, A]) bool {
|
||||
|
||||
// Left creates a new instance of an [Either] representing the left value.
|
||||
func Left[A, E any](value E) Either[E, A] {
|
||||
return Either[E, A]{true, value}
|
||||
return Either[E, A]{value, true}
|
||||
}
|
||||
|
||||
// Right creates a new instance of an [Either] representing the right value.
|
||||
func Right[E, A any](value A) Either[E, A] {
|
||||
return Either[E, A]{false, value}
|
||||
return Either[E, A]{value, false}
|
||||
}
|
||||
|
||||
// MonadFold extracts the values from an [Either] by invoking the [onLeft] callback or the [onRight] callback depending on the case
|
||||
@@ -94,8 +94,7 @@ func Unwrap[E, A any](ma Either[E, A]) (A, E) {
|
||||
if ma.isLeft {
|
||||
var a A
|
||||
return a, ma.value.(E)
|
||||
} else {
|
||||
var e E
|
||||
return ma.value.(A), e
|
||||
}
|
||||
var e E
|
||||
return ma.value.(A), e
|
||||
}
|
||||
|
||||
374
v2/context/readerioeither/BENCHMARKS.md
Normal file
374
v2/context/readerioeither/BENCHMARKS.md
Normal file
@@ -0,0 +1,374 @@
|
||||
# ReaderIOEither Benchmarks
|
||||
|
||||
This document describes the benchmark suite for the `context/readerioeither` package and how to interpret the results to identify performance bottlenecks.
|
||||
|
||||
## Running Benchmarks
|
||||
|
||||
To run all benchmarks:
|
||||
```bash
|
||||
cd context/readerioeither
|
||||
go test -bench=. -benchmem
|
||||
```
|
||||
|
||||
To run specific benchmarks:
|
||||
```bash
|
||||
go test -bench=BenchmarkMap -benchmem
|
||||
go test -bench=BenchmarkChain -benchmem
|
||||
go test -bench=BenchmarkApPar -benchmem
|
||||
```
|
||||
|
||||
To run with more iterations for stable results:
|
||||
```bash
|
||||
go test -bench=. -benchmem -benchtime=100000x
|
||||
```
|
||||
|
||||
## Benchmark Categories
|
||||
|
||||
### 1. Core Constructors
|
||||
- `BenchmarkLeft` - Creating Left (error) values (~64ns, 2 allocs)
|
||||
- `BenchmarkRight` - Creating Right (success) values (~64ns, 2 allocs)
|
||||
- `BenchmarkOf` - Creating Right values via Of (~47ns, 2 allocs)
|
||||
|
||||
**Key Insights:**
|
||||
- All constructors allocate 2 times (64B total)
|
||||
- `Of` is slightly faster than `Right` due to inlining
|
||||
- Construction is very fast, suitable for hot paths
|
||||
|
||||
### 2. Conversion Operations
|
||||
- `BenchmarkFromEither_Right/Left` - Converting Either to ReaderIOEither (~70ns, 2 allocs)
|
||||
- `BenchmarkFromIO` - Converting IO to ReaderIOEither (~78ns, 3 allocs)
|
||||
- `BenchmarkFromIOEither_Right/Left` - Converting IOEither (~23ns, 1 alloc)
|
||||
|
||||
**Key Insights:**
|
||||
- FromIOEither is the fastest conversion (~23ns)
|
||||
- FromIO has an extra allocation due to wrapping
|
||||
- All conversions are lightweight
|
||||
|
||||
### 3. Execution Operations
|
||||
- `BenchmarkExecute_Right` - Executing Right computation (~37ns, 1 alloc)
|
||||
- `BenchmarkExecute_Left` - Executing Left computation (~48ns, 1 alloc)
|
||||
- `BenchmarkExecute_WithContext` - Executing with context (~42ns, 1 alloc)
|
||||
|
||||
**Key Insights:**
|
||||
- Execution is very fast with minimal allocations
|
||||
- Left path is slightly slower due to error handling
|
||||
- Context overhead is minimal (~5ns)
|
||||
|
||||
### 4. Functor Operations (Map)
|
||||
- `BenchmarkMonadMap_Right/Left` - Direct map (~135ns, 5 allocs)
|
||||
- `BenchmarkMap_Right/Left` - Curried map (~24ns, 1 alloc)
|
||||
- `BenchmarkMapTo_Right` - Replacing with constant (~69ns, 1 alloc)
|
||||
|
||||
**Key Insights:**
|
||||
- **Bottleneck:** MonadMap has 5 allocations (128B)
|
||||
- Curried Map is ~5x faster with fewer allocations
|
||||
- **Recommendation:** Use curried `Map` instead of `MonadMap`
|
||||
|
||||
### 5. Monad Operations (Chain)
|
||||
- `BenchmarkMonadChain_Right/Left` - Direct chain (~190ns, 6 allocs)
|
||||
- `BenchmarkChain_Right/Left` - Curried chain (~28ns, 1 alloc)
|
||||
- `BenchmarkChainFirst_Right/Left` - Chain preserving original (~27ns, 1 alloc)
|
||||
- `BenchmarkFlatten_Right/Left` - Removing nesting (~147ns, 7 allocs)
|
||||
|
||||
**Key Insights:**
|
||||
- **Bottleneck:** MonadChain has 6 allocations (160B)
|
||||
- Curried Chain is ~7x faster
|
||||
- ChainFirst is as fast as Chain
|
||||
- **Bottleneck:** Flatten has 7 allocations
|
||||
- **Recommendation:** Use curried `Chain` instead of `MonadChain`
|
||||
|
||||
### 6. Applicative Operations (Ap)
|
||||
- `BenchmarkMonadApSeq_RightRight` - Sequential apply (~281ns, 8 allocs)
|
||||
- `BenchmarkMonadApPar_RightRight` - Parallel apply (~49ns, 3 allocs)
|
||||
- `BenchmarkExecuteApSeq_RightRight` - Executing sequential (~1403ns, 8 allocs)
|
||||
- `BenchmarkExecuteApPar_RightRight` - Executing parallel (~5606ns, 61 allocs)
|
||||
|
||||
**Key Insights:**
|
||||
- **Major Bottleneck:** Parallel execution has 61 allocations (1896B)
|
||||
- Construction of ApPar is fast (~49ns), but execution is expensive
|
||||
- Sequential execution is faster for simple operations (~1.4μs vs ~5.6μs)
|
||||
- **Recommendation:** Use ApSeq for simple operations, ApPar only for truly independent, expensive computations
|
||||
- Parallel overhead includes context management and goroutine coordination
|
||||
|
||||
### 7. Alternative Operations
|
||||
- `BenchmarkAlt_RightRight/LeftRight` - Providing alternatives (~210-344ns, 6 allocs)
|
||||
- `BenchmarkOrElse_Right/Left` - Recovery from Left (~40-52ns, 2 allocs)
|
||||
|
||||
**Key Insights:**
|
||||
- Alt has significant overhead (6 allocations)
|
||||
- OrElse is much more efficient for error recovery
|
||||
- **Recommendation:** Prefer OrElse over Alt when possible
|
||||
|
||||
### 8. Chain Operations with Different Types
|
||||
- `BenchmarkChainEitherK_Right/Left` - Chaining Either (~25ns, 1 alloc)
|
||||
- `BenchmarkChainIOK_Right/Left` - Chaining IO (~55ns, 1 alloc)
|
||||
- `BenchmarkChainIOEitherK_Right/Left` - Chaining IOEither (~53ns, 1 alloc)
|
||||
|
||||
**Key Insights:**
|
||||
- All chain-K operations are efficient
|
||||
- ChainEitherK is fastest (pure transformation)
|
||||
- ChainIOK and ChainIOEitherK have similar performance
|
||||
|
||||
### 9. Context Operations
|
||||
- `BenchmarkAsk` - Accessing context (~52ns, 3 allocs)
|
||||
- `BenchmarkDefer` - Lazy generation (~34ns, 1 alloc)
|
||||
- `BenchmarkMemoize` - Caching results (~82ns, 4 allocs)
|
||||
|
||||
**Key Insights:**
|
||||
- Ask has 3 allocations for context wrapping
|
||||
- Defer is lightweight
|
||||
- Memoize has overhead but pays off for expensive computations
|
||||
|
||||
### 10. Delay Operations
|
||||
- `BenchmarkDelay_Construction` - Creating delayed computation (~19ns, 1 alloc)
|
||||
- `BenchmarkTimer_Construction` - Creating timer (~92ns, 3 allocs)
|
||||
|
||||
**Key Insights:**
|
||||
- Delay construction is very cheap
|
||||
- Timer has additional overhead for time operations
|
||||
|
||||
### 11. TryCatch Operations
|
||||
- `BenchmarkTryCatch_Success/Error` - Creating TryCatch (~33ns, 1 alloc)
|
||||
- `BenchmarkExecuteTryCatch_Success/Error` - Executing TryCatch (~3ns, 0 allocs)
|
||||
|
||||
**Key Insights:**
|
||||
- TryCatch construction is cheap
|
||||
- Execution is extremely fast with zero allocations
|
||||
- Excellent for wrapping Go error-returning functions
|
||||
|
||||
### 12. Pipeline Operations
|
||||
- `BenchmarkPipeline_Map_Right/Left` - Single Map in pipeline (~200-306ns, 9 allocs)
|
||||
- `BenchmarkPipeline_Chain_Right/Left` - Single Chain in pipeline (~155-217ns, 7 allocs)
|
||||
- `BenchmarkPipeline_Complex_Right/Left` - Multiple operations (~777-1039ns, 25 allocs)
|
||||
- `BenchmarkExecutePipeline_Complex_Right` - Executing complex pipeline (~533ns, 10 allocs)
|
||||
|
||||
**Key Insights:**
|
||||
- **Major Bottleneck:** Pipeline operations allocate heavily
|
||||
- Single Map: ~200ns with 9 allocations (224B)
|
||||
- Complex pipeline: ~900ns with 25 allocations (640B)
|
||||
- **Recommendation:** Avoid F.Pipe in hot paths, use direct function calls
|
||||
|
||||
### 13. Do-Notation Operations
|
||||
- `BenchmarkDo` - Creating empty context (~45ns, 2 allocs)
|
||||
- `BenchmarkBind_Right` - Binding values (~25ns, 1 alloc)
|
||||
- `BenchmarkLet_Right` - Pure computations (~23ns, 1 alloc)
|
||||
- `BenchmarkApS_Right` - Applicative binding (~99ns, 4 allocs)
|
||||
|
||||
**Key Insights:**
|
||||
- Do-notation operations are efficient
|
||||
- Bind and Let are very fast
|
||||
- ApS has more overhead (4 allocations)
|
||||
- Much better than either package's Bind (~130ns vs ~25ns)
|
||||
|
||||
### 14. Traverse Operations
|
||||
- `BenchmarkTraverseArray_Empty` - Empty array (~689ns, 13 allocs)
|
||||
- `BenchmarkTraverseArray_Small` - 3 elements (~1971ns, 37 allocs)
|
||||
- `BenchmarkTraverseArray_Medium` - 10 elements (~4386ns, 93 allocs)
|
||||
- `BenchmarkTraverseArraySeq_Small` - Sequential (~1885ns, 52 allocs)
|
||||
- `BenchmarkTraverseArrayPar_Small` - Parallel (~1362ns, 37 allocs)
|
||||
- `BenchmarkExecuteTraverseArraySeq_Small` - Executing sequential (~1080ns, 34 allocs)
|
||||
- `BenchmarkExecuteTraverseArrayPar_Small` - Executing parallel (~18560ns, 202 allocs)
|
||||
|
||||
**Key Insights:**
|
||||
- **Bottleneck:** Traverse operations allocate per element
|
||||
- Empty array still has 13 allocations (overhead)
|
||||
- Parallel construction is faster but execution is much slower
|
||||
- **Major Bottleneck:** Parallel execution: ~18.5μs with 202 allocations
|
||||
- Sequential execution is ~17x faster for small arrays
|
||||
- **Recommendation:** Use sequential traverse for small collections, parallel only for large, expensive operations
|
||||
|
||||
### 15. Record Operations
|
||||
- `BenchmarkTraverseRecord_Small` - 3 entries (~1444ns, 55 allocs)
|
||||
- `BenchmarkSequenceRecord_Small` - 3 entries (~1073ns, 54 allocs)
|
||||
|
||||
**Key Insights:**
|
||||
- Record operations have high allocation overhead
|
||||
- Similar performance to array traversal
|
||||
- Allocations scale with map size
|
||||
|
||||
### 16. Resource Management
|
||||
- `BenchmarkWithResource_Success` - Creating resource wrapper (~193ns, 8 allocs)
|
||||
- `BenchmarkExecuteWithResource_Success` - Executing with resource (varies)
|
||||
- `BenchmarkExecuteWithResource_ErrorInBody` - Error handling (varies)
|
||||
|
||||
**Key Insights:**
|
||||
- Resource management has 8 allocations for safety
|
||||
- Ensures proper cleanup even on errors
|
||||
- Overhead is acceptable for resource safety guarantees
|
||||
|
||||
### 17. Context Cancellation
|
||||
- `BenchmarkExecute_CanceledContext` - Executing with canceled context
|
||||
- `BenchmarkExecuteApPar_CanceledContext` - Parallel with canceled context
|
||||
|
||||
**Key Insights:**
|
||||
- Cancellation is handled efficiently
|
||||
- Minimal overhead for checking cancellation
|
||||
- ApPar respects cancellation properly
|
||||
|
||||
## Performance Bottlenecks Summary
|
||||
|
||||
### Critical Bottlenecks (>100ns or >5 allocations)
|
||||
|
||||
1. **Pipeline operations with F.Pipe** (~200-1000ns, 9-25 allocations)
|
||||
- **Impact:** High - commonly used pattern
|
||||
- **Mitigation:** Use direct function calls in hot paths
|
||||
- **Example:**
|
||||
```go
|
||||
// Slow (200ns, 9 allocs)
|
||||
result := F.Pipe1(rioe, Map[int](transform))
|
||||
|
||||
// Fast (24ns, 1 alloc)
|
||||
result := Map[int](transform)(rioe)
|
||||
```
|
||||
|
||||
2. **MonadMap and MonadChain** (~135-207ns, 5-6 allocations)
|
||||
- **Impact:** High - fundamental operations
|
||||
- **Mitigation:** Use curried versions (Map, Chain)
|
||||
- **Speedup:** 5-7x faster
|
||||
|
||||
3. **Parallel applicative execution** (~5.6μs, 61 allocations)
|
||||
- **Impact:** High when used
|
||||
- **Mitigation:** Use ApSeq for simple operations
|
||||
- **Note:** Only use ApPar for truly independent, expensive computations
|
||||
|
||||
4. **Parallel traverse execution** (~18.5μs, 202 allocations)
|
||||
- **Impact:** High for collections
|
||||
- **Mitigation:** Use sequential traverse for small collections
|
||||
- **Threshold:** Consider parallel only for >100 elements with expensive operations
|
||||
|
||||
5. **Alt operations** (~210-344ns, 6 allocations)
|
||||
- **Impact:** Medium
|
||||
- **Mitigation:** Use OrElse for error recovery (40-52ns, 2 allocs)
|
||||
|
||||
### Minor Bottlenecks (50-100ns or 3-4 allocations)
|
||||
|
||||
6. **Flatten operations** (~147ns, 7 allocations)
|
||||
- **Impact:** Low - less commonly used
|
||||
- **Mitigation:** Avoid unnecessary nesting
|
||||
|
||||
7. **Memoize** (~82ns, 4 allocations)
|
||||
- **Impact:** Low - overhead pays off for expensive computations
|
||||
- **Mitigation:** Only use for computations >1μs
|
||||
|
||||
8. **ApS in do-notation** (~99ns, 4 allocations)
|
||||
- **Impact:** Low
|
||||
- **Mitigation:** Use Let or Bind when possible
|
||||
|
||||
## Optimization Recommendations
|
||||
|
||||
### For Hot Paths
|
||||
|
||||
1. **Use curried functions over Monad* versions:**
|
||||
```go
|
||||
// Instead of:
|
||||
result := MonadMap(rioe, transform) // 135ns, 5 allocs
|
||||
|
||||
// Use:
|
||||
result := Map[int](transform)(rioe) // 24ns, 1 alloc
|
||||
```
|
||||
|
||||
2. **Avoid F.Pipe in performance-critical code:**
|
||||
```go
|
||||
// Instead of:
|
||||
result := F.Pipe3(rioe, Map(f1), Chain(f2), Map(f3)) // 1000ns, 25 allocs
|
||||
|
||||
// Use:
|
||||
result := Map(f3)(Chain(f2)(Map(f1)(rioe))) // Much faster
|
||||
```
|
||||
|
||||
3. **Use sequential operations for small collections:**
|
||||
```go
|
||||
// For arrays with <10 elements:
|
||||
result := TraverseArraySeq(f)(arr) // 1.9μs, 52 allocs
|
||||
|
||||
// Instead of:
|
||||
result := TraverseArrayPar(f)(arr) // 18.5μs, 202 allocs (when executed)
|
||||
```
|
||||
|
||||
4. **Prefer OrElse over Alt for error recovery:**
|
||||
```go
|
||||
// Instead of:
|
||||
result := Alt(alternative)(rioe) // 210-344ns, 6 allocs
|
||||
|
||||
// Use:
|
||||
result := OrElse(recover)(rioe) // 40-52ns, 2 allocs
|
||||
```
|
||||
|
||||
### For Context Operations
|
||||
|
||||
- Context operations are generally efficient
|
||||
- Ask has 3 allocations but is necessary for context access
|
||||
- Cancellation checking is fast and should be used liberally
|
||||
|
||||
### For Resource Management
|
||||
|
||||
- WithResource overhead (8 allocations) is acceptable for safety
|
||||
- Always use for resources that need cleanup
|
||||
- The RAII pattern prevents resource leaks
|
||||
|
||||
### Memory Considerations
|
||||
|
||||
- Most operations have 1-2 allocations
|
||||
- Monad* versions have 5-8 allocations
|
||||
- Pipeline operations allocate heavily
|
||||
- Parallel operations have significant allocation overhead
|
||||
- Traverse operations allocate per element
|
||||
|
||||
## Comparative Analysis
|
||||
|
||||
### Fastest Operations (<50ns, <2 allocations)
|
||||
- Constructors (Left, Right, Of)
|
||||
- Execution (Execute_Right/Left)
|
||||
- Curried operations (Map, Chain, ChainFirst)
|
||||
- TryCatch execution
|
||||
- Chain-K operations
|
||||
- Let and Bind in do-notation
|
||||
|
||||
### Medium Speed (50-200ns, 2-8 allocations)
|
||||
- Conversion operations
|
||||
- MonadMap, MonadChain
|
||||
- Flatten
|
||||
- Memoize
|
||||
- Resource management construction
|
||||
- Traverse construction
|
||||
|
||||
### Slower Operations (>200ns or >8 allocations)
|
||||
- Pipeline operations
|
||||
- Alt operations
|
||||
- Traverse operations (especially parallel)
|
||||
- Applicative operations (especially parallel execution)
|
||||
|
||||
## Parallel vs Sequential Trade-offs
|
||||
|
||||
### When to Use Sequential (ApSeq, TraverseArraySeq)
|
||||
- Small collections (<10 elements)
|
||||
- Fast operations (<1μs per element)
|
||||
- When allocations matter
|
||||
- Default choice for most use cases
|
||||
|
||||
### When to Use Parallel (ApPar, TraverseArrayPar)
|
||||
- Large collections (>100 elements)
|
||||
- Expensive operations (>10μs per element)
|
||||
- Independent computations
|
||||
- When latency matters more than throughput
|
||||
- I/O-bound operations
|
||||
|
||||
### Parallel Overhead
|
||||
- Construction: ~50ns, 3 allocs
|
||||
- Execution: +4-17μs, +40-160 allocs
|
||||
- Context management and goroutine coordination
|
||||
- Only worthwhile for truly expensive operations
|
||||
|
||||
## Conclusion
|
||||
|
||||
The `context/readerioeither` package is well-optimized with most operations completing in nanoseconds with minimal allocations. Key recommendations:
|
||||
|
||||
1. Use curried functions (Map, Chain) instead of Monad* versions (5-7x faster)
|
||||
2. Avoid F.Pipe in hot paths (5-10x slower)
|
||||
3. Use sequential operations by default; parallel only for expensive, independent computations
|
||||
4. Prefer OrElse over Alt for error recovery (5x faster)
|
||||
5. TryCatch is excellent for wrapping Go functions (near-zero execution cost)
|
||||
6. Context operations are efficient; use liberally
|
||||
7. Resource management overhead is acceptable for safety guarantees
|
||||
|
||||
For typical use cases, the performance is excellent. Only in extremely hot paths (millions of operations per second) should you consider the micro-optimizations suggested above.
|
||||
887
v2/context/readerioeither/reader_bench_test.go
Normal file
887
v2/context/readerioeither/reader_bench_test.go
Normal file
@@ -0,0 +1,887 @@
|
||||
// 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 readerioeither
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
E "github.com/IBM/fp-go/v2/either"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
IOE "github.com/IBM/fp-go/v2/ioeither"
|
||||
)
|
||||
|
||||
var (
|
||||
benchErr = errors.New("benchmark error")
|
||||
benchCtx = context.Background()
|
||||
benchResult Either[int]
|
||||
benchRIOE ReaderIOEither[int]
|
||||
benchInt int
|
||||
)
|
||||
|
||||
// Benchmark core constructors
|
||||
func BenchmarkLeft(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = Left[int](benchErr)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRight(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = Right[int](42)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkOf(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = Of[int](42)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFromEither_Right(b *testing.B) {
|
||||
either := E.Right[error](42)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = FromEither(either)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFromEither_Left(b *testing.B) {
|
||||
either := E.Left[int](benchErr)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = FromEither(either)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFromIO(b *testing.B) {
|
||||
io := func() int { return 42 }
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = FromIO[int](io)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFromIOEither_Right(b *testing.B) {
|
||||
ioe := IOE.Of[error](42)
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = FromIOEither(ioe)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFromIOEither_Left(b *testing.B) {
|
||||
ioe := IOE.Left[int](benchErr)
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = FromIOEither(ioe)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark execution
|
||||
func BenchmarkExecute_Right(b *testing.B) {
|
||||
rioe := Right[int](42)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = rioe(benchCtx)()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkExecute_Left(b *testing.B) {
|
||||
rioe := Left[int](benchErr)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = rioe(benchCtx)()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkExecute_WithContext(b *testing.B) {
|
||||
rioe := Right[int](42)
|
||||
ctx, cancel := context.WithCancel(benchCtx)
|
||||
defer cancel()
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = rioe(ctx)()
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark functor operations
|
||||
func BenchmarkMonadMap_Right(b *testing.B) {
|
||||
rioe := Right[int](42)
|
||||
mapper := func(a int) int { return a * 2 }
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = MonadMap(rioe, mapper)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMonadMap_Left(b *testing.B) {
|
||||
rioe := Left[int](benchErr)
|
||||
mapper := func(a int) int { return a * 2 }
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = MonadMap(rioe, mapper)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMap_Right(b *testing.B) {
|
||||
rioe := Right[int](42)
|
||||
mapper := Map[int](func(a int) int { return a * 2 })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = mapper(rioe)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMap_Left(b *testing.B) {
|
||||
rioe := Left[int](benchErr)
|
||||
mapper := Map[int](func(a int) int { return a * 2 })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = mapper(rioe)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMapTo_Right(b *testing.B) {
|
||||
rioe := Right[int](42)
|
||||
mapper := MapTo[int](99)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = mapper(rioe)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark monad operations
|
||||
func BenchmarkMonadChain_Right(b *testing.B) {
|
||||
rioe := Right[int](42)
|
||||
chainer := func(a int) ReaderIOEither[int] { return Right[int](a * 2) }
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = MonadChain(rioe, chainer)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMonadChain_Left(b *testing.B) {
|
||||
rioe := Left[int](benchErr)
|
||||
chainer := func(a int) ReaderIOEither[int] { return Right[int](a * 2) }
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = MonadChain(rioe, chainer)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkChain_Right(b *testing.B) {
|
||||
rioe := Right[int](42)
|
||||
chainer := Chain[int](func(a int) ReaderIOEither[int] { return Right[int](a * 2) })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = chainer(rioe)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkChain_Left(b *testing.B) {
|
||||
rioe := Left[int](benchErr)
|
||||
chainer := Chain[int](func(a int) ReaderIOEither[int] { return Right[int](a * 2) })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = chainer(rioe)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkChainFirst_Right(b *testing.B) {
|
||||
rioe := Right[int](42)
|
||||
chainer := ChainFirst[int](func(a int) ReaderIOEither[string] { return Right[string]("logged") })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = chainer(rioe)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkChainFirst_Left(b *testing.B) {
|
||||
rioe := Left[int](benchErr)
|
||||
chainer := ChainFirst[int](func(a int) ReaderIOEither[string] { return Right[string]("logged") })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = chainer(rioe)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFlatten_Right(b *testing.B) {
|
||||
nested := Right[ReaderIOEither[int]](Right[int](42))
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = Flatten(nested)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFlatten_Left(b *testing.B) {
|
||||
nested := Left[ReaderIOEither[int]](benchErr)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = Flatten(nested)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark applicative operations
|
||||
func BenchmarkMonadApSeq_RightRight(b *testing.B) {
|
||||
fab := Right[func(int) int](func(a int) int { return a * 2 })
|
||||
fa := Right[int](42)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = MonadApSeq(fab, fa)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMonadApSeq_RightLeft(b *testing.B) {
|
||||
fab := Right[func(int) int](func(a int) int { return a * 2 })
|
||||
fa := Left[int](benchErr)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = MonadApSeq(fab, fa)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMonadApSeq_LeftRight(b *testing.B) {
|
||||
fab := Left[func(int) int](benchErr)
|
||||
fa := Right[int](42)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = MonadApSeq(fab, fa)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMonadApPar_RightRight(b *testing.B) {
|
||||
fab := Right[func(int) int](func(a int) int { return a * 2 })
|
||||
fa := Right[int](42)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = MonadApPar(fab, fa)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMonadApPar_RightLeft(b *testing.B) {
|
||||
fab := Right[func(int) int](func(a int) int { return a * 2 })
|
||||
fa := Left[int](benchErr)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = MonadApPar(fab, fa)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMonadApPar_LeftRight(b *testing.B) {
|
||||
fab := Left[func(int) int](benchErr)
|
||||
fa := Right[int](42)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = MonadApPar(fab, fa)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark execution of applicative operations
|
||||
func BenchmarkExecuteApSeq_RightRight(b *testing.B) {
|
||||
fab := Right[func(int) int](func(a int) int { return a * 2 })
|
||||
fa := Right[int](42)
|
||||
rioe := MonadApSeq(fab, fa)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = rioe(benchCtx)()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkExecuteApPar_RightRight(b *testing.B) {
|
||||
fab := Right[func(int) int](func(a int) int { return a * 2 })
|
||||
fa := Right[int](42)
|
||||
rioe := MonadApPar(fab, fa)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = rioe(benchCtx)()
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark alternative operations
|
||||
func BenchmarkAlt_RightRight(b *testing.B) {
|
||||
rioe := Right[int](42)
|
||||
alternative := Alt[int](func() ReaderIOEither[int] { return Right[int](99) })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = alternative(rioe)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAlt_LeftRight(b *testing.B) {
|
||||
rioe := Left[int](benchErr)
|
||||
alternative := Alt[int](func() ReaderIOEither[int] { return Right[int](99) })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = alternative(rioe)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkOrElse_Right(b *testing.B) {
|
||||
rioe := Right[int](42)
|
||||
recover := OrElse[int](func(e error) ReaderIOEither[int] { return Right[int](0) })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = recover(rioe)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkOrElse_Left(b *testing.B) {
|
||||
rioe := Left[int](benchErr)
|
||||
recover := OrElse[int](func(e error) ReaderIOEither[int] { return Right[int](0) })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = recover(rioe)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark chain operations with different types
|
||||
func BenchmarkChainEitherK_Right(b *testing.B) {
|
||||
rioe := Right[int](42)
|
||||
chainer := ChainEitherK[int](func(a int) Either[int] { return E.Right[error](a * 2) })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = chainer(rioe)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkChainEitherK_Left(b *testing.B) {
|
||||
rioe := Left[int](benchErr)
|
||||
chainer := ChainEitherK[int](func(a int) Either[int] { return E.Right[error](a * 2) })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = chainer(rioe)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkChainIOK_Right(b *testing.B) {
|
||||
rioe := Right[int](42)
|
||||
chainer := ChainIOK[int](func(a int) func() int { return func() int { return a * 2 } })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = chainer(rioe)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkChainIOK_Left(b *testing.B) {
|
||||
rioe := Left[int](benchErr)
|
||||
chainer := ChainIOK[int](func(a int) func() int { return func() int { return a * 2 } })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = chainer(rioe)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkChainIOEitherK_Right(b *testing.B) {
|
||||
rioe := Right[int](42)
|
||||
chainer := ChainIOEitherK[int](func(a int) IOEither[int] { return IOE.Of[error](a * 2) })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = chainer(rioe)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkChainIOEitherK_Left(b *testing.B) {
|
||||
rioe := Left[int](benchErr)
|
||||
chainer := ChainIOEitherK[int](func(a int) IOEither[int] { return IOE.Of[error](a * 2) })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = chainer(rioe)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark context operations
|
||||
func BenchmarkAsk(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = Ask()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDefer(b *testing.B) {
|
||||
gen := func() ReaderIOEither[int] { return Right[int](42) }
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = Defer(gen)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMemoize(b *testing.B) {
|
||||
rioe := Right[int](42)
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = Memoize(rioe)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark delay operations
|
||||
func BenchmarkDelay_Construction(b *testing.B) {
|
||||
rioe := Right[int](42)
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = Delay[int](time.Millisecond)(rioe)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTimer_Construction(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = Timer(time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark TryCatch
|
||||
func BenchmarkTryCatch_Success(b *testing.B) {
|
||||
f := func(ctx context.Context) func() (int, error) {
|
||||
return func() (int, error) { return 42, nil }
|
||||
}
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = TryCatch(f)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTryCatch_Error(b *testing.B) {
|
||||
f := func(ctx context.Context) func() (int, error) {
|
||||
return func() (int, error) { return 0, benchErr }
|
||||
}
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = TryCatch(f)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkExecuteTryCatch_Success(b *testing.B) {
|
||||
f := func(ctx context.Context) func() (int, error) {
|
||||
return func() (int, error) { return 42, nil }
|
||||
}
|
||||
rioe := TryCatch(f)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = rioe(benchCtx)()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkExecuteTryCatch_Error(b *testing.B) {
|
||||
f := func(ctx context.Context) func() (int, error) {
|
||||
return func() (int, error) { return 0, benchErr }
|
||||
}
|
||||
rioe := TryCatch(f)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = rioe(benchCtx)()
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark pipeline operations
|
||||
func BenchmarkPipeline_Map_Right(b *testing.B) {
|
||||
rioe := Right[int](21)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = F.Pipe1(
|
||||
rioe,
|
||||
Map[int](func(x int) int { return x * 2 }),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPipeline_Map_Left(b *testing.B) {
|
||||
rioe := Left[int](benchErr)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = F.Pipe1(
|
||||
rioe,
|
||||
Map[int](func(x int) int { return x * 2 }),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPipeline_Chain_Right(b *testing.B) {
|
||||
rioe := Right[int](21)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = F.Pipe1(
|
||||
rioe,
|
||||
Chain[int](func(x int) ReaderIOEither[int] { return Right[int](x * 2) }),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPipeline_Chain_Left(b *testing.B) {
|
||||
rioe := Left[int](benchErr)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = F.Pipe1(
|
||||
rioe,
|
||||
Chain[int](func(x int) ReaderIOEither[int] { return Right[int](x * 2) }),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPipeline_Complex_Right(b *testing.B) {
|
||||
rioe := Right[int](10)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = F.Pipe3(
|
||||
rioe,
|
||||
Map[int](func(x int) int { return x * 2 }),
|
||||
Chain[int](func(x int) ReaderIOEither[int] { return Right[int](x + 1) }),
|
||||
Map[int](func(x int) int { return x * 2 }),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPipeline_Complex_Left(b *testing.B) {
|
||||
rioe := Left[int](benchErr)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = F.Pipe3(
|
||||
rioe,
|
||||
Map[int](func(x int) int { return x * 2 }),
|
||||
Chain[int](func(x int) ReaderIOEither[int] { return Right[int](x + 1) }),
|
||||
Map[int](func(x int) int { return x * 2 }),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkExecutePipeline_Complex_Right(b *testing.B) {
|
||||
rioe := F.Pipe3(
|
||||
Right[int](10),
|
||||
Map[int](func(x int) int { return x * 2 }),
|
||||
Chain[int](func(x int) ReaderIOEither[int] { return Right[int](x + 1) }),
|
||||
Map[int](func(x int) int { return x * 2 }),
|
||||
)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = rioe(benchCtx)()
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark do-notation operations
|
||||
func BenchmarkDo(b *testing.B) {
|
||||
type State struct{ value int }
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = Do(State{})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBind_Right(b *testing.B) {
|
||||
type State struct{ value int }
|
||||
initial := Do(State{})
|
||||
binder := Bind[State, State](
|
||||
func(v int) func(State) State {
|
||||
return func(s State) State { return State{value: v} }
|
||||
},
|
||||
func(s State) ReaderIOEither[int] {
|
||||
return Right[int](42)
|
||||
},
|
||||
)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = binder(initial)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkLet_Right(b *testing.B) {
|
||||
type State struct{ value int }
|
||||
initial := Right[State](State{value: 10})
|
||||
letter := Let[State, State](
|
||||
func(v int) func(State) State {
|
||||
return func(s State) State { return State{value: s.value + v} }
|
||||
},
|
||||
func(s State) int { return 32 },
|
||||
)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = letter(initial)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkApS_Right(b *testing.B) {
|
||||
type State struct{ value int }
|
||||
initial := Right[State](State{value: 10})
|
||||
aps := ApS[State, State](
|
||||
func(v int) func(State) State {
|
||||
return func(s State) State { return State{value: v} }
|
||||
},
|
||||
Right[int](42),
|
||||
)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = aps(initial)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark traverse operations
|
||||
func BenchmarkTraverseArray_Empty(b *testing.B) {
|
||||
arr := []int{}
|
||||
traverser := TraverseArray[int](func(x int) ReaderIOEither[int] {
|
||||
return Right[int](x * 2)
|
||||
})
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = traverser(arr)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTraverseArray_Small(b *testing.B) {
|
||||
arr := []int{1, 2, 3}
|
||||
traverser := TraverseArray[int](func(x int) ReaderIOEither[int] {
|
||||
return Right[int](x * 2)
|
||||
})
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = traverser(arr)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTraverseArray_Medium(b *testing.B) {
|
||||
arr := make([]int, 10)
|
||||
for i := range arr {
|
||||
arr[i] = i
|
||||
}
|
||||
traverser := TraverseArray[int](func(x int) ReaderIOEither[int] {
|
||||
return Right[int](x * 2)
|
||||
})
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = traverser(arr)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTraverseArraySeq_Small(b *testing.B) {
|
||||
arr := []int{1, 2, 3}
|
||||
traverser := TraverseArraySeq[int](func(x int) ReaderIOEither[int] {
|
||||
return Right[int](x * 2)
|
||||
})
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = traverser(arr)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTraverseArrayPar_Small(b *testing.B) {
|
||||
arr := []int{1, 2, 3}
|
||||
traverser := TraverseArrayPar[int](func(x int) ReaderIOEither[int] {
|
||||
return Right[int](x * 2)
|
||||
})
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = traverser(arr)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSequenceArray_Small(b *testing.B) {
|
||||
arr := []ReaderIOEither[int]{
|
||||
Right[int](1),
|
||||
Right[int](2),
|
||||
Right[int](3),
|
||||
}
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = SequenceArray(arr)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkExecuteTraverseArray_Small(b *testing.B) {
|
||||
arr := []int{1, 2, 3}
|
||||
rioe := TraverseArray[int](func(x int) ReaderIOEither[int] {
|
||||
return Right[int](x * 2)
|
||||
})(arr)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = rioe(benchCtx)()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkExecuteTraverseArraySeq_Small(b *testing.B) {
|
||||
arr := []int{1, 2, 3}
|
||||
rioe := TraverseArraySeq[int](func(x int) ReaderIOEither[int] {
|
||||
return Right[int](x * 2)
|
||||
})(arr)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = rioe(benchCtx)()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkExecuteTraverseArrayPar_Small(b *testing.B) {
|
||||
arr := []int{1, 2, 3}
|
||||
rioe := TraverseArrayPar[int](func(x int) ReaderIOEither[int] {
|
||||
return Right[int](x * 2)
|
||||
})(arr)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = rioe(benchCtx)()
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark record operations
|
||||
func BenchmarkTraverseRecord_Small(b *testing.B) {
|
||||
rec := map[string]int{"a": 1, "b": 2, "c": 3}
|
||||
traverser := TraverseRecord[string, int](func(x int) ReaderIOEither[int] {
|
||||
return Right[int](x * 2)
|
||||
})
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = traverser(rec)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSequenceRecord_Small(b *testing.B) {
|
||||
rec := map[string]ReaderIOEither[int]{
|
||||
"a": Right[int](1),
|
||||
"b": Right[int](2),
|
||||
"c": Right[int](3),
|
||||
}
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = SequenceRecord(rec)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark resource management
|
||||
func BenchmarkWithResource_Success(b *testing.B) {
|
||||
acquire := Right[int](42)
|
||||
release := func(int) ReaderIOEither[int] { return Right[int](0) }
|
||||
body := func(x int) ReaderIOEither[int] { return Right[int](x * 2) }
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = WithResource[int, int, int](acquire, release)(body)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkExecuteWithResource_Success(b *testing.B) {
|
||||
acquire := Right[int](42)
|
||||
release := func(int) ReaderIOEither[int] { return Right[int](0) }
|
||||
body := func(x int) ReaderIOEither[int] { return Right[int](x * 2) }
|
||||
rioe := WithResource[int, int, int](acquire, release)(body)
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = rioe(benchCtx)()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkExecuteWithResource_ErrorInBody(b *testing.B) {
|
||||
acquire := Right[int](42)
|
||||
release := func(int) ReaderIOEither[int] { return Right[int](0) }
|
||||
body := func(x int) ReaderIOEither[int] { return Left[int](benchErr) }
|
||||
rioe := WithResource[int, int, int](acquire, release)(body)
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = rioe(benchCtx)()
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark context cancellation
|
||||
func BenchmarkExecute_CanceledContext(b *testing.B) {
|
||||
rioe := Right[int](42)
|
||||
ctx, cancel := context.WithCancel(benchCtx)
|
||||
cancel() // Cancel immediately
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = rioe(ctx)()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkExecuteApPar_CanceledContext(b *testing.B) {
|
||||
fab := Right[func(int) int](func(a int) int { return a * 2 })
|
||||
fa := Right[int](42)
|
||||
rioe := MonadApPar(fab, fa)
|
||||
ctx, cancel := context.WithCancel(benchCtx)
|
||||
cancel() // Cancel immediately
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = rioe(ctx)()
|
||||
}
|
||||
}
|
||||
|
||||
// Made with Bob
|
||||
277
v2/either/BENCHMARKS.md
Normal file
277
v2/either/BENCHMARKS.md
Normal file
@@ -0,0 +1,277 @@
|
||||
# Either Benchmarks
|
||||
|
||||
This document describes the benchmark suite for the `either` package and how to interpret the results to identify performance bottlenecks.
|
||||
|
||||
## Running Benchmarks
|
||||
|
||||
To run all benchmarks:
|
||||
```bash
|
||||
cd either
|
||||
go test -bench=. -benchmem
|
||||
```
|
||||
|
||||
To run specific benchmarks:
|
||||
```bash
|
||||
go test -bench=BenchmarkMap -benchmem
|
||||
go test -bench=BenchmarkChain -benchmem
|
||||
```
|
||||
|
||||
To run with more iterations for stable results:
|
||||
```bash
|
||||
go test -bench=. -benchmem -benchtime=1000000x
|
||||
```
|
||||
|
||||
## Benchmark Categories
|
||||
|
||||
### 1. Core Constructors
|
||||
- `BenchmarkLeft` - Creating Left values
|
||||
- `BenchmarkRight` - Creating Right values
|
||||
- `BenchmarkOf` - Creating Right values via Of
|
||||
|
||||
**Key Insights:**
|
||||
- Right construction is ~2x faster than Left (~0.45ns vs ~0.87ns)
|
||||
- All constructors have zero allocations
|
||||
- These are the fastest operations in the package
|
||||
|
||||
### 2. Predicates
|
||||
- `BenchmarkIsLeft` - Testing if Either is Left
|
||||
- `BenchmarkIsRight` - Testing if Either is Right
|
||||
|
||||
**Key Insights:**
|
||||
- Both predicates are extremely fast (~0.3ns)
|
||||
- Zero allocations
|
||||
- No performance difference between Left and Right checks
|
||||
|
||||
### 3. Fold Operations
|
||||
- `BenchmarkMonadFold_Right/Left` - Direct fold with handlers
|
||||
- `BenchmarkFold_Right/Left` - Curried fold
|
||||
|
||||
**Key Insights:**
|
||||
- Right path is ~10x faster than Left path (0.5ns vs 5-7ns)
|
||||
- Curried version adds ~2ns overhead on Left path
|
||||
- Zero allocations for both paths
|
||||
- **Bottleneck:** Left path has higher overhead due to type assertions
|
||||
|
||||
### 4. Unwrap Operations
|
||||
- `BenchmarkUnwrap_Right/Left` - Converting to (value, error) tuple
|
||||
- `BenchmarkUnwrapError_Right/Left` - Specialized for error types
|
||||
|
||||
**Key Insights:**
|
||||
- Right path is ~10x faster than Left path
|
||||
- Zero allocations
|
||||
- Similar performance characteristics to Fold
|
||||
|
||||
### 5. Functor Operations (Map)
|
||||
- `BenchmarkMonadMap_Right/Left` - Direct map
|
||||
- `BenchmarkMap_Right/Left` - Curried map
|
||||
- `BenchmarkMapLeft_Right/Left` - Mapping over Left channel
|
||||
- `BenchmarkBiMap_Right/Left` - Mapping both channels
|
||||
|
||||
**Key Insights:**
|
||||
- Map operations: ~11-14ns, zero allocations
|
||||
- MapLeft on Left: ~34ns with 1 allocation (16B)
|
||||
- BiMap: ~29-39ns with 1 allocation (16B)
|
||||
- **Bottleneck:** BiMap and MapLeft allocate closures
|
||||
|
||||
### 6. Monad Operations (Chain)
|
||||
- `BenchmarkMonadChain_Right/Left` - Direct chain
|
||||
- `BenchmarkChain_Right/Left` - Curried chain
|
||||
- `BenchmarkChainFirst_Right/Left` - Chain preserving original value
|
||||
- `BenchmarkFlatten_Right/Left` - Removing nesting
|
||||
|
||||
**Key Insights:**
|
||||
- Chain is very fast: 2-7ns, zero allocations
|
||||
- **Bottleneck:** ChainFirst on Right: ~168ns with 5 allocations (120B)
|
||||
- ChainFirst on Left short-circuits efficiently (~9ns)
|
||||
- Curried Chain is faster than MonadChain
|
||||
|
||||
### 7. Applicative Operations (Ap)
|
||||
- `BenchmarkMonadAp_RightRight/RightLeft/LeftRight` - Direct apply
|
||||
- `BenchmarkAp_RightRight` - Curried apply
|
||||
|
||||
**Key Insights:**
|
||||
- Ap operations: 5-12ns, zero allocations
|
||||
- Short-circuits efficiently when either operand is Left
|
||||
- MonadAp slightly slower than curried Ap
|
||||
|
||||
### 8. Alternative Operations
|
||||
- `BenchmarkAlt_RightRight/LeftRight` - Providing alternatives
|
||||
- `BenchmarkOrElse_Right/Left` - Recovery from Left
|
||||
|
||||
**Key Insights:**
|
||||
- Very fast: 1.6-4.5ns, zero allocations
|
||||
- Right path short-circuits without evaluating alternative
|
||||
- Efficient error recovery mechanism
|
||||
|
||||
### 9. Conversion Operations
|
||||
- `BenchmarkTryCatch_Success/Error` - Converting (value, error) tuples
|
||||
- `BenchmarkTryCatchError_Success/Error` - Specialized for errors
|
||||
- `BenchmarkSwap_Right/Left` - Swapping Left/Right
|
||||
- `BenchmarkGetOrElse_Right/Left` - Extracting with default
|
||||
|
||||
**Key Insights:**
|
||||
- All operations: 0.3-6ns, zero allocations
|
||||
- Very efficient conversions
|
||||
- GetOrElse on Right is extremely fast (~0.3ns)
|
||||
|
||||
### 10. Pipeline Operations
|
||||
- `BenchmarkPipeline_Map_Right/Left` - Single Map in pipeline
|
||||
- `BenchmarkPipeline_Chain_Right/Left` - Single Chain in pipeline
|
||||
- `BenchmarkPipeline_Complex_Right/Left` - Multiple operations
|
||||
|
||||
**Key Insights:**
|
||||
- **Major Bottleneck:** Pipeline operations allocate heavily
|
||||
- Single Map: ~100ns with 4 allocations (96B)
|
||||
- Complex pipeline: ~200-250ns with 8 allocations (192B)
|
||||
- Chain in pipeline is much faster: 2-4.5ns, zero allocations
|
||||
- **Recommendation:** Use direct function calls instead of F.Pipe for hot paths
|
||||
|
||||
### 11. Sequence Operations
|
||||
- `BenchmarkMonadSequence2_RightRight/LeftRight` - Combining 2 Eithers
|
||||
- `BenchmarkMonadSequence3_RightRightRight` - Combining 3 Eithers
|
||||
|
||||
**Key Insights:**
|
||||
- Sequence2: ~6ns, zero allocations
|
||||
- Sequence3: ~9ns, zero allocations
|
||||
- Efficient short-circuiting on Left
|
||||
- Linear scaling with number of Eithers
|
||||
|
||||
### 12. Do-Notation Operations
|
||||
- `BenchmarkDo` - Creating empty context
|
||||
- `BenchmarkBind_Right` - Binding values to context
|
||||
- `BenchmarkLet_Right` - Pure computations in context
|
||||
|
||||
**Key Insights:**
|
||||
- Do is extremely fast: ~0.4ns, zero allocations
|
||||
- **Bottleneck:** Bind: ~130ns with 6 allocations (144B)
|
||||
- Let is more efficient: ~23ns with 1 allocation (16B)
|
||||
- **Recommendation:** Prefer Let over Bind when possible
|
||||
|
||||
### 13. String Formatting
|
||||
- `BenchmarkString_Right/Left` - Converting to string
|
||||
|
||||
**Key Insights:**
|
||||
- Right: ~69ns with 1 allocation (16B)
|
||||
- Left: ~111ns with 1 allocation (48B)
|
||||
- Only use for debugging, not in hot paths
|
||||
|
||||
## Performance Bottlenecks Summary
|
||||
|
||||
### Critical Bottlenecks (>100ns or multiple allocations)
|
||||
|
||||
1. **Pipeline operations with F.Pipe** (~100-250ns, 4-8 allocations)
|
||||
- **Impact:** High - commonly used pattern
|
||||
- **Mitigation:** Use direct function calls in hot paths
|
||||
- **Example:**
|
||||
```go
|
||||
// Slow (100ns, 4 allocs)
|
||||
result := F.Pipe1(either, Map[error](transform))
|
||||
|
||||
// Fast (12ns, 0 allocs)
|
||||
result := Map[error](transform)(either)
|
||||
```
|
||||
|
||||
2. **ChainFirst on Right path** (~168ns, 5 allocations)
|
||||
- **Impact:** Medium - used for side effects
|
||||
- **Mitigation:** Avoid in hot paths, use direct Chain if side effect result not needed
|
||||
|
||||
3. **Bind in do-notation** (~130ns, 6 allocations)
|
||||
- **Impact:** Medium - used in complex workflows
|
||||
- **Mitigation:** Use Let for pure computations, minimize Bind calls
|
||||
|
||||
### Minor Bottlenecks (20-50ns or 1 allocation)
|
||||
|
||||
4. **BiMap operations** (~29-39ns, 1 allocation)
|
||||
- **Impact:** Low - less commonly used
|
||||
- **Mitigation:** Use Map or MapLeft separately if only one channel needs transformation
|
||||
|
||||
5. **MapLeft on Left path** (~34ns, 1 allocation)
|
||||
- **Impact:** Low - error path typically not hot
|
||||
- **Mitigation:** None needed unless in critical error handling
|
||||
|
||||
## Optimization Recommendations
|
||||
|
||||
### For Hot Paths
|
||||
|
||||
1. **Prefer direct function calls over pipelines:**
|
||||
```go
|
||||
// Instead of:
|
||||
result := F.Pipe3(either, Map(f1), Chain(f2), Map(f3))
|
||||
|
||||
// Use:
|
||||
result := Map(f3)(Chain(f2)(Map(f1)(either)))
|
||||
```
|
||||
|
||||
2. **Use Chain instead of ChainFirst when possible:**
|
||||
```go
|
||||
// If you don't need the original value:
|
||||
result := Chain(f)(either) // Fast
|
||||
|
||||
// Instead of:
|
||||
result := ChainFirst(func(a A) Either[E, B] {
|
||||
f(a)
|
||||
return Right[error](a)
|
||||
})(either) // Slow
|
||||
```
|
||||
|
||||
3. **Prefer Let over Bind in do-notation:**
|
||||
```go
|
||||
// For pure computations:
|
||||
result := Let(setter, pureFunc)(either) // 23ns
|
||||
|
||||
// Instead of:
|
||||
result := Bind(setter, func(s S) Either[E, T] {
|
||||
return Right[error](pureFunc(s))
|
||||
})(either) // 130ns
|
||||
```
|
||||
|
||||
### For Error Paths
|
||||
|
||||
- Left path operations are generally slower but acceptable since errors are exceptional
|
||||
- Focus optimization on Right (success) path
|
||||
- Use MapLeft and BiMap freely in error handling code
|
||||
|
||||
### Memory Considerations
|
||||
|
||||
- Most operations have zero allocations
|
||||
- Avoid string formatting (String()) in production code
|
||||
- Pipeline operations allocate - use sparingly in hot paths
|
||||
|
||||
## Comparative Analysis
|
||||
|
||||
### Fastest Operations (<5ns)
|
||||
- Constructors (Left, Right, Of)
|
||||
- Predicates (IsLeft, IsRight)
|
||||
- GetOrElse on Right
|
||||
- Chain (curried version)
|
||||
- Alt/OrElse on Right
|
||||
- Do
|
||||
|
||||
### Medium Speed (5-20ns)
|
||||
- Fold operations
|
||||
- Unwrap operations
|
||||
- Map operations
|
||||
- MonadChain
|
||||
- Ap operations
|
||||
- Sequence operations
|
||||
- Let
|
||||
|
||||
### Slower Operations (>20ns)
|
||||
- BiMap
|
||||
- MapLeft on Left
|
||||
- String formatting
|
||||
- Pipeline operations
|
||||
- Bind
|
||||
- ChainFirst on Right
|
||||
|
||||
## Conclusion
|
||||
|
||||
The `either` package is highly optimized with most operations completing in nanoseconds with zero allocations. The main performance considerations are:
|
||||
|
||||
1. Avoid F.Pipe in hot paths - use direct function calls
|
||||
2. Prefer Let over Bind in do-notation
|
||||
3. Use Chain instead of ChainFirst when the original value isn't needed
|
||||
4. The Right (success) path is consistently faster than Left (error) path
|
||||
5. Most operations have zero allocations, making them GC-friendly
|
||||
|
||||
For typical use cases, the performance is excellent. Only in extremely hot paths (millions of operations per second) should you consider the micro-optimizations suggested above.
|
||||
66
v2/either/bench_pointers.txt
Normal file
66
v2/either/bench_pointers.txt
Normal file
@@ -0,0 +1,66 @@
|
||||
2025/11/08 09:02:49 out: test
|
||||
goos: windows
|
||||
goarch: amd64
|
||||
pkg: github.com/IBM/fp-go/v2/either
|
||||
cpu: AMD Ryzen 7 PRO 7840U w/ Radeon 780M Graphics
|
||||
BenchmarkLeft-16 71784931 16.52 ns/op 16 B/op 1 allocs/op
|
||||
BenchmarkRight-16 132961911 8.799 ns/op 8 B/op 1 allocs/op
|
||||
BenchmarkOf-16 132703154 8.894 ns/op 8 B/op 1 allocs/op
|
||||
BenchmarkIsLeft-16 1000000000 0.2188 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkIsRight-16 1000000000 0.2202 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMonadFold_Right-16 1000000000 0.2215 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMonadFold_Left-16 1000000000 0.2198 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkFold_Right-16 1000000000 0.4467 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkFold_Left-16 1000000000 0.2616 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkUnwrap_Right-16 1000000000 0.2937 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkUnwrap_Left-16 1000000000 0.3019 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkUnwrapError_Right-16 1000000000 0.5962 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkUnwrapError_Left-16 1000000000 0.2343 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMonadMap_Right-16 78786684 15.06 ns/op 8 B/op 1 allocs/op
|
||||
BenchmarkMonadMap_Left-16 60553860 20.47 ns/op 16 B/op 1 allocs/op
|
||||
BenchmarkMap_Right-16 81438198 14.52 ns/op 8 B/op 1 allocs/op
|
||||
BenchmarkMap_Left-16 61249489 22.27 ns/op 16 B/op 1 allocs/op
|
||||
BenchmarkMapLeft_Right-16 100000000 11.54 ns/op 8 B/op 1 allocs/op
|
||||
BenchmarkMapLeft_Left-16 53107918 22.85 ns/op 16 B/op 1 allocs/op
|
||||
BenchmarkBiMap_Right-16 47468728 22.02 ns/op 16 B/op 1 allocs/op
|
||||
BenchmarkBiMap_Left-16 53815036 22.93 ns/op 16 B/op 1 allocs/op
|
||||
BenchmarkMonadChain_Right-16 100000000 10.73 ns/op 8 B/op 1 allocs/op
|
||||
BenchmarkMonadChain_Left-16 71138511 18.14 ns/op 16 B/op 1 allocs/op
|
||||
BenchmarkChain_Right-16 129499905 9.254 ns/op 8 B/op 1 allocs/op
|
||||
BenchmarkChain_Left-16 62561910 16.93 ns/op 16 B/op 1 allocs/op
|
||||
BenchmarkChainFirst_Right-16 9357235 134.4 ns/op 144 B/op 7 allocs/op
|
||||
BenchmarkChainFirst_Left-16 46529842 21.52 ns/op 16 B/op 1 allocs/op
|
||||
BenchmarkFlatten_Right-16 353777130 3.336 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkFlatten_Left-16 63614917 20.03 ns/op 16 B/op 1 allocs/op
|
||||
BenchmarkMonadAp_RightRight-16 81210578 14.48 ns/op 8 B/op 1 allocs/op
|
||||
BenchmarkMonadAp_RightLeft-16 63263110 19.65 ns/op 16 B/op 1 allocs/op
|
||||
BenchmarkMonadAp_LeftRight-16 60824167 17.87 ns/op 16 B/op 1 allocs/op
|
||||
BenchmarkAp_RightRight-16 81860413 14.58 ns/op 8 B/op 1 allocs/op
|
||||
BenchmarkAlt_RightRight-16 130601511 8.955 ns/op 8 B/op 1 allocs/op
|
||||
BenchmarkAlt_LeftRight-16 136723171 9.062 ns/op 8 B/op 1 allocs/op
|
||||
BenchmarkOrElse_Right-16 130252060 9.187 ns/op 8 B/op 1 allocs/op
|
||||
BenchmarkOrElse_Left-16 137806992 9.325 ns/op 8 B/op 1 allocs/op
|
||||
BenchmarkTryCatch_Success-16 100000000 10.24 ns/op 8 B/op 1 allocs/op
|
||||
BenchmarkTryCatch_Error-16 61422231 19.29 ns/op 16 B/op 1 allocs/op
|
||||
BenchmarkTryCatchError_Success-16 100000000 10.06 ns/op 8 B/op 1 allocs/op
|
||||
BenchmarkTryCatchError_Error-16 68159355 18.74 ns/op 16 B/op 1 allocs/op
|
||||
BenchmarkSwap_Right-16 125292858 9.739 ns/op 8 B/op 1 allocs/op
|
||||
BenchmarkSwap_Left-16 64153282 18.13 ns/op 16 B/op 1 allocs/op
|
||||
BenchmarkGetOrElse_Right-16 1000000000 0.2167 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkGetOrElse_Left-16 1000000000 0.2154 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkPipeline_Map_Right-16 13649640 88.69 ns/op 104 B/op 5 allocs/op
|
||||
BenchmarkPipeline_Map_Left-16 11999940 101.6 ns/op 112 B/op 5 allocs/op
|
||||
BenchmarkPipeline_Chain_Right-16 135232720 8.888 ns/op 8 B/op 1 allocs/op
|
||||
BenchmarkPipeline_Chain_Left-16 72481712 17.98 ns/op 16 B/op 1 allocs/op
|
||||
BenchmarkPipeline_Complex_Right-16 6314605 185.0 ns/op 216 B/op 11 allocs/op
|
||||
BenchmarkPipeline_Complex_Left-16 5850564 217.1 ns/op 240 B/op 11 allocs/op
|
||||
BenchmarkMonadSequence2_RightRight-16 85073667 14.56 ns/op 8 B/op 1 allocs/op
|
||||
BenchmarkMonadSequence2_LeftRight-16 69183006 19.21 ns/op 16 B/op 1 allocs/op
|
||||
BenchmarkMonadSequence3_RightRightRight-16 73083387 16.82 ns/op 8 B/op 1 allocs/op
|
||||
BenchmarkDo-16 1000000000 0.2121 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkBind_Right-16 8551692 142.5 ns/op 160 B/op 8 allocs/op
|
||||
BenchmarkLet_Right-16 40338846 30.32 ns/op 24 B/op 2 allocs/op
|
||||
BenchmarkString_Right-16 15758826 73.75 ns/op 16 B/op 1 allocs/op
|
||||
BenchmarkString_Left-16 12798620 94.71 ns/op 48 B/op 1 allocs/op
|
||||
PASS
|
||||
ok github.com/IBM/fp-go/v2/either 72.607s
|
||||
66
v2/either/bench_struct.txt
Normal file
66
v2/either/bench_struct.txt
Normal file
@@ -0,0 +1,66 @@
|
||||
2025/11/08 09:04:15 out: test
|
||||
goos: windows
|
||||
goarch: amd64
|
||||
pkg: github.com/IBM/fp-go/v2/either
|
||||
cpu: AMD Ryzen 7 PRO 7840U w/ Radeon 780M Graphics
|
||||
BenchmarkLeft-16 1000000000 0.8335 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkRight-16 1000000000 0.4256 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkOf-16 1000000000 0.4370 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkIsLeft-16 1000000000 0.2199 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkIsRight-16 1000000000 0.2181 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMonadFold_Right-16 1000000000 0.3209 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMonadFold_Left-16 289017271 4.176 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkFold_Right-16 1000000000 0.4687 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkFold_Left-16 274668618 4.669 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkUnwrap_Right-16 1000000000 0.3520 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkUnwrap_Left-16 273187717 4.226 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkUnwrapError_Right-16 1000000000 0.4503 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkUnwrapError_Left-16 279699219 4.285 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMonadMap_Right-16 152084527 7.855 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMonadMap_Left-16 181258614 6.711 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMap_Right-16 146735268 8.328 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMap_Left-16 136155859 8.829 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMapLeft_Right-16 254870900 4.687 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMapLeft_Left-16 44555998 27.87 ns/op 16 B/op 1 allocs/op
|
||||
BenchmarkBiMap_Right-16 53596072 22.82 ns/op 16 B/op 1 allocs/op
|
||||
BenchmarkBiMap_Left-16 44864508 26.88 ns/op 16 B/op 1 allocs/op
|
||||
BenchmarkMonadChain_Right-16 350417858 3.456 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMonadChain_Left-16 228175792 5.328 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkChain_Right-16 681885982 1.558 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkChain_Left-16 273064258 4.305 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkChainFirst_Right-16 11384614 107.1 ns/op 120 B/op 5 allocs/op
|
||||
BenchmarkChainFirst_Left-16 145602547 8.177 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkFlatten_Right-16 333052550 3.563 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkFlatten_Left-16 178190731 6.822 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMonadAp_RightRight-16 153308394 7.690 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMonadAp_RightLeft-16 187848016 6.593 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMonadAp_LeftRight-16 226528400 5.175 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkAp_RightRight-16 163718392 7.422 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkAlt_RightRight-16 773928914 1.603 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkAlt_LeftRight-16 296220595 4.034 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkOrElse_Right-16 772122764 1.587 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkOrElse_Left-16 295411228 4.018 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkTryCatch_Success-16 446405984 2.691 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkTryCatch_Error-16 445387840 2.750 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkTryCatchError_Success-16 442356684 2.682 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkTryCatchError_Error-16 441426070 2.801 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkSwap_Right-16 448856370 2.819 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkSwap_Left-16 213215637 5.602 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkGetOrElse_Right-16 1000000000 0.2997 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkGetOrElse_Left-16 270844960 4.397 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkPipeline_Map_Right-16 13732585 83.72 ns/op 96 B/op 4 allocs/op
|
||||
BenchmarkPipeline_Map_Left-16 15044474 84.31 ns/op 96 B/op 4 allocs/op
|
||||
BenchmarkPipeline_Chain_Right-16 763123821 1.638 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkPipeline_Chain_Left-16 265623768 4.513 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkPipeline_Complex_Right-16 6963698 173.9 ns/op 192 B/op 8 allocs/op
|
||||
BenchmarkPipeline_Complex_Left-16 6829795 178.2 ns/op 192 B/op 8 allocs/op
|
||||
BenchmarkMonadSequence2_RightRight-16 188600779 6.451 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMonadSequence2_LeftRight-16 203297380 5.912 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMonadSequence3_RightRightRight-16 127004353 9.377 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkDo-16 1000000000 0.2096 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkBind_Right-16 9656920 124.5 ns/op 144 B/op 6 allocs/op
|
||||
BenchmarkLet_Right-16 49086178 22.46 ns/op 16 B/op 1 allocs/op
|
||||
BenchmarkString_Right-16 16014156 71.13 ns/op 16 B/op 1 allocs/op
|
||||
BenchmarkString_Left-16 11845627 94.86 ns/op 48 B/op 1 allocs/op
|
||||
PASS
|
||||
ok github.com/IBM/fp-go/v2/either 82.686s
|
||||
@@ -12,6 +12,7 @@
|
||||
// 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.
|
||||
//go:build !either_pointers
|
||||
|
||||
package either
|
||||
|
||||
@@ -21,8 +22,8 @@ import (
|
||||
|
||||
type (
|
||||
either struct {
|
||||
isLeft bool
|
||||
value any
|
||||
value any
|
||||
isRight bool
|
||||
}
|
||||
|
||||
// Either defines a data structure that logically holds either an E or an A. The flag discriminates the cases
|
||||
@@ -33,10 +34,10 @@ type (
|
||||
//
|
||||
//go:noinline
|
||||
func eitherString(s *either) string {
|
||||
if s.isLeft {
|
||||
return fmt.Sprintf("Left[%T](%v)", s.value, s.value)
|
||||
if s.isRight {
|
||||
return fmt.Sprintf("Right[%T](%v)", s.value, s.value)
|
||||
}
|
||||
return fmt.Sprintf("Right[%T](%v)", s.value, s.value)
|
||||
return fmt.Sprintf("Left[%T](%v)", s.value, s.value)
|
||||
}
|
||||
|
||||
// Format prints some debug info for the object
|
||||
@@ -72,7 +73,7 @@ func (s Either[E, A]) Format(f fmt.State, c rune) {
|
||||
//
|
||||
//go:inline
|
||||
func IsLeft[E, A any](val Either[E, A]) bool {
|
||||
return val.isLeft
|
||||
return !val.isRight
|
||||
}
|
||||
|
||||
// IsRight tests if the Either is a Right value.
|
||||
@@ -86,7 +87,7 @@ func IsLeft[E, A any](val Either[E, A]) bool {
|
||||
//
|
||||
//go:inline
|
||||
func IsRight[E, A any](val Either[E, A]) bool {
|
||||
return !val.isLeft
|
||||
return val.isRight
|
||||
}
|
||||
|
||||
// Left creates a new Either representing a Left (error/failure) value.
|
||||
@@ -98,7 +99,7 @@ func IsRight[E, A any](val Either[E, A]) bool {
|
||||
//
|
||||
//go:inline
|
||||
func Left[A, E any](value E) Either[E, A] {
|
||||
return Either[E, A]{true, value}
|
||||
return Either[E, A]{value, false}
|
||||
}
|
||||
|
||||
// Right creates a new Either representing a Right (success) value.
|
||||
@@ -110,7 +111,7 @@ func Left[A, E any](value E) Either[E, A] {
|
||||
//
|
||||
//go:inline
|
||||
func Right[E, A any](value A) Either[E, A] {
|
||||
return Either[E, A]{false, value}
|
||||
return Either[E, A]{value, true}
|
||||
}
|
||||
|
||||
// MonadFold extracts the value from an Either by providing handlers for both cases.
|
||||
@@ -126,10 +127,10 @@ func Right[E, A any](value A) Either[E, A] {
|
||||
//
|
||||
//go:inline
|
||||
func MonadFold[E, A, B any](ma Either[E, A], onLeft func(e E) B, onRight func(a A) B) B {
|
||||
if ma.isLeft {
|
||||
return onLeft(ma.value.(E))
|
||||
if ma.isRight {
|
||||
return onRight(ma.value.(A))
|
||||
}
|
||||
return onRight(ma.value.(A))
|
||||
return onLeft(ma.value.(E))
|
||||
}
|
||||
|
||||
// Unwrap converts an Either into the idiomatic Go tuple (value, error).
|
||||
@@ -143,11 +144,11 @@ func MonadFold[E, A, B any](ma Either[E, A], onLeft func(e E) B, onRight func(a
|
||||
//
|
||||
//go:inline
|
||||
func Unwrap[E, A any](ma Either[E, A]) (A, E) {
|
||||
if ma.isLeft {
|
||||
var a A
|
||||
return a, ma.value.(E)
|
||||
} else {
|
||||
if ma.isRight {
|
||||
var e E
|
||||
return ma.value.(A), e
|
||||
} else {
|
||||
var a A
|
||||
return a, ma.value.(E)
|
||||
}
|
||||
}
|
||||
|
||||
94
v2/either/core_pointers.go
Normal file
94
v2/either/core_pointers.go
Normal file
@@ -0,0 +1,94 @@
|
||||
// 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.
|
||||
//go:build either_pointers
|
||||
|
||||
package either
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Either[E, A any] struct {
|
||||
left *E
|
||||
right *A
|
||||
}
|
||||
|
||||
// String prints some debug info for the object
|
||||
//
|
||||
//go:noinline
|
||||
func eitherString[E, A any](s *Either[E, A]) string {
|
||||
if s.right != nil {
|
||||
return fmt.Sprintf("Right[%T](%v)", *s.right, *s.right)
|
||||
}
|
||||
return fmt.Sprintf("Left[%T](%v)", *s.left, *s.left)
|
||||
}
|
||||
|
||||
// Format prints some debug info for the object
|
||||
//
|
||||
//go:noinline
|
||||
func eitherFormat[E, A any](e *Either[E, A], f fmt.State, c rune) {
|
||||
switch c {
|
||||
case 's':
|
||||
fmt.Fprint(f, eitherString(e))
|
||||
default:
|
||||
fmt.Fprint(f, eitherString(e))
|
||||
}
|
||||
}
|
||||
|
||||
// String prints some debug info for the object
|
||||
func (s Either[E, A]) String() string {
|
||||
return eitherString(&s)
|
||||
}
|
||||
|
||||
// Format prints some debug info for the object
|
||||
func (s Either[E, A]) Format(f fmt.State, c rune) {
|
||||
eitherFormat(&s, f, c)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func Left[A, E any](value E) Either[E, A] {
|
||||
return Either[E, A]{left: &value}
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func Right[E, A any](value A) Either[E, A] {
|
||||
return Either[E, A]{right: &value}
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func IsLeft[E, A any](e Either[E, A]) bool {
|
||||
return e.left != nil
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func IsRight[E, A any](e Either[E, A]) bool {
|
||||
return e.right != nil
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func MonadFold[E, A, B any](ma Either[E, A], onLeft func(E) B, onRight func(A) B) B {
|
||||
if ma.right != nil {
|
||||
return onRight(*ma.right)
|
||||
}
|
||||
return onLeft(*ma.left)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func Unwrap[E, A any](ma Either[E, A]) (A, E) {
|
||||
if ma.right != nil {
|
||||
var e E
|
||||
return *ma.right, e
|
||||
}
|
||||
var a A
|
||||
return a, *ma.left
|
||||
}
|
||||
652
v2/either/either_bench_test.go
Normal file
652
v2/either/either_bench_test.go
Normal file
@@ -0,0 +1,652 @@
|
||||
// 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 either
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
)
|
||||
|
||||
var (
|
||||
benchErr = errors.New("benchmark error")
|
||||
benchResult Either[error, int]
|
||||
benchBool bool
|
||||
benchInt int
|
||||
benchString string
|
||||
)
|
||||
|
||||
// Benchmark core constructors
|
||||
func BenchmarkLeft(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = Left[int](benchErr)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRight(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = Right[error](42)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkOf(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = Of[error](42)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark predicates
|
||||
func BenchmarkIsLeft(b *testing.B) {
|
||||
left := Left[int](benchErr)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchBool = IsLeft(left)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIsRight(b *testing.B) {
|
||||
right := Right[error](42)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchBool = IsRight(right)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark fold operations
|
||||
func BenchmarkMonadFold_Right(b *testing.B) {
|
||||
right := Right[error](42)
|
||||
onLeft := func(e error) int { return 0 }
|
||||
onRight := func(a int) int { return a * 2 }
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchInt = MonadFold(right, onLeft, onRight)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMonadFold_Left(b *testing.B) {
|
||||
left := Left[int](benchErr)
|
||||
onLeft := func(e error) int { return 0 }
|
||||
onRight := func(a int) int { return a * 2 }
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchInt = MonadFold(left, onLeft, onRight)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFold_Right(b *testing.B) {
|
||||
right := Right[error](42)
|
||||
folder := Fold(
|
||||
func(e error) int { return 0 },
|
||||
func(a int) int { return a * 2 },
|
||||
)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchInt = folder(right)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFold_Left(b *testing.B) {
|
||||
left := Left[int](benchErr)
|
||||
folder := Fold(
|
||||
func(e error) int { return 0 },
|
||||
func(a int) int { return a * 2 },
|
||||
)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchInt = folder(left)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark unwrap operations
|
||||
func BenchmarkUnwrap_Right(b *testing.B) {
|
||||
right := Right[error](42)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchInt, _ = Unwrap(right)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnwrap_Left(b *testing.B) {
|
||||
left := Left[int](benchErr)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchInt, _ = Unwrap(left)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnwrapError_Right(b *testing.B) {
|
||||
right := Right[error](42)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchInt, _ = UnwrapError(right)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnwrapError_Left(b *testing.B) {
|
||||
left := Left[int](benchErr)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchInt, _ = UnwrapError(left)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark functor operations
|
||||
func BenchmarkMonadMap_Right(b *testing.B) {
|
||||
right := Right[error](42)
|
||||
mapper := func(a int) int { return a * 2 }
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = MonadMap(right, mapper)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMonadMap_Left(b *testing.B) {
|
||||
left := Left[int](benchErr)
|
||||
mapper := func(a int) int { return a * 2 }
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = MonadMap(left, mapper)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMap_Right(b *testing.B) {
|
||||
right := Right[error](42)
|
||||
mapper := Map[error](func(a int) int { return a * 2 })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = mapper(right)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMap_Left(b *testing.B) {
|
||||
left := Left[int](benchErr)
|
||||
mapper := Map[error](func(a int) int { return a * 2 })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = mapper(left)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMapLeft_Right(b *testing.B) {
|
||||
right := Right[error](42)
|
||||
mapper := MapLeft[int](func(e error) string { return e.Error() })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = mapper(right)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMapLeft_Left(b *testing.B) {
|
||||
left := Left[int](benchErr)
|
||||
mapper := MapLeft[int](func(e error) string { return e.Error() })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = mapper(left)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBiMap_Right(b *testing.B) {
|
||||
right := Right[error](42)
|
||||
mapper := BiMap(
|
||||
func(e error) string { return e.Error() },
|
||||
func(a int) string { return "value" },
|
||||
)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = mapper(right)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBiMap_Left(b *testing.B) {
|
||||
left := Left[int](benchErr)
|
||||
mapper := BiMap(
|
||||
func(e error) string { return e.Error() },
|
||||
func(a int) string { return "value" },
|
||||
)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = mapper(left)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark monad operations
|
||||
func BenchmarkMonadChain_Right(b *testing.B) {
|
||||
right := Right[error](42)
|
||||
chainer := func(a int) Either[error, int] { return Right[error](a * 2) }
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = MonadChain(right, chainer)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMonadChain_Left(b *testing.B) {
|
||||
left := Left[int](benchErr)
|
||||
chainer := func(a int) Either[error, int] { return Right[error](a * 2) }
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = MonadChain(left, chainer)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkChain_Right(b *testing.B) {
|
||||
right := Right[error](42)
|
||||
chainer := Chain[error](func(a int) Either[error, int] { return Right[error](a * 2) })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = chainer(right)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkChain_Left(b *testing.B) {
|
||||
left := Left[int](benchErr)
|
||||
chainer := Chain[error](func(a int) Either[error, int] { return Right[error](a * 2) })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = chainer(left)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkChainFirst_Right(b *testing.B) {
|
||||
right := Right[error](42)
|
||||
chainer := ChainFirst[error](func(a int) Either[error, string] { return Right[error]("logged") })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = chainer(right)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkChainFirst_Left(b *testing.B) {
|
||||
left := Left[int](benchErr)
|
||||
chainer := ChainFirst[error](func(a int) Either[error, string] { return Right[error]("logged") })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = chainer(left)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFlatten_Right(b *testing.B) {
|
||||
nested := Right[error](Right[error](42))
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = Flatten(nested)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFlatten_Left(b *testing.B) {
|
||||
nested := Left[Either[error, int]](benchErr)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = Flatten(nested)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark applicative operations
|
||||
func BenchmarkMonadAp_RightRight(b *testing.B) {
|
||||
fab := Right[error](func(a int) int { return a * 2 })
|
||||
fa := Right[error](42)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = MonadAp(fab, fa)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMonadAp_RightLeft(b *testing.B) {
|
||||
fab := Right[error](func(a int) int { return a * 2 })
|
||||
fa := Left[int](benchErr)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = MonadAp(fab, fa)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMonadAp_LeftRight(b *testing.B) {
|
||||
fab := Left[func(int) int](benchErr)
|
||||
fa := Right[error](42)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = MonadAp(fab, fa)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAp_RightRight(b *testing.B) {
|
||||
fab := Right[error](func(a int) int { return a * 2 })
|
||||
fa := Right[error](42)
|
||||
ap := Ap[int, error, int](fa)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = ap(fab)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark alternative operations
|
||||
func BenchmarkAlt_RightRight(b *testing.B) {
|
||||
right := Right[error](42)
|
||||
alternative := Alt[error](func() Either[error, int] { return Right[error](99) })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = alternative(right)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAlt_LeftRight(b *testing.B) {
|
||||
left := Left[int](benchErr)
|
||||
alternative := Alt[error](func() Either[error, int] { return Right[error](99) })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = alternative(left)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkOrElse_Right(b *testing.B) {
|
||||
right := Right[error](42)
|
||||
recover := OrElse[error](func(e error) Either[error, int] { return Right[error](0) })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = recover(right)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkOrElse_Left(b *testing.B) {
|
||||
left := Left[int](benchErr)
|
||||
recover := OrElse[error](func(e error) Either[error, int] { return Right[error](0) })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = recover(left)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark conversion operations
|
||||
func BenchmarkTryCatch_Success(b *testing.B) {
|
||||
onThrow := func(err error) error { return err }
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = TryCatch(42, nil, onThrow)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTryCatch_Error(b *testing.B) {
|
||||
onThrow := func(err error) error { return err }
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = TryCatch(0, benchErr, onThrow)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTryCatchError_Success(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = TryCatchError(42, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTryCatchError_Error(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = TryCatchError(0, benchErr)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSwap_Right(b *testing.B) {
|
||||
right := Right[error](42)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = Swap(right)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSwap_Left(b *testing.B) {
|
||||
left := Left[int](benchErr)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = Swap(left)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGetOrElse_Right(b *testing.B) {
|
||||
right := Right[error](42)
|
||||
getter := GetOrElse[error](func(e error) int { return 0 })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchInt = getter(right)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGetOrElse_Left(b *testing.B) {
|
||||
left := Left[int](benchErr)
|
||||
getter := GetOrElse[error](func(e error) int { return 0 })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchInt = getter(left)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark pipeline operations
|
||||
func BenchmarkPipeline_Map_Right(b *testing.B) {
|
||||
right := Right[error](21)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = F.Pipe1(
|
||||
right,
|
||||
Map[error](func(x int) int { return x * 2 }),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPipeline_Map_Left(b *testing.B) {
|
||||
left := Left[int](benchErr)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = F.Pipe1(
|
||||
left,
|
||||
Map[error](func(x int) int { return x * 2 }),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPipeline_Chain_Right(b *testing.B) {
|
||||
right := Right[error](21)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = F.Pipe1(
|
||||
right,
|
||||
Chain[error](func(x int) Either[error, int] { return Right[error](x * 2) }),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPipeline_Chain_Left(b *testing.B) {
|
||||
left := Left[int](benchErr)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = F.Pipe1(
|
||||
left,
|
||||
Chain[error](func(x int) Either[error, int] { return Right[error](x * 2) }),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPipeline_Complex_Right(b *testing.B) {
|
||||
right := Right[error](10)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = F.Pipe3(
|
||||
right,
|
||||
Map[error](func(x int) int { return x * 2 }),
|
||||
Chain[error](func(x int) Either[error, int] { return Right[error](x + 1) }),
|
||||
Map[error](func(x int) int { return x * 2 }),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPipeline_Complex_Left(b *testing.B) {
|
||||
left := Left[int](benchErr)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = F.Pipe3(
|
||||
left,
|
||||
Map[error](func(x int) int { return x * 2 }),
|
||||
Chain[error](func(x int) Either[error, int] { return Right[error](x + 1) }),
|
||||
Map[error](func(x int) int { return x * 2 }),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark sequence operations
|
||||
func BenchmarkMonadSequence2_RightRight(b *testing.B) {
|
||||
e1 := Right[error](10)
|
||||
e2 := Right[error](20)
|
||||
f := func(a, b int) Either[error, int] { return Right[error](a + b) }
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = MonadSequence2(e1, e2, f)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMonadSequence2_LeftRight(b *testing.B) {
|
||||
e1 := Left[int](benchErr)
|
||||
e2 := Right[error](20)
|
||||
f := func(a, b int) Either[error, int] { return Right[error](a + b) }
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = MonadSequence2(e1, e2, f)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMonadSequence3_RightRightRight(b *testing.B) {
|
||||
e1 := Right[error](10)
|
||||
e2 := Right[error](20)
|
||||
e3 := Right[error](30)
|
||||
f := func(a, b, c int) Either[error, int] { return Right[error](a + b + c) }
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = MonadSequence3(e1, e2, e3, f)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark do-notation operations
|
||||
func BenchmarkDo(b *testing.B) {
|
||||
type State struct{ value int }
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = Do[error](State{})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBind_Right(b *testing.B) {
|
||||
type State struct{ value int }
|
||||
initial := Do[error](State{})
|
||||
binder := Bind[error, State, State](
|
||||
func(v int) func(State) State {
|
||||
return func(s State) State { return State{value: v} }
|
||||
},
|
||||
func(s State) Either[error, int] {
|
||||
return Right[error](42)
|
||||
},
|
||||
)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = binder(initial)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkLet_Right(b *testing.B) {
|
||||
type State struct{ value int }
|
||||
initial := Right[error](State{value: 10})
|
||||
letter := Let[error, State, State](
|
||||
func(v int) func(State) State {
|
||||
return func(s State) State { return State{value: s.value + v} }
|
||||
},
|
||||
func(s State) int { return 32 },
|
||||
)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = letter(initial)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark string formatting
|
||||
func BenchmarkString_Right(b *testing.B) {
|
||||
right := Right[error](42)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchString = right.String()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkString_Left(b *testing.B) {
|
||||
left := Left[int](benchErr)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchString = left.String()
|
||||
}
|
||||
}
|
||||
|
||||
// Made with Bob
|
||||
@@ -117,7 +117,7 @@ func TestStringer(t *testing.T) {
|
||||
|
||||
assert.Equal(t, exp, e.String())
|
||||
|
||||
var s fmt.Stringer = e
|
||||
var s fmt.Stringer = &e
|
||||
assert.Equal(t, exp, s.String())
|
||||
}
|
||||
|
||||
|
||||
1701
v2/function/gen.go
1701
v2/function/gen.go
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user