mirror of
https://github.com/IBM/fp-go.git
synced 2025-12-07 23:03:15 +02:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c742b81e6 | ||
|
|
a1d6c94b15 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,4 +2,5 @@ fp-go.exe
|
||||
fp-go
|
||||
main.exe
|
||||
build/
|
||||
.idea
|
||||
.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_full_struct.txt
Normal file
66
v2/either/bench_full_struct.txt
Normal file
@@ -0,0 +1,66 @@
|
||||
2025/11/08 09:58:27 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.8874 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkRight-16 1000000000 0.5417 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkOf-16 1000000000 0.5751 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkIsLeft-16 1000000000 0.2564 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkIsRight-16 1000000000 0.2269 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMonadFold_Right-16 1000000000 0.2320 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMonadFold_Left-16 1000000000 0.2322 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkFold_Right-16 1000000000 0.2258 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkFold_Left-16 1000000000 0.2313 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkUnwrap_Right-16 1000000000 0.2263 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkUnwrap_Left-16 1000000000 0.2294 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkUnwrapError_Right-16 1000000000 0.2228 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkUnwrapError_Left-16 1000000000 0.2253 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMonadMap_Right-16 174464096 6.904 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMonadMap_Left-16 268986886 4.544 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMap_Right-16 183861918 7.383 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMap_Left-16 275134764 4.550 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMapLeft_Right-16 296297173 4.534 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMapLeft_Left-16 157772442 9.138 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkBiMap_Right-16 29180962 42.42 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkBiMap_Left-16 28203771 42.58 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMonadChain_Right-16 461887719 2.479 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMonadChain_Left-16 572442338 1.964 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkChain_Right-16 1000000000 0.8653 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkChain_Left-16 1000000000 0.7443 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkChainFirst_Right-16 10762824 126.3 ns/op 120 B/op 5 allocs/op
|
||||
BenchmarkChainFirst_Left-16 253755582 4.394 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkFlatten_Right-16 100000000 10.23 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkFlatten_Left-16 100000000 10.09 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMonadAp_RightRight-16 176284840 6.734 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMonadAp_RightLeft-16 434471394 2.874 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMonadAp_LeftRight-16 451601320 2.795 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkAp_RightRight-16 177040815 6.786 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkAlt_RightRight-16 1000000000 0.8134 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkAlt_LeftRight-16 1000000000 0.8202 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkOrElse_Right-16 1000000000 0.8501 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkOrElse_Left-16 1000000000 0.6825 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkTryCatch_Success-16 565892258 2.091 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkTryCatch_Error-16 408437362 2.905 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkTryCatchError_Success-16 562500350 2.150 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkTryCatchError_Error-16 424754853 2.769 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkSwap_Right-16 1000000000 1.173 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkSwap_Left-16 995449963 1.162 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkGetOrElse_Right-16 1000000000 0.2427 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkGetOrElse_Left-16 1000000000 0.2457 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkPipeline_Map_Right-16 12972158 86.78 ns/op 96 B/op 4 allocs/op
|
||||
BenchmarkPipeline_Map_Left-16 13715413 86.44 ns/op 96 B/op 4 allocs/op
|
||||
BenchmarkPipeline_Chain_Right-16 1000000000 0.8321 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkPipeline_Chain_Left-16 1000000000 0.6849 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkPipeline_Complex_Right-16 6963496 173.1 ns/op 192 B/op 8 allocs/op
|
||||
BenchmarkPipeline_Complex_Left-16 6503500 174.6 ns/op 192 B/op 8 allocs/op
|
||||
BenchmarkMonadSequence2_RightRight-16 236820728 5.060 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMonadSequence2_LeftRight-16 545203750 2.215 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMonadSequence3_RightRightRight-16 120316371 9.657 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkDo-16 1000000000 0.2182 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkBind_Right-16 8784483 137.3 ns/op 144 B/op 6 allocs/op
|
||||
BenchmarkLet_Right-16 51356451 23.61 ns/op 16 B/op 1 allocs/op
|
||||
BenchmarkString_Right-16 15664588 79.59 ns/op 16 B/op 1 allocs/op
|
||||
BenchmarkString_Left-16 12226196 103.0 ns/op 48 B/op 1 allocs/op
|
||||
PASS
|
||||
ok github.com/IBM/fp-go/v2/either 66.778s
|
||||
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
|
||||
@@ -20,47 +20,36 @@ import (
|
||||
)
|
||||
|
||||
type (
|
||||
either struct {
|
||||
isLeft bool
|
||||
value any
|
||||
}
|
||||
|
||||
// Either defines a data structure that logically holds either an E or an A. The flag discriminates the cases
|
||||
Either[E, A any] either
|
||||
Either[E, A any] struct {
|
||||
r A
|
||||
l E
|
||||
isL bool
|
||||
}
|
||||
)
|
||||
|
||||
// String prints some debug info for the object
|
||||
//
|
||||
//go:noinline
|
||||
func eitherString(s *either) string {
|
||||
if s.isLeft {
|
||||
return fmt.Sprintf("Left[%T](%v)", s.value, s.value)
|
||||
func (s Either[E, A]) String() string {
|
||||
if !s.isL {
|
||||
return fmt.Sprintf("Right[%T](%v)", s.r, s.r)
|
||||
}
|
||||
return fmt.Sprintf("Right[%T](%v)", s.value, s.value)
|
||||
return fmt.Sprintf("Left[%T](%v)", s.l, s.l)
|
||||
}
|
||||
|
||||
// Format prints some debug info for the object
|
||||
//
|
||||
//go:noinline
|
||||
func eitherFormat(e *either, f fmt.State, c rune) {
|
||||
func (s Either[E, A]) Format(f fmt.State, c rune) {
|
||||
switch c {
|
||||
case 's':
|
||||
fmt.Fprint(f, eitherString(e))
|
||||
fmt.Fprint(f, s.String())
|
||||
default:
|
||||
fmt.Fprint(f, eitherString(e))
|
||||
fmt.Fprint(f, s.String())
|
||||
}
|
||||
}
|
||||
|
||||
// String prints some debug info for the object
|
||||
func (s Either[E, A]) String() string {
|
||||
return eitherString((*either)(&s))
|
||||
}
|
||||
|
||||
// Format prints some debug info for the object
|
||||
func (s Either[E, A]) Format(f fmt.State, c rune) {
|
||||
eitherFormat((*either)(&s), f, c)
|
||||
}
|
||||
|
||||
// IsLeft tests if the Either is a Left value.
|
||||
// Rather use [Fold] or [MonadFold] if you need to access the values.
|
||||
// Inverse is [IsRight].
|
||||
@@ -72,7 +61,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.isL
|
||||
}
|
||||
|
||||
// IsRight tests if the Either is a Right value.
|
||||
@@ -86,7 +75,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.isL
|
||||
}
|
||||
|
||||
// Left creates a new Either representing a Left (error/failure) value.
|
||||
@@ -98,7 +87,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]{l: value, isL: true}
|
||||
}
|
||||
|
||||
// Right creates a new Either representing a Right (success) value.
|
||||
@@ -110,7 +99,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]{r: value}
|
||||
}
|
||||
|
||||
// MonadFold extracts the value from an Either by providing handlers for both cases.
|
||||
@@ -126,10 +115,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.isL {
|
||||
return onRight(ma.r)
|
||||
}
|
||||
return onRight(ma.value.(A))
|
||||
return onLeft(ma.l)
|
||||
}
|
||||
|
||||
// Unwrap converts an Either into the idiomatic Go tuple (value, error).
|
||||
@@ -143,11 +132,5 @@ 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 {
|
||||
var e E
|
||||
return ma.value.(A), e
|
||||
}
|
||||
return ma.r, ma.l
|
||||
}
|
||||
|
||||
154
v2/either/core_any.go
Normal file
154
v2/either/core_any.go
Normal file
@@ -0,0 +1,154 @@
|
||||
// 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_any
|
||||
|
||||
package either
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type (
|
||||
either struct {
|
||||
value any
|
||||
isRight bool
|
||||
}
|
||||
|
||||
// Either defines a data structure that logically holds either an E or an A. The flag discriminates the cases
|
||||
Either[E, A any] either
|
||||
)
|
||||
|
||||
// String prints some debug info for the object
|
||||
//
|
||||
//go:noinline
|
||||
func eitherString(s *either) string {
|
||||
if s.isRight {
|
||||
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
|
||||
//
|
||||
//go:noinline
|
||||
func eitherFormat(e *either, 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((*either)(&s))
|
||||
}
|
||||
|
||||
// Format prints some debug info for the object
|
||||
func (s Either[E, A]) Format(f fmt.State, c rune) {
|
||||
eitherFormat((*either)(&s), f, c)
|
||||
}
|
||||
|
||||
// IsLeft tests if the Either is a Left value.
|
||||
// Rather use [Fold] or [MonadFold] if you need to access the values.
|
||||
// Inverse is [IsRight].
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// either.IsLeft(either.Left[int](errors.New("err"))) // true
|
||||
// either.IsLeft(either.Right[error](42)) // false
|
||||
//
|
||||
//go:inline
|
||||
func IsLeft[E, A any](val Either[E, A]) bool {
|
||||
return !val.isRight
|
||||
}
|
||||
|
||||
// IsRight tests if the Either is a Right value.
|
||||
// Rather use [Fold] or [MonadFold] if you need to access the values.
|
||||
// Inverse is [IsLeft].
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// either.IsRight(either.Right[error](42)) // true
|
||||
// either.IsRight(either.Left[int](errors.New("err"))) // false
|
||||
//
|
||||
//go:inline
|
||||
func IsRight[E, A any](val Either[E, A]) bool {
|
||||
return val.isRight
|
||||
}
|
||||
|
||||
// Left creates a new Either representing a Left (error/failure) value.
|
||||
// By convention, Left represents the error case.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// result := either.Left[int](errors.New("something went wrong"))
|
||||
//
|
||||
//go:inline
|
||||
func Left[A, E any](value E) Either[E, A] {
|
||||
return Either[E, A]{value, false}
|
||||
}
|
||||
|
||||
// Right creates a new Either representing a Right (success) value.
|
||||
// By convention, Right represents the success case.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// result := either.Right[error](42)
|
||||
//
|
||||
//go:inline
|
||||
func Right[E, A any](value A) Either[E, A] {
|
||||
return Either[E, A]{value, true}
|
||||
}
|
||||
|
||||
// MonadFold extracts the value from an Either by providing handlers for both cases.
|
||||
// This is the fundamental pattern matching operation for Either.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// result := either.MonadFold(
|
||||
// either.Right[error](42),
|
||||
// func(err error) string { return "Error: " + err.Error() },
|
||||
// func(n int) string { return fmt.Sprintf("Value: %d", n) },
|
||||
// ) // "Value: 42"
|
||||
//
|
||||
//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.isRight {
|
||||
return onRight(ma.value.(A))
|
||||
}
|
||||
return onLeft(ma.value.(E))
|
||||
}
|
||||
|
||||
// Unwrap converts an Either into the idiomatic Go tuple (value, error).
|
||||
// For Right values, returns (value, zero-error).
|
||||
// For Left values, returns (zero-value, error).
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// val, err := either.Unwrap(either.Right[error](42)) // 42, nil
|
||||
// val, err := either.Unwrap(either.Left[int](errors.New("fail"))) // 0, error
|
||||
//
|
||||
//go:inline
|
||||
func Unwrap[E, A any](ma Either[E, A]) (A, E) {
|
||||
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
@@ -17,6 +17,7 @@
|
||||
package lens
|
||||
|
||||
import (
|
||||
EQ "github.com/IBM/fp-go/v2/eq"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
)
|
||||
|
||||
@@ -29,6 +30,17 @@ func setCopy[SET ~func(*S, A) *S, S, A any](setter SET) func(s *S, a A) *S {
|
||||
}
|
||||
}
|
||||
|
||||
func setCopyWithEq[GET ~func(*S) A, SET ~func(*S, A) *S, S, A any](pred EQ.Eq[A], getter GET, setter SET) func(s *S, a A) *S {
|
||||
return func(s *S, a A) *S {
|
||||
if pred.Equals(getter(s), a) {
|
||||
return s
|
||||
}
|
||||
// we need to make a copy
|
||||
cpy := *s
|
||||
return setter(&cpy, a)
|
||||
}
|
||||
}
|
||||
|
||||
// setCopyCurried wraps a setter for a pointer into a setter that first creates a copy before
|
||||
// modifying that copy
|
||||
func setCopyCurried[SET ~func(A) Endomorphism[*S], S, A any](setter SET) func(a A) Endomorphism[*S] {
|
||||
@@ -161,6 +173,115 @@ func MakeLensRef[GET ~func(*S) A, SET func(*S, A) *S, S, A any](get GET, set SET
|
||||
return MakeLens(get, setCopy(set))
|
||||
}
|
||||
|
||||
// MakeLensWithEq creates a [Lens] for pointer-based structures with equality optimization.
|
||||
//
|
||||
// This is similar to [MakeLensRef] but includes an optimization: if the new value equals
|
||||
// the current value (according to the provided Eq predicate), the original pointer is returned
|
||||
// unchanged instead of creating a copy. This can improve performance and reduce allocations
|
||||
// when setting values that don't actually change the structure.
|
||||
//
|
||||
// The setter does not need to create a copy manually; this function automatically wraps it
|
||||
// to ensure immutability when changes are made.
|
||||
//
|
||||
// This lens assumes that property A always exists in structure S (i.e., it's not optional).
|
||||
//
|
||||
// Type Parameters:
|
||||
// - GET: Getter function type (*S → A)
|
||||
// - SET: Setter function type (*S, A → *S)
|
||||
// - S: Source structure type (will be used as *S)
|
||||
// - A: Focus/field type
|
||||
//
|
||||
// Parameters:
|
||||
// - pred: Equality predicate to compare values of type A
|
||||
// - get: Function to extract value A from pointer *S
|
||||
// - set: Function to update value A in pointer *S (copying handled automatically)
|
||||
//
|
||||
// Returns:
|
||||
// - A Lens[*S, A] that can get and set values immutably on pointers with equality optimization
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type Person struct {
|
||||
// Name string
|
||||
// Age int
|
||||
// }
|
||||
//
|
||||
// nameLens := lens.MakeLensWithEq(
|
||||
// eq.FromStrictEquals[string](),
|
||||
// func(p *Person) string { return p.Name },
|
||||
// func(p *Person, name string) *Person {
|
||||
// p.Name = name // No manual copy needed
|
||||
// return p
|
||||
// },
|
||||
// )
|
||||
//
|
||||
// person := &Person{Name: "Alice", Age: 30}
|
||||
//
|
||||
// // Setting the same value returns the original pointer (no copy)
|
||||
// same := nameLens.Set("Alice")(person)
|
||||
// // same == person (same pointer)
|
||||
//
|
||||
// // Setting a different value creates a new copy
|
||||
// updated := nameLens.Set("Bob")(person)
|
||||
// // person.Name is still "Alice", updated is a new pointer with Name "Bob"
|
||||
func MakeLensWithEq[GET ~func(*S) A, SET func(*S, A) *S, S, A any](pred EQ.Eq[A], get GET, set SET) Lens[*S, A] {
|
||||
return MakeLens(get, setCopyWithEq(pred, get, set))
|
||||
}
|
||||
|
||||
// MakeLensStrict creates a [Lens] for pointer-based structures with strict equality optimization.
|
||||
//
|
||||
// This is a convenience function that combines [MakeLensWithEq] with strict equality comparison (==).
|
||||
// It's suitable for comparable types (primitives, strings, pointers, etc.) and provides the same
|
||||
// optimization as MakeLensWithEq: if the new value equals the current value, the original pointer
|
||||
// is returned unchanged instead of creating a copy.
|
||||
//
|
||||
// The setter does not need to create a copy manually; this function automatically wraps it
|
||||
// to ensure immutability when changes are made.
|
||||
//
|
||||
// This lens assumes that property A always exists in structure S (i.e., it's not optional).
|
||||
//
|
||||
// Type Parameters:
|
||||
// - GET: Getter function type (*S → A)
|
||||
// - SET: Setter function type (*S, A → *S)
|
||||
// - S: Source structure type (will be used as *S)
|
||||
// - A: Focus/field type (must be comparable)
|
||||
//
|
||||
// Parameters:
|
||||
// - get: Function to extract value A from pointer *S
|
||||
// - set: Function to update value A in pointer *S (copying handled automatically)
|
||||
//
|
||||
// Returns:
|
||||
// - A Lens[*S, A] that can get and set values immutably on pointers with strict equality optimization
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type Person struct {
|
||||
// Name string
|
||||
// Age int
|
||||
// }
|
||||
//
|
||||
// // Using MakeLensStrict for a string field (comparable type)
|
||||
// nameLens := lens.MakeLensStrict(
|
||||
// func(p *Person) string { return p.Name },
|
||||
// func(p *Person, name string) *Person {
|
||||
// p.Name = name // No manual copy needed
|
||||
// return p
|
||||
// },
|
||||
// )
|
||||
//
|
||||
// person := &Person{Name: "Alice", Age: 30}
|
||||
//
|
||||
// // Setting the same value returns the original pointer (no copy)
|
||||
// same := nameLens.Set("Alice")(person)
|
||||
// // same == person (same pointer)
|
||||
//
|
||||
// // Setting a different value creates a new copy
|
||||
// updated := nameLens.Set("Bob")(person)
|
||||
// // person.Name is still "Alice", updated is a new pointer with Name "Bob"
|
||||
func MakeLensStrict[GET ~func(*S) A, SET func(*S, A) *S, S any, A comparable](get GET, set SET) Lens[*S, A] {
|
||||
return MakeLensWithEq(EQ.FromStrictEquals[A](), get, set)
|
||||
}
|
||||
|
||||
// MakeLensRefCurried creates a [Lens] for pointer-based structures with a curried setter.
|
||||
//
|
||||
// This combines the benefits of [MakeLensRef] (automatic copying) with [MakeLensCurried]
|
||||
|
||||
@@ -18,6 +18,7 @@ package lens
|
||||
import (
|
||||
"testing"
|
||||
|
||||
EQ "github.com/IBM/fp-go/v2/eq"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -231,3 +232,315 @@ func TestMakeLensRefCurried(t *testing.T) {
|
||||
assert.Equal(t, "Oak", updated.name)
|
||||
assert.Equal(t, "Main", street.name)
|
||||
}
|
||||
|
||||
func TestMakeLensWithEq(t *testing.T) {
|
||||
// Create a lens with equality check for string
|
||||
nameLens := MakeLensWithEq(
|
||||
EQ.FromStrictEquals[string](),
|
||||
func(s *Street) string { return s.name },
|
||||
func(s *Street, name string) *Street {
|
||||
s.name = name
|
||||
return s
|
||||
},
|
||||
)
|
||||
|
||||
street := &Street{num: 1, name: "Main"}
|
||||
|
||||
// Test getting value
|
||||
assert.Equal(t, "Main", nameLens.Get(street))
|
||||
|
||||
// Test setting a different value - should create a new copy
|
||||
updated := nameLens.Set("Oak")(street)
|
||||
assert.Equal(t, "Oak", updated.name)
|
||||
assert.Equal(t, "Main", street.name) // Original unchanged
|
||||
assert.NotSame(t, street, updated) // Different pointers
|
||||
|
||||
// Test setting the same value - should return original pointer (optimization)
|
||||
same := nameLens.Set("Main")(street)
|
||||
assert.Equal(t, "Main", same.name)
|
||||
assert.Same(t, street, same) // Same pointer (no copy made)
|
||||
}
|
||||
|
||||
func TestMakeLensWithEq_IntField(t *testing.T) {
|
||||
// Create a lens with equality check for int
|
||||
numLens := MakeLensWithEq(
|
||||
EQ.FromStrictEquals[int](),
|
||||
func(s *Street) int { return s.num },
|
||||
func(s *Street, num int) *Street {
|
||||
s.num = num
|
||||
return s
|
||||
},
|
||||
)
|
||||
|
||||
street := &Street{num: 42, name: "Main"}
|
||||
|
||||
// Test getting value
|
||||
assert.Equal(t, 42, numLens.Get(street))
|
||||
|
||||
// Test setting a different value
|
||||
updated := numLens.Set(100)(street)
|
||||
assert.Equal(t, 100, updated.num)
|
||||
assert.Equal(t, 42, street.num)
|
||||
assert.NotSame(t, street, updated)
|
||||
|
||||
// Test setting the same value - should return original pointer
|
||||
same := numLens.Set(42)(street)
|
||||
assert.Equal(t, 42, same.num)
|
||||
assert.Same(t, street, same)
|
||||
}
|
||||
|
||||
func TestMakeLensWithEq_CustomEq(t *testing.T) {
|
||||
// Create a custom equality that ignores case
|
||||
caseInsensitiveEq := EQ.FromEquals(func(a, b string) bool {
|
||||
return len(a) == len(b) && a == b // Simple equality for this test
|
||||
})
|
||||
|
||||
nameLens := MakeLensWithEq(
|
||||
caseInsensitiveEq,
|
||||
func(s *Street) string { return s.name },
|
||||
func(s *Street, name string) *Street {
|
||||
s.name = name
|
||||
return s
|
||||
},
|
||||
)
|
||||
|
||||
street := &Street{num: 1, name: "Main"}
|
||||
|
||||
// Setting the exact same value should return original pointer
|
||||
same := nameLens.Set("Main")(street)
|
||||
assert.Same(t, street, same)
|
||||
|
||||
// Setting a different value should create a copy
|
||||
updated := nameLens.Set("Oak")(street)
|
||||
assert.NotSame(t, street, updated)
|
||||
assert.Equal(t, "Main", street.name)
|
||||
assert.Equal(t, "Oak", updated.name)
|
||||
}
|
||||
|
||||
func TestMakeLensWithEq_ComposedLens(t *testing.T) {
|
||||
// Create lenses with equality optimization
|
||||
streetLensEq := MakeLensWithEq(
|
||||
EQ.FromStrictEquals[string](),
|
||||
(*Street).GetName,
|
||||
(*Street).SetName,
|
||||
)
|
||||
|
||||
addrLensEq := MakeLensWithEq(
|
||||
EQ.FromStrictEquals[*Street](),
|
||||
(*Address).GetStreet,
|
||||
(*Address).SetStreet,
|
||||
)
|
||||
|
||||
// Compose the lenses
|
||||
streetName := Compose[*Address](streetLensEq)(addrLensEq)
|
||||
|
||||
sampleStreet := Street{num: 220, name: "Schönaicherstr"}
|
||||
sampleAddress := Address{city: "Böblingen", street: &sampleStreet}
|
||||
|
||||
// Test getting value
|
||||
assert.Equal(t, sampleStreet.name, streetName.Get(&sampleAddress))
|
||||
|
||||
// Test setting a different value
|
||||
newName := "Böblingerstr"
|
||||
updated := streetName.Set(newName)(&sampleAddress)
|
||||
assert.Equal(t, newName, streetName.Get(updated))
|
||||
assert.Equal(t, sampleStreet.name, sampleAddress.street.name) // Original unchanged
|
||||
}
|
||||
|
||||
func TestMakeLensWithEq_MultipleUpdates(t *testing.T) {
|
||||
nameLens := MakeLensWithEq(
|
||||
EQ.FromStrictEquals[string](),
|
||||
func(s *Street) string { return s.name },
|
||||
func(s *Street, name string) *Street {
|
||||
s.name = name
|
||||
return s
|
||||
},
|
||||
)
|
||||
|
||||
street := &Street{num: 1, name: "Main"}
|
||||
|
||||
// First update - creates a copy
|
||||
updated1 := nameLens.Set("Oak")(street)
|
||||
assert.NotSame(t, street, updated1)
|
||||
assert.Equal(t, "Oak", updated1.name)
|
||||
|
||||
// Second update with same value - returns same pointer
|
||||
updated2 := nameLens.Set("Oak")(updated1)
|
||||
assert.Same(t, updated1, updated2)
|
||||
|
||||
// Third update with different value - creates new copy
|
||||
updated3 := nameLens.Set("Elm")(updated2)
|
||||
assert.NotSame(t, updated2, updated3)
|
||||
assert.Equal(t, "Elm", updated3.name)
|
||||
assert.Equal(t, "Oak", updated2.name)
|
||||
}
|
||||
|
||||
func TestMakeLensStrict(t *testing.T) {
|
||||
// Create a lens with strict equality for string
|
||||
nameLens := MakeLensStrict(
|
||||
func(s *Street) string { return s.name },
|
||||
func(s *Street, name string) *Street {
|
||||
s.name = name
|
||||
return s
|
||||
},
|
||||
)
|
||||
|
||||
street := &Street{num: 1, name: "Main"}
|
||||
|
||||
// Test getting value
|
||||
assert.Equal(t, "Main", nameLens.Get(street))
|
||||
|
||||
// Test setting a different value - should create a new copy
|
||||
updated := nameLens.Set("Oak")(street)
|
||||
assert.Equal(t, "Oak", updated.name)
|
||||
assert.Equal(t, "Main", street.name) // Original unchanged
|
||||
assert.NotSame(t, street, updated) // Different pointers
|
||||
|
||||
// Test setting the same value - should return original pointer (optimization)
|
||||
same := nameLens.Set("Main")(street)
|
||||
assert.Equal(t, "Main", same.name)
|
||||
assert.Same(t, street, same) // Same pointer (no copy made)
|
||||
}
|
||||
|
||||
func TestMakeLensStrict_IntField(t *testing.T) {
|
||||
// Create a lens with strict equality for int
|
||||
numLens := MakeLensStrict(
|
||||
func(s *Street) int { return s.num },
|
||||
func(s *Street, num int) *Street {
|
||||
s.num = num
|
||||
return s
|
||||
},
|
||||
)
|
||||
|
||||
street := &Street{num: 42, name: "Main"}
|
||||
|
||||
// Test getting value
|
||||
assert.Equal(t, 42, numLens.Get(street))
|
||||
|
||||
// Test setting a different value
|
||||
updated := numLens.Set(100)(street)
|
||||
assert.Equal(t, 100, updated.num)
|
||||
assert.Equal(t, 42, street.num)
|
||||
assert.NotSame(t, street, updated)
|
||||
|
||||
// Test setting the same value - should return original pointer
|
||||
same := numLens.Set(42)(street)
|
||||
assert.Equal(t, 42, same.num)
|
||||
assert.Same(t, street, same)
|
||||
}
|
||||
|
||||
func TestMakeLensStrict_PointerField(t *testing.T) {
|
||||
// Test with pointer field (comparable type)
|
||||
type Container struct {
|
||||
ptr *int
|
||||
}
|
||||
|
||||
ptrLens := MakeLensStrict(
|
||||
func(c *Container) *int { return c.ptr },
|
||||
func(c *Container, ptr *int) *Container {
|
||||
c.ptr = ptr
|
||||
return c
|
||||
},
|
||||
)
|
||||
|
||||
value1 := 42
|
||||
value2 := 100
|
||||
container := &Container{ptr: &value1}
|
||||
|
||||
// Test getting value
|
||||
assert.Equal(t, &value1, ptrLens.Get(container))
|
||||
|
||||
// Test setting a different pointer
|
||||
updated := ptrLens.Set(&value2)(container)
|
||||
assert.Equal(t, &value2, updated.ptr)
|
||||
assert.Equal(t, &value1, container.ptr)
|
||||
assert.NotSame(t, container, updated)
|
||||
|
||||
// Test setting the same pointer - should return original
|
||||
same := ptrLens.Set(&value1)(container)
|
||||
assert.Same(t, container, same)
|
||||
}
|
||||
|
||||
func TestMakeLensStrict_ComposedLens(t *testing.T) {
|
||||
// Create lenses with strict equality optimization
|
||||
streetLensStrict := MakeLensStrict(
|
||||
(*Street).GetName,
|
||||
(*Street).SetName,
|
||||
)
|
||||
|
||||
addrLensStrict := MakeLensStrict(
|
||||
(*Address).GetStreet,
|
||||
(*Address).SetStreet,
|
||||
)
|
||||
|
||||
// Compose the lenses
|
||||
streetName := Compose[*Address](streetLensStrict)(addrLensStrict)
|
||||
|
||||
sampleStreet := Street{num: 220, name: "Schönaicherstr"}
|
||||
sampleAddress := Address{city: "Böblingen", street: &sampleStreet}
|
||||
|
||||
// Test getting value
|
||||
assert.Equal(t, sampleStreet.name, streetName.Get(&sampleAddress))
|
||||
|
||||
// Test setting a different value
|
||||
newName := "Böblingerstr"
|
||||
updated := streetName.Set(newName)(&sampleAddress)
|
||||
assert.Equal(t, newName, streetName.Get(updated))
|
||||
assert.Equal(t, sampleStreet.name, sampleAddress.street.name) // Original unchanged
|
||||
}
|
||||
|
||||
func TestMakeLensStrict_MultipleUpdates(t *testing.T) {
|
||||
nameLens := MakeLensStrict(
|
||||
func(s *Street) string { return s.name },
|
||||
func(s *Street, name string) *Street {
|
||||
s.name = name
|
||||
return s
|
||||
},
|
||||
)
|
||||
|
||||
street := &Street{num: 1, name: "Main"}
|
||||
|
||||
// First update - creates a copy
|
||||
updated1 := nameLens.Set("Oak")(street)
|
||||
assert.NotSame(t, street, updated1)
|
||||
assert.Equal(t, "Oak", updated1.name)
|
||||
|
||||
// Second update with same value - returns same pointer
|
||||
updated2 := nameLens.Set("Oak")(updated1)
|
||||
assert.Same(t, updated1, updated2)
|
||||
|
||||
// Third update with different value - creates new copy
|
||||
updated3 := nameLens.Set("Elm")(updated2)
|
||||
assert.NotSame(t, updated2, updated3)
|
||||
assert.Equal(t, "Elm", updated3.name)
|
||||
assert.Equal(t, "Oak", updated2.name)
|
||||
}
|
||||
|
||||
func TestMakeLensStrict_BoolField(t *testing.T) {
|
||||
type Config struct {
|
||||
enabled bool
|
||||
}
|
||||
|
||||
enabledLens := MakeLensStrict(
|
||||
func(c *Config) bool { return c.enabled },
|
||||
func(c *Config, enabled bool) *Config {
|
||||
c.enabled = enabled
|
||||
return c
|
||||
},
|
||||
)
|
||||
|
||||
config := &Config{enabled: true}
|
||||
|
||||
// Test getting value
|
||||
assert.True(t, enabledLens.Get(config))
|
||||
|
||||
// Test setting a different value
|
||||
updated := enabledLens.Set(false)(config)
|
||||
assert.False(t, updated.enabled)
|
||||
assert.True(t, config.enabled)
|
||||
assert.NotSame(t, config, updated)
|
||||
|
||||
// Test setting the same value - should return original pointer
|
||||
same := enabledLens.Set(true)(config)
|
||||
assert.Same(t, config, same)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user