1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-12-07 23:03:15 +02:00

Compare commits

...

2 Commits

Author SHA1 Message Date
Dr. Carsten Leue
8d92df83ad fix: introduce Result type for convenience
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-10 12:08:50 +01:00
Dr. Carsten Leue
0c742b81e6 fix: better performance for either
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-08 10:01:14 +01:00
49 changed files with 5069 additions and 457 deletions

View File

@@ -1,277 +0,0 @@
# 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.

View File

@@ -1,66 +0,0 @@
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

View File

@@ -1,66 +0,0 @@
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

View File

@@ -58,8 +58,8 @@ func Do[E, S any](
//go:inline
func Bind[E, S1, S2, T any](
setter func(T) func(S1) S2,
f func(S1) Either[E, T],
) func(Either[E, S1]) Either[E, S2] {
f Kleisli[E, S1, T],
) Operator[E, S1, S2] {
return C.Bind(
Chain[E, S1, S2],
Map[E, T, S2],
@@ -88,7 +88,7 @@ func Bind[E, S1, S2, T any](
func Let[E, S1, S2, T any](
key func(T) func(S1) S2,
f func(S1) T,
) func(Either[E, S1]) Either[E, S2] {
) Operator[E, S1, S2] {
return F.Let(
Map[E, S1, S2],
key,
@@ -115,7 +115,7 @@ func Let[E, S1, S2, T any](
func LetTo[E, S1, S2, T any](
key func(T) func(S1) S2,
b T,
) func(Either[E, S1]) Either[E, S2] {
) Operator[E, S1, S2] {
return F.LetTo(
Map[E, S1, S2],
key,
@@ -137,7 +137,7 @@ func LetTo[E, S1, S2, T any](
//go:inline
func BindTo[E, S1, T any](
setter func(T) S1,
) func(Either[E, T]) Either[E, S1] {
) Operator[E, T, S1] {
return C.BindTo(
Map[E, T, S1],
setter,
@@ -164,7 +164,7 @@ func BindTo[E, S1, T any](
func ApS[E, S1, S2, T any](
setter func(T) func(S1) S2,
fa Either[E, T],
) func(Either[E, S1]) Either[E, S2] {
) Operator[E, S1, S2] {
return A.ApS(
Ap[S2, E, T],
Map[E, S1, func(T) S2],
@@ -271,7 +271,7 @@ func ApSL[E, S, T any](
//go:inline
func BindL[E, S, T any](
lens Lens[S, T],
f func(T) Either[E, T],
f Kleisli[E, T, T],
) Endomorphism[Either[E, S]] {
return Bind[E, S, S, T](lens.Set, function.Flow2(lens.Get, f))
}

View File

@@ -12,7 +12,6 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !either_pointers
package either
@@ -21,47 +20,36 @@ import (
)
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
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.isRight {
return fmt.Sprintf("Right[%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("Left[%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].
@@ -73,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.isRight
return val.isL
}
// IsRight tests if the Either is a Right value.
@@ -87,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.isRight
return !val.isL
}
// Left creates a new Either representing a Left (error/failure) value.
@@ -99,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]{value, false}
return Either[E, A]{l: value, isL: true}
}
// Right creates a new Either representing a Right (success) value.
@@ -111,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]{value, true}
return Either[E, A]{r: value}
}
// MonadFold extracts the value from an Either by providing handlers for both cases.
@@ -127,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.isRight {
return onRight(ma.value.(A))
if !ma.isL {
return onRight(ma.r)
}
return onLeft(ma.value.(E))
return onLeft(ma.l)
}
// Unwrap converts an Either into the idiomatic Go tuple (value, error).
@@ -144,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.isRight {
var e E
return ma.value.(A), e
} else {
var a A
return a, ma.value.(E)
}
return ma.r, ma.l
}

154
v2/either/core_any.go Normal file
View 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)
}
}

View File

@@ -449,7 +449,7 @@ func Alt[E, A any](that L.Lazy[Either[E, A]]) Operator[E, A, A] {
// return either.Right[error](0) // default value
// })
// result := recover(either.Left[int](errors.New("fail"))) // Right(0)
func OrElse[E, A any](onLeft func(e E) Either[E, A]) Operator[E, A, A] {
func OrElse[E, A any](onLeft Kleisli[E, E, A]) Operator[E, A, A] {
return Fold(onLeft, Of[E, A])
}

View File

@@ -35,7 +35,7 @@ import (
// )(f)(eitherOfOption)
func Traverse[A, E, B, HKTB, HKTRB any](
mof func(Either[E, B]) HKTRB,
mmap func(func(B) Either[E, B]) func(HKTB) HKTRB,
mmap func(Kleisli[E, B, B]) func(HKTB) HKTRB,
) func(func(A) HKTB) func(Either[E, A]) HKTRB {
left := F.Flow2(Left[B, E], mof)
@@ -62,7 +62,7 @@ func Traverse[A, E, B, HKTB, HKTRB any](
// )(eitherOfOption)
func Sequence[E, A, HKTA, HKTRA any](
mof func(Either[E, A]) HKTRA,
mmap func(func(A) Either[E, A]) func(HKTA) HKTRA,
mmap func(Kleisli[E, A, A]) func(HKTA) HKTRA,
) func(Either[E, HKTA]) HKTRA {
return Fold(F.Flow2(Left[A, E], mof), mmap(Right[E, A]))
}

View File

@@ -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]

View File

@@ -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)
}

51
v2/result/apply.go Normal file
View File

@@ -0,0 +1,51 @@
// 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 result
import (
"github.com/IBM/fp-go/v2/either"
M "github.com/IBM/fp-go/v2/monoid"
S "github.com/IBM/fp-go/v2/semigroup"
)
// ApplySemigroup lifts a Semigroup over the Right values of Either.
// Combines two Right values using the provided Semigroup.
// If either value is Left, returns the first Left encountered.
//
// Example:
//
// intAdd := semigroup.MakeSemigroup(func(a, b int) int { return a + b })
// eitherSemi := either.ApplySemigroup[error](intAdd)
// result := eitherSemi.Concat(either.Right[error](2), either.Right[error](3)) // Right(5)
//
//go:inline
func ApplySemigroup[A any](s S.Semigroup[A]) S.Semigroup[Result[A]] {
return either.ApplySemigroup[error](s)
}
// ApplicativeMonoid returns a Monoid that concatenates Either instances via their applicative.
// Provides an empty Either (Right with monoid's empty value) and combines Right values using the monoid.
//
// Example:
//
// intAddMonoid := monoid.MakeMonoid(0, func(a, b int) int { return a + b })
// eitherMon := either.ApplicativeMonoid[error](intAddMonoid)
// empty := eitherMon.Empty() // Right(0)
//
//go:inline
func ApplicativeMonoid[A any](m M.Monoid[A]) M.Monoid[Result[A]] {
return either.ApplicativeMonoid[error](m)
}

64
v2/result/apply_test.go Normal file
View File

@@ -0,0 +1,64 @@
// 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 result
import (
"errors"
"testing"
M "github.com/IBM/fp-go/v2/monoid/testing"
N "github.com/IBM/fp-go/v2/number"
"github.com/stretchr/testify/assert"
)
func TestApplySemigroup(t *testing.T) {
sg := ApplySemigroup(N.SemigroupSum[int]())
la := Left[int](errors.New("a"))
lb := Left[int](errors.New("b"))
r1 := Right(1)
r2 := Right(2)
r3 := Right(3)
assert.Equal(t, la, sg.Concat(la, lb))
assert.Equal(t, lb, sg.Concat(r1, lb))
assert.Equal(t, la, sg.Concat(la, r2))
assert.Equal(t, lb, sg.Concat(r1, lb))
assert.Equal(t, r3, sg.Concat(r1, r2))
}
func TestApplicativeMonoid(t *testing.T) {
m := ApplicativeMonoid(N.MonoidSum[int]())
la := Left[int](errors.New("a"))
lb := Left[int](errors.New("b"))
r1 := Right(1)
r2 := Right(2)
r3 := Right(3)
assert.Equal(t, la, m.Concat(la, m.Empty()))
assert.Equal(t, lb, m.Concat(m.Empty(), lb))
assert.Equal(t, r1, m.Concat(r1, m.Empty()))
assert.Equal(t, r2, m.Concat(m.Empty(), r2))
assert.Equal(t, r3, m.Concat(r1, r2))
}
func TestApplicativeMonoidLaws(t *testing.T) {
m := ApplicativeMonoid(N.MonoidSum[int]())
M.AssertLaws(t, m)([]Result[int]{Left[int](errors.New("a")), Right(1)})
}

157
v2/result/array.go Normal file
View File

@@ -0,0 +1,157 @@
// 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 result
import (
"github.com/IBM/fp-go/v2/either"
)
// TraverseArrayG transforms an array by applying a function that returns an Either to each element.
// If any element produces a Left, the entire result is that Left (short-circuits).
// Otherwise, returns Right containing the array of all Right values.
// The G suffix indicates support for generic slice types.
//
// Example:
//
// parse := func(s string) either.Result[int] {
// v, err := strconv.Atoi(s)
// return either.FromError(v, err)
// }
// result := either.TraverseArrayG[[]string, []int](parse)([]string{"1", "2", "3"})
// // result is Right([]int{1, 2, 3})
//
//go:inline
func TraverseArrayG[GA ~[]A, GB ~[]B, A, B any](f Kleisli[A, B]) Kleisli[GA, GB] {
return either.TraverseArrayG[GA, GB, error, A, B](f)
}
// TraverseArray transforms an array by applying a function that returns an Either to each element.
// If any element produces a Left, the entire result is that Left (short-circuits).
// Otherwise, returns Right containing the array of all Right values.
//
// Example:
//
// parse := func(s string) either.Result[int] {
// v, err := strconv.Atoi(s)
// return either.FromError(v, err)
// }
// result := either.TraverseArray(parse)([]string{"1", "2", "3"})
// // result is Right([]int{1, 2, 3})
//
//go:inline
func TraverseArray[A, B any](f Kleisli[A, B]) Kleisli[[]A, []B] {
return either.TraverseArray[error](f)
}
// TraverseArrayWithIndexG transforms an array by applying an indexed function that returns an Either.
// The function receives both the index and the element.
// If any element produces a Left, the entire result is that Left (short-circuits).
// The G suffix indicates support for generic slice types.
//
// Example:
//
// validate := func(i int, s string) either.Result[string] {
// if len(s) > 0 {
// return either.Right[error](fmt.Sprintf("%d:%s", i, s))
// }
// return either.Left[string](fmt.Errorf("empty at index %d", i))
// }
// result := either.TraverseArrayWithIndexG[[]string, []string](validate)([]string{"a", "b"})
// // result is Right([]string{"0:a", "1:b"})
//
//go:inline
func TraverseArrayWithIndexG[GA ~[]A, GB ~[]B, A, B any](f func(int, A) Result[B]) Kleisli[GA, GB] {
return either.TraverseArrayWithIndexG[GA, GB, error, A, B](f)
}
// TraverseArrayWithIndex transforms an array by applying an indexed function that returns an Either.
// The function receives both the index and the element.
// If any element produces a Left, the entire result is that Left (short-circuits).
//
// Example:
//
// validate := func(i int, s string) either.Result[string] {
// if len(s) > 0 {
// return either.Right[error](fmt.Sprintf("%d:%s", i, s))
// }
// return either.Left[string](fmt.Errorf("empty at index %d", i))
// }
// result := either.TraverseArrayWithIndex(validate)([]string{"a", "b"})
// // result is Right([]string{"0:a", "1:b"})
//
//go:inline
func TraverseArrayWithIndex[A, B any](f func(int, A) Result[B]) Kleisli[[]A, []B] {
return either.TraverseArrayWithIndex[error, A, B](f)
}
//go:inline
func SequenceArrayG[GA ~[]A, GOA ~[]Result[A], A any](ma GOA) Result[GA] {
return either.SequenceArrayG[GA, GOA, error, A](ma)
}
// SequenceArray converts a homogeneous sequence of Either into an Either of sequence.
// If any element is Left, returns that Left (short-circuits).
// Otherwise, returns Right containing all the Right values.
//
// Example:
//
// eithers := []either.Result[int]{
// either.Right[error](1),
// either.Right[error](2),
// either.Right[error](3),
// }
// result := either.SequenceArray(eithers)
// // result is Right([]int{1, 2, 3})
//
//go:inline
func SequenceArray[A any](ma []Result[A]) Result[[]A] {
return either.SequenceArray[error, A](ma)
}
// CompactArrayG discards all Left values and keeps only the Right values.
// The G suffix indicates support for generic slice types.
//
// Example:
//
// eithers := []either.Result[int]{
// either.Right[error](1),
// either.Left[int](errors.New("error")),
// either.Right[error](3),
// }
// result := either.CompactArrayG[[]either.Result[int], []int](eithers)
// // result is []int{1, 3}
//
//go:inline
func CompactArrayG[A1 ~[]Result[A], A2 ~[]A, A any](fa A1) A2 {
return either.CompactArrayG[A1, A2, error, A](fa)
}
// CompactArray discards all Left values and keeps only the Right values.
//
// Example:
//
// eithers := []either.Result[int]{
// either.Right[error](1),
// either.Left[int](errors.New("error")),
// either.Right[error](3),
// }
// result := either.CompactArray(eithers)
// // result is []int{1, 3}
//
//go:inline
func CompactArray[A any](fa []Result[A]) []A {
return either.CompactArray[error, A](fa)
}

51
v2/result/array_test.go Normal file
View File

@@ -0,0 +1,51 @@
package result
import (
"errors"
"fmt"
"testing"
TST "github.com/IBM/fp-go/v2/internal/testing"
"github.com/stretchr/testify/assert"
)
func TestCompactArray(t *testing.T) {
ar := []Result[string]{
Of("ok"),
Left[string](errors.New("err")),
Of("ok"),
}
res := CompactArray(ar)
assert.Equal(t, 2, len(res))
}
func TestSequenceArray(t *testing.T) {
s := TST.SequenceArrayTest(
FromStrictEquals[bool](),
Pointed[string](),
Pointed[bool](),
Functor[[]string, bool](),
SequenceArray[string],
)
for i := 0; i < 10; i++ {
t.Run(fmt.Sprintf("TestSequenceArray %d", i), s(i))
}
}
func TestSequenceArrayError(t *testing.T) {
s := TST.SequenceArrayErrorTest(
FromStrictEquals[bool](),
Left[string],
Left[bool],
Pointed[string](),
Pointed[bool](),
Functor[[]string, bool](),
SequenceArray[string],
)
// run across four bits
s(4)(t)
}

351
v2/result/bind.go Normal file
View File

@@ -0,0 +1,351 @@
// 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 result
import (
"github.com/IBM/fp-go/v2/either"
)
// Do creates an empty context of type S to be used with the Bind operation.
// This is the starting point for do-notation style computations.
//
// Example:
//
// type State struct { x, y int }
// result := either.Do[error](State{})
//
//go:inline
func Do[S any](
empty S,
) Result[S] {
return either.Do[error, S](empty)
}
// Bind attaches the result of a computation to a context S1 to produce a context S2.
// This enables building up complex computations in a pipeline.
//
// Example:
//
// type State struct { value int }
// result := F.Pipe2(
// either.Do[error](State{}),
// either.Bind(
// func(v int) func(State) State {
// return func(s State) State { return State{value: v} }
// },
// func(s State) either.Result[int] {
// return either.Right[error](42)
// },
// ),
// )
//
//go:inline
func Bind[S1, S2, T any](
setter func(T) func(S1) S2,
f Kleisli[S1, T],
) Operator[S1, S2] {
return either.Bind[error](setter, f)
}
// Let attaches the result of a pure computation to a context S1 to produce a context S2.
// Similar to Bind but for pure (non-Either) computations.
//
// Example:
//
// type State struct { value int }
// result := F.Pipe2(
// either.Right[error](State{value: 10}),
// either.Let(
// func(v int) func(State) State {
// return func(s State) State { return State{value: s.value + v} }
// },
// func(s State) int { return 32 },
// ),
// ) // Right(State{value: 42})
//
//go:inline
func Let[S1, S2, T any](
key func(T) func(S1) S2,
f func(S1) T,
) Operator[S1, S2] {
return either.Let[error](key, f)
}
// LetTo attaches a constant value to a context S1 to produce a context S2.
//
// Example:
//
// type State struct { name string }
// result := F.Pipe2(
// either.Right[error](State{}),
// either.LetTo(
// func(n string) func(State) State {
// return func(s State) State { return State{name: n} }
// },
// "Alice",
// ),
// ) // Right(State{name: "Alice"})
//
//go:inline
func LetTo[S1, S2, T any](
key func(T) func(S1) S2,
b T,
) Operator[S1, S2] {
return either.LetTo[error](key, b)
}
// BindTo initializes a new state S1 from a value T.
// This is typically used to start a bind chain.
//
// Example:
//
// type State struct { value int }
// result := F.Pipe2(
// either.Right[error](42),
// either.BindTo(func(v int) State { return State{value: v} }),
// ) // Right(State{value: 42})
//
//go:inline
func BindTo[S1, T any](
setter func(T) S1,
) Operator[T, S1] {
return either.BindTo[error](setter)
}
// ApS attaches a value to a context S1 to produce a context S2 by considering the context and the value concurrently.
// Uses applicative semantics rather than monadic sequencing.
//
// Example:
//
// type State struct { x, y int }
// result := F.Pipe2(
// either.Right[error](State{x: 10}),
// either.ApS(
// func(y int) func(State) State {
// return func(s State) State { return State{x: s.x, y: y} }
// },
// either.Right[error](32),
// ),
// ) // Right(State{x: 10, y: 32})
//
//go:inline
func ApS[S1, S2, T any](
setter func(T) func(S1) S2,
fa Result[T],
) Operator[S1, S2] {
return either.ApS[error](setter, fa)
}
// ApSL attaches a value to a context using a lens-based setter.
// This is a convenience function that combines ApS with a lens, allowing you to use
// optics to update nested structures in a more composable way.
//
// The lens parameter provides both the getter and setter for a field within the structure S.
// This eliminates the need to manually write setter functions and enables working with
// nested fields in a type-safe manner.
//
// Unlike BindL, ApSL uses applicative semantics, meaning the computation fa is independent
// of the current state and can be evaluated concurrently.
//
// Type Parameters:
// - E: Error type for the Either
// - S: Structure type containing the field to update
// - T: Type of the field being updated
//
// Parameters:
// - lens: A Lens[S, T] that focuses on a field of type T within structure S
// - fa: An Result[T] computation that produces the value to set
//
// Returns:
// - An endomorphism that updates the focused field in the Either context
//
// Example:
//
// type Person struct {
// Name string
// Age int
// }
//
// ageLens := lens.MakeLens(
// func(p Person) int { return p.Age },
// func(p Person, a int) Person { p.Age = a; return p },
// )
//
// result := F.Pipe2(
// either.Right[error](Person{Name: "Alice", Age: 25}),
// either.ApSL(ageLens, either.Right[error](30)),
// ) // Right(Person{Name: "Alice", Age: 30})
//
//go:inline
func ApSL[S, T any](
lens Lens[S, T],
fa Result[T],
) Operator[S, S] {
return either.ApSL[error](lens, fa)
}
// BindL attaches the result of a computation to a context using a lens-based setter.
// This is a convenience function that combines Bind with a lens, allowing you to use
// optics to update nested structures based on their current values.
//
// The lens parameter provides both the getter and setter for a field within the structure S.
// The computation function f receives the current value of the focused field and returns
// an Either that produces the new value.
//
// Unlike ApSL, BindL uses monadic sequencing, meaning the computation f can depend on
// the current value of the focused field.
//
// Type Parameters:
// - E: Error type for the Either
// - S: Structure type containing the field to update
// - T: Type of the field being updated
//
// Parameters:
// - lens: A Lens[S, T] that focuses on a field of type T within structure S
// - f: A function that takes the current field value and returns an Result[T]
//
// Returns:
// - An endomorphism that updates the focused field based on its current value
//
// Example:
//
// type Counter struct {
// Value int
// }
//
// valueLens := lens.MakeLens(
// func(c Counter) int { return c.Value },
// func(c Counter, v int) Counter { c.Value = v; return c },
// )
//
// // Increment the counter, but fail if it would exceed 100
// increment := func(v int) either.Result[int] {
// if v >= 100 {
// return either.Left[int](errors.New("counter overflow"))
// }
// return either.Right[error](v + 1)
// }
//
// result := F.Pipe1(
// either.Right[error](Counter{Value: 42}),
// either.BindL(valueLens, increment),
// ) // Right(Counter{Value: 43})
//
//go:inline
func BindL[S, T any](
lens Lens[S, T],
f Kleisli[T, T],
) Operator[S, S] {
return either.BindL[error](lens, f)
}
// LetL attaches the result of a pure computation to a context using a lens-based setter.
// This is a convenience function that combines Let with a lens, allowing you to use
// optics to update nested structures with pure transformations.
//
// The lens parameter provides both the getter and setter for a field within the structure S.
// The transformation function f receives the current value of the focused field and returns
// the new value directly (not wrapped in Either).
//
// This is useful for pure transformations that cannot fail, such as mathematical operations,
// string manipulations, or other deterministic updates.
//
// Type Parameters:
// - E: Error type for the Either
// - S: Structure type containing the field to update
// - T: Type of the field being updated
//
// Parameters:
// - lens: A Lens[S, T] that focuses on a field of type T within structure S
// - f: An endomorphism (T → T) that transforms the current field value
//
// Returns:
// - An endomorphism that updates the focused field with the transformed value
//
// Example:
//
// type Counter struct {
// Value int
// }
//
// valueLens := lens.MakeLens(
// func(c Counter) int { return c.Value },
// func(c Counter, v int) Counter { c.Value = v; return c },
// )
//
// // Double the counter value
// double := func(v int) int { return v * 2 }
//
// result := F.Pipe1(
// either.Right[error](Counter{Value: 21}),
// either.LetL(valueLens, double),
// ) // Right(Counter{Value: 42})
//
//go:inline
func LetL[S, T any](
lens Lens[S, T],
f Endomorphism[T],
) Operator[S, S] {
return either.LetL[error](lens, f)
}
// LetToL attaches a constant value to a context using a lens-based setter.
// This is a convenience function that combines LetTo with a lens, allowing you to use
// optics to set nested fields to specific values.
//
// The lens parameter provides the setter for a field within the structure S.
// Unlike LetL which transforms the current value, LetToL simply replaces it with
// the provided constant value b.
//
// This is useful for resetting fields, initializing values, or setting fields to
// predetermined constants.
//
// Type Parameters:
// - E: Error type for the Either
// - S: Structure type containing the field to update
// - T: Type of the field being updated
//
// Parameters:
// - lens: A Lens[S, T] that focuses on a field of type T within structure S
// - b: The constant value to set the field to
//
// Returns:
// - An endomorphism that sets the focused field to the constant value
//
// Example:
//
// type Config struct {
// Debug bool
// Timeout int
// }
//
// debugLens := lens.MakeLens(
// func(c Config) bool { return c.Debug },
// func(c Config, d bool) Config { c.Debug = d; return c },
// )
//
// result := F.Pipe1(
// either.Right[error](Config{Debug: true, Timeout: 30}),
// either.LetToL(debugLens, false),
// ) // Right(Config{Debug: false, Timeout: 30})
//
//go:inline
func LetToL[S, T any](
lens Lens[S, T],
b T,
) Operator[S, S] {
return either.LetToL[error](lens, b)
}

361
v2/result/bind_test.go Normal file
View File

@@ -0,0 +1,361 @@
// 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 result
import (
"testing"
F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/internal/utils"
L "github.com/IBM/fp-go/v2/optics/lens"
"github.com/stretchr/testify/assert"
)
func getLastName(s utils.Initial) Result[string] {
return Of("Doe")
}
func getGivenName(s utils.WithLastName) Result[string] {
return Of("John")
}
func TestBind(t *testing.T) {
res := F.Pipe3(
Do(utils.Empty),
Bind(utils.SetLastName, getLastName),
Bind(utils.SetGivenName, getGivenName),
Map(utils.GetFullName),
)
assert.Equal(t, res, Of("John Doe"))
}
func TestApS(t *testing.T) {
res := F.Pipe3(
Do(utils.Empty),
ApS(utils.SetLastName, Of("Doe")),
ApS(utils.SetGivenName, Of("John")),
Map(utils.GetFullName),
)
assert.Equal(t, res, Of("John Doe"))
}
// Test types for lens-based operations
type Counter struct {
Value int
}
type Person struct {
Name string
Age int
}
type Config struct {
Debug bool
Timeout int
}
func TestApSL(t *testing.T) {
// Create a lens for the Age field
ageLens := L.MakeLens(
func(p Person) int { return p.Age },
func(p Person, a int) Person { p.Age = a; return p },
)
t.Run("ApSL with Right value", func(t *testing.T) {
result := F.Pipe1(
Right(Person{Name: "Alice", Age: 25}),
ApSL(ageLens, Right(30)),
)
expected := Right(Person{Name: "Alice", Age: 30})
assert.Equal(t, expected, result)
})
t.Run("ApSL with Left in context", func(t *testing.T) {
result := F.Pipe1(
Left[Person](assert.AnError),
ApSL(ageLens, Right(30)),
)
expected := Left[Person](assert.AnError)
assert.Equal(t, expected, result)
})
t.Run("ApSL with Left in value", func(t *testing.T) {
result := F.Pipe1(
Right(Person{Name: "Alice", Age: 25}),
ApSL(ageLens, Left[int](assert.AnError)),
)
expected := Left[Person](assert.AnError)
assert.Equal(t, expected, result)
})
t.Run("ApSL with both Left", func(t *testing.T) {
result := F.Pipe1(
Left[Person](assert.AnError),
ApSL(ageLens, Left[int](assert.AnError)),
)
expected := Left[Person](assert.AnError)
assert.Equal(t, expected, result)
})
}
func TestBindL(t *testing.T) {
// Create a lens for the Value field
valueLens := L.MakeLens(
func(c Counter) int { return c.Value },
func(c Counter, v int) Counter { c.Value = v; return c },
)
t.Run("BindL with successful transformation", func(t *testing.T) {
// Increment the counter, but fail if it would exceed 100
increment := func(v int) Result[int] {
if v >= 100 {
return Left[int](assert.AnError)
}
return Right(v + 1)
}
result := F.Pipe1(
Right(Counter{Value: 42}),
BindL(valueLens, increment),
)
expected := Right(Counter{Value: 43})
assert.Equal(t, expected, result)
})
t.Run("BindL with failing transformation", func(t *testing.T) {
increment := func(v int) Result[int] {
if v >= 100 {
return Left[int](assert.AnError)
}
return Right(v + 1)
}
result := F.Pipe1(
Right(Counter{Value: 100}),
BindL(valueLens, increment),
)
expected := Left[Counter](assert.AnError)
assert.Equal(t, expected, result)
})
t.Run("BindL with Left input", func(t *testing.T) {
increment := func(v int) Result[int] {
return Right(v + 1)
}
result := F.Pipe1(
Left[Counter](assert.AnError),
BindL(valueLens, increment),
)
expected := Left[Counter](assert.AnError)
assert.Equal(t, expected, result)
})
t.Run("BindL with multiple operations", func(t *testing.T) {
double := func(v int) Result[int] {
return Right(v * 2)
}
addTen := func(v int) Result[int] {
return Right(v + 10)
}
result := F.Pipe2(
Right(Counter{Value: 5}),
BindL(valueLens, double),
BindL(valueLens, addTen),
)
expected := Right(Counter{Value: 20})
assert.Equal(t, expected, result)
})
}
func TestLetL(t *testing.T) {
// Create a lens for the Value field
valueLens := L.MakeLens(
func(c Counter) int { return c.Value },
func(c Counter, v int) Counter { c.Value = v; return c },
)
t.Run("LetL with pure transformation", func(t *testing.T) {
double := func(v int) int { return v * 2 }
result := F.Pipe1(
Right(Counter{Value: 21}),
LetL(valueLens, double),
)
expected := Right(Counter{Value: 42})
assert.Equal(t, expected, result)
})
t.Run("LetL with Left input", func(t *testing.T) {
double := func(v int) int { return v * 2 }
result := F.Pipe1(
Left[Counter](assert.AnError),
LetL(valueLens, double),
)
expected := Left[Counter](assert.AnError)
assert.Equal(t, expected, result)
})
t.Run("LetL with multiple transformations", func(t *testing.T) {
double := func(v int) int { return v * 2 }
addTen := func(v int) int { return v + 10 }
result := F.Pipe2(
Right(Counter{Value: 5}),
LetL(valueLens, double),
LetL(valueLens, addTen),
)
expected := Right(Counter{Value: 20})
assert.Equal(t, expected, result)
})
t.Run("LetL with identity transformation", func(t *testing.T) {
identity := func(v int) int { return v }
result := F.Pipe1(
Right(Counter{Value: 42}),
LetL(valueLens, identity),
)
expected := Right(Counter{Value: 42})
assert.Equal(t, expected, result)
})
}
func TestLetToL(t *testing.T) {
// Create a lens for the Debug field
debugLens := L.MakeLens(
func(c Config) bool { return c.Debug },
func(c Config, d bool) Config { c.Debug = d; return c },
)
t.Run("LetToL with constant value", func(t *testing.T) {
result := F.Pipe1(
Right(Config{Debug: true, Timeout: 30}),
LetToL(debugLens, false),
)
expected := Right(Config{Debug: false, Timeout: 30})
assert.Equal(t, expected, result)
})
t.Run("LetToL with Left input", func(t *testing.T) {
result := F.Pipe1(
Left[Config](assert.AnError),
LetToL(debugLens, false),
)
expected := Left[Config](assert.AnError)
assert.Equal(t, expected, result)
})
t.Run("LetToL with multiple fields", func(t *testing.T) {
timeoutLens := L.MakeLens(
func(c Config) int { return c.Timeout },
func(c Config, t int) Config { c.Timeout = t; return c },
)
result := F.Pipe2(
Right(Config{Debug: true, Timeout: 30}),
LetToL(debugLens, false),
LetToL(timeoutLens, 60),
)
expected := Right(Config{Debug: false, Timeout: 60})
assert.Equal(t, expected, result)
})
t.Run("LetToL setting same value", func(t *testing.T) {
result := F.Pipe1(
Right(Config{Debug: false, Timeout: 30}),
LetToL(debugLens, false),
)
expected := Right(Config{Debug: false, Timeout: 30})
assert.Equal(t, expected, result)
})
}
func TestLensOperationsCombined(t *testing.T) {
// Test combining different lens operations
valueLens := L.MakeLens(
func(c Counter) int { return c.Value },
func(c Counter, v int) Counter { c.Value = v; return c },
)
t.Run("Combine LetToL and LetL", func(t *testing.T) {
double := func(v int) int { return v * 2 }
result := F.Pipe2(
Right(Counter{Value: 100}),
LetToL(valueLens, 10),
LetL(valueLens, double),
)
expected := Right(Counter{Value: 20})
assert.Equal(t, expected, result)
})
t.Run("Combine LetL and BindL", func(t *testing.T) {
double := func(v int) int { return v * 2 }
validate := func(v int) Result[int] {
if v > 100 {
return Left[int](assert.AnError)
}
return Right(v)
}
result := F.Pipe2(
Right(Counter{Value: 25}),
LetL(valueLens, double),
BindL(valueLens, validate),
)
expected := Right(Counter{Value: 50})
assert.Equal(t, expected, result)
})
t.Run("Combine ApSL and LetL", func(t *testing.T) {
addFive := func(v int) int { return v + 5 }
result := F.Pipe2(
Right(Counter{Value: 10}),
ApSL(valueLens, Right(20)),
LetL(valueLens, addFive),
)
expected := Right(Counter{Value: 25})
assert.Equal(t, expected, result)
})
}

101
v2/result/core.go Normal file
View File

@@ -0,0 +1,101 @@
// 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 result
import "github.com/IBM/fp-go/v2/either"
// 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[A any](val Result[A]) bool {
return either.IsLeft(val)
}
// 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[A any](val Result[A]) bool {
return either.IsRight(val)
}
// 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 any](value error) Result[A] {
return either.Left[A](value)
}
// 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[A any](value A) Result[A] {
return either.Right[error](value)
}
// 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[A, B any](ma Result[A], onLeft func(e error) B, onRight func(a A) B) B {
return either.MonadFold(ma, onLeft, onRight)
}
// 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[A any](ma Result[A]) (A, error) {
return either.Unwrap(ma)
}

103
v2/result/curry.go Normal file
View File

@@ -0,0 +1,103 @@
// 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 result
import (
"github.com/IBM/fp-go/v2/either"
)
// Curry0 converts a Go function that returns (R, error) into a curried version that returns Result[R].
//
// Example:
//
// getConfig := func() (string, error) { return "config", nil }
// curried := either.Curry0(getConfig)
// result := curried() // Right("config")
func Curry0[R any](f func() (R, error)) func() Result[R] {
return either.Curry0[R](f)
}
// Curry1 converts a Go function that returns (R, error) into a curried version that returns Result[R].
//
// Example:
//
// parse := func(s string) (int, error) { return strconv.Atoi(s) }
// curried := either.Curry1(parse)
// result := curried("42") // Right(42)
func Curry1[T1, R any](f func(T1) (R, error)) func(T1) Result[R] {
return either.Curry1[T1, R](f)
}
// Curry2 converts a 2-argument Go function that returns (R, error) into a curried version.
//
// Example:
//
// divide := func(a, b int) (int, error) {
// if b == 0 { return 0, errors.New("div by zero") }
// return a / b, nil
// }
// curried := either.Curry2(divide)
// result := curried(10)(2) // Right(5)
func Curry2[T1, T2, R any](f func(T1, T2) (R, error)) func(T1) func(T2) Result[R] {
return either.Curry2[T1, T2, R](f)
}
// Curry3 converts a 3-argument Go function that returns (R, error) into a curried version.
func Curry3[T1, T2, T3, R any](f func(T1, T2, T3) (R, error)) func(T1) func(T2) func(T3) Result[R] {
return either.Curry3[T1, T2, T3, R](f)
}
// Curry4 converts a 4-argument Go function that returns (R, error) into a curried version.
func Curry4[T1, T2, T3, T4, R any](f func(T1, T2, T3, T4) (R, error)) func(T1) func(T2) func(T3) func(T4) Result[R] {
return either.Curry4[T1, T2, T3, T4, R](f)
}
// Uncurry0 converts a function returning Result[R] back to Go's (R, error) style.
//
// Example:
//
// curried := func() either.Result[string] { return either.Right[error]("value") }
// uncurried := either.Uncurry0(curried)
// result, err := uncurried() // "value", nil
func Uncurry0[R any](f func() Result[R]) func() (R, error) {
return either.Uncurry0[R](f)
}
// Uncurry1 converts a function returning Result[R] back to Go's (R, error) style.
//
// Example:
//
// curried := func(x int) either.Result[string] { return either.Right[error](strconv.Itoa(x)) }
// uncurried := either.Uncurry1(curried)
// result, err := uncurried(42) // "42", nil
func Uncurry1[T1, R any](f func(T1) Result[R]) func(T1) (R, error) {
return either.Uncurry1[T1, R](f)
}
// Uncurry2 converts a curried function returning Result[R] back to Go's (R, error) style.
func Uncurry2[T1, T2, R any](f func(T1) func(T2) Result[R]) func(T1, T2) (R, error) {
return either.Uncurry2[T1, T2, R](f)
}
// Uncurry3 converts a curried function returning Result[R] back to Go's (R, error) style.
func Uncurry3[T1, T2, T3, R any](f func(T1) func(T2) func(T3) Result[R]) func(T1, T2, T3) (R, error) {
return either.Uncurry3[T1, T2, T3, R](f)
}
// Uncurry4 converts a curried function returning Result[R] back to Go's (R, error) style.
func Uncurry4[T1, T2, T3, T4, R any](f func(T1) func(T2) func(T3) func(T4) Result[R]) func(T1, T2, T3, T4) (R, error) {
return either.Uncurry4[T1, T2, T3, T4, R](f)
}

65
v2/result/doc.go Normal file
View File

@@ -0,0 +1,65 @@
// 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 result provides the Either monad, a data structure representing a value of one of two possible types.
//
// Either is commonly used for error handling, where by convention:
// - Left represents an error or failure case (type E)
// - Right represents a success case (type A)
//
// # Core Concepts
//
// The Either type is a discriminated union that can hold either a Left value (typically an error)
// or a Right value (typically a successful result). This makes it ideal for computations that may fail.
//
// # Basic Usage
//
// // Creating Either values
// success := either.Right[error](42) // Right value
// failure := either.Left[int](errors.New("oops")) // Left value
//
// // Pattern matching with Fold
// result := either.Fold(
// func(err error) string { return "Error: " + err.Error() },
// func(n int) string { return fmt.Sprintf("Success: %d", n) },
// )(success)
//
// // Chaining operations (short-circuits on Left)
// result := either.Chain(func(n int) either.Result[int] {
// return either.Right[error](n * 2)
// })(success)
//
// # Monadic Operations
//
// Either implements the Monad interface, providing:
// - Map: Transform the Right value
// - Chain (FlatMap): Chain computations that may fail
// - Ap: Apply a function wrapped in Either
//
// # Error Handling
//
// Either provides utilities for working with Go's error type:
// - TryCatchError: Convert (value, error) tuples to Either
// - UnwrapError: Convert Either back to (value, error) tuple
// - FromError: Create Either from error-returning functions
//
// # Subpackages
//
// - either/exec: Execute system commands returning Either
// - either/http: HTTP request builders returning Either
// - either/testing: Testing utilities for Either laws
package result
//go:generate go run .. either --count 15 --filename gen.go

556
v2/result/either.go Normal file
View File

@@ -0,0 +1,556 @@
// 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 result implements the Either monad
//
// A data type that can be of either of two types but not both. This is
// typically used to carry an error or a return value
package result
import (
"github.com/IBM/fp-go/v2/either"
"github.com/IBM/fp-go/v2/option"
)
// Of constructs a Right value containing the given value.
// This is the monadic return/pure operation for Either.
// Equivalent to [Right].
//
// Example:
//
// result := either.Of[error](42) // Right(42)
//
//go:inline
func Of[A any](value A) Result[A] {
return either.Of[error, A](value)
}
// FromIO executes an IO operation and wraps the result in a Right value.
// This is useful for lifting pure IO operations into the Either context.
//
// Example:
//
// getValue := func() int { return 42 }
// result := either.FromIO[error](getValue) // Right(42)
//
//go:inline
func FromIO[IO ~func() A, A any](f IO) Result[A] {
return either.FromIO[error, IO, A](f)
}
// MonadAp applies a function wrapped in Either to a value wrapped in Either.
// If either the function or the value is Left, returns Left.
// This is the applicative apply operation.
//
// Example:
//
// fab := either.Right[error](func(x int) int { return x * 2 })
// fa := either.Right[error](21)
// result := either.MonadAp(fab, fa) // Right(42)
//
//go:inline
func MonadAp[B, A any](fab Result[func(a A) B], fa Result[A]) Result[B] {
return either.MonadAp[B, error, A](fab, fa)
}
// Ap is the curried version of [MonadAp].
// Returns a function that applies a wrapped function to the given wrapped value.
//
//go:inline
func Ap[B, A any](fa Result[A]) Operator[func(A) B, B] {
return either.Ap[B, error, A](fa)
}
// MonadMap transforms the Right value using the provided function.
// If the Either is Left, returns Left unchanged.
// This is the functor map operation.
//
// Example:
//
// result := either.MonadMap(
// either.Right[error](21),
// func(x int) int { return x * 2 },
// ) // Right(42)
//
//go:inline
func MonadMap[A, B any](fa Result[A], f func(a A) B) Result[B] {
return either.MonadMap[error, A, B](fa, f)
}
// MonadBiMap applies two functions: one to transform a Left value, another to transform a Right value.
// This allows transforming both channels of the Either simultaneously.
//
// Example:
//
// result := either.MonadBiMap(
// either.Left[int](errors.New("error")),
// func(e error) string { return e.Error() },
// func(n int) string { return fmt.Sprint(n) },
// ) // Left("error")
//
//go:inline
func MonadBiMap[E, A, B any](fa Result[A], f func(error) E, g func(a A) B) Either[E, B] {
return either.MonadBiMap[error, E, A, B](fa, f, g)
}
// BiMap is the curried version of [MonadBiMap].
// Maps a pair of functions over the two type arguments of the bifunctor.
//
//go:inline
func BiMap[E, A, B any](f func(error) E, g func(a A) B) func(Result[A]) Either[E, B] {
return either.BiMap[error, E, A, B](f, g)
}
// MonadMapTo replaces the Right value with a constant value.
// If the Either is Left, returns Left unchanged.
//
// Example:
//
// result := either.MonadMapTo(either.Right[error](21), "success") // Right("success")
//
//go:inline
func MonadMapTo[A, B any](fa Result[A], b B) Result[B] {
return either.MonadMapTo[error, A, B](fa, b)
}
// MapTo is the curried version of [MonadMapTo].
//
//go:inline
func MapTo[A, B any](b B) Operator[A, B] {
return either.MapTo[error, A, B](b)
}
// MonadMapLeft applies a transformation function to the Left (error) value.
// If the Either is Right, returns Right unchanged.
//
// Example:
//
// result := either.MonadMapLeft(
// either.Left[int](errors.New("error")),
// func(e error) string { return e.Error() },
// ) // Left("error")
//
//go:inline
func MonadMapLeft[A, E any](fa Result[A], f func(error) E) Either[E, A] {
return either.MonadMapLeft[error, A, E](fa, f)
}
// Map is the curried version of [MonadMap].
// Transforms the Right value using the provided function.
//
//go:inline
func Map[A, B any](f func(a A) B) Operator[A, B] {
return either.Map[error, A, B](f)
}
// MapLeft is the curried version of [MonadMapLeft].
// Applies a mapping function to the Left (error) channel.
//
//go:inline
func MapLeft[A, E any](f func(error) E) func(fa Result[A]) Either[E, A] {
return either.MapLeft[A, error, E](f)
}
// MonadChain sequences two computations, where the second depends on the result of the first.
// If the first Either is Left, returns Left without executing the second computation.
// This is the monadic bind operation (also known as flatMap).
//
// Example:
//
// result := either.MonadChain(
// either.Right[error](21),
// func(x int) either.Result[int] {
// return either.Right[error](x * 2)
// },
// ) // Right(42)
//
//go:inline
func MonadChain[A, B any](fa Result[A], f Kleisli[A, B]) Result[B] {
return either.MonadChain[error, A, B](fa, f)
}
// MonadChainFirst executes a side-effect computation but returns the original value.
// Useful for performing actions (like logging) without changing the value.
//
// Example:
//
// result := either.MonadChainFirst(
// either.Right[error](42),
// func(x int) either.Result[string] {
// fmt.Println(x) // side effect
// return either.Right[error]("logged")
// },
// ) // Right(42) - original value preserved
//
//go:inline
func MonadChainFirst[A, B any](ma Result[A], f Kleisli[A, B]) Result[A] {
return either.MonadChainFirst[error, A, B](ma, f)
}
// MonadChainTo ignores the first Either and returns the second.
// Useful for sequencing operations where you don't need the first result.
//
//go:inline
func MonadChainTo[A, B any](ma Result[A], mb Result[B]) Result[B] {
return either.MonadChainTo[A, error, B](ma, mb)
}
// MonadChainOptionK chains a function that returns an Option, converting None to Left.
//
// Example:
//
// result := either.MonadChainOptionK(
// func() error { return errors.New("not found") },
// either.Right[error](42),
// func(x int) option.Option[string] {
// if x > 0 { return option.Some("positive") }
// return option.None[string]()
// },
// ) // Right("positive")
//
//go:inline
func MonadChainOptionK[A, B any](onNone func() error, ma Result[A], f option.Kleisli[A, B]) Result[B] {
return either.MonadChainOptionK[A, B, error](onNone, ma, f)
}
// ChainOptionK is the curried version of [MonadChainOptionK].
//
//go:inline
func ChainOptionK[A, B any](onNone func() error) func(option.Kleisli[A, B]) Operator[A, B] {
return either.ChainOptionK[A, B, error](onNone)
}
// ChainTo is the curried version of [MonadChainTo].
//
//go:inline
func ChainTo[A, B any](mb Result[B]) Operator[A, B] {
return either.ChainTo[A, error, B](mb)
}
// Chain is the curried version of [MonadChain].
// Sequences two computations where the second depends on the first.
//
//go:inline
func Chain[A, B any](f Kleisli[A, B]) Operator[A, B] {
return either.Chain[error, A, B](f)
}
// ChainFirst is the curried version of [MonadChainFirst].
//
//go:inline
func ChainFirst[A, B any](f Kleisli[A, B]) Operator[A, A] {
return either.ChainFirst[error, A, B](f)
}
// Flatten removes one level of nesting from a nested Either.
//
// Example:
//
// nested := either.Right[error](either.Right[error](42))
// result := either.Flatten(nested) // Right(42)
//
//go:inline
func Flatten[A any](mma Result[Result[A]]) Result[A] {
return either.Flatten[error, A](mma)
}
// TryCatch converts a (value, error) tuple into an Either, applying a transformation to the error.
//
// Example:
//
// result := either.TryCatch(
// 42, nil,
// func(err error) string { return err.Error() },
// ) // Right(42)
//
//go:inline
func TryCatch[FE Endomorphism[error], A any](val A, err error, onThrow FE) Result[A] {
return either.TryCatch[FE, error, A](val, err, onThrow)
}
// TryCatchError is a specialized version of [TryCatch] for error types.
// Converts a (value, error) tuple into Result[A].
//
// Example:
//
// result := either.TryCatchError(42, nil) // Right(42)
// result := either.TryCatchError(0, errors.New("fail")) // Left(error)
//
//go:inline
func TryCatchError[A any](val A, err error) Result[A] {
return either.TryCatchError[A](val, err)
}
// Sequence2 sequences two Either values using a combining function.
// Short-circuits on the first Left encountered.
//
//go:inline
func Sequence2[T1, T2, R any](f func(T1, T2) Result[R]) func(Result[T1], Result[T2]) Result[R] {
return either.Sequence2[error, T1, T2, R](f)
}
// Sequence3 sequences three Either values using a combining function.
// Short-circuits on the first Left encountered.
//
//go:inline
func Sequence3[T1, T2, T3, R any](f func(T1, T2, T3) Result[R]) func(Result[T1], Result[T2], Result[T3]) Result[R] {
return either.Sequence3[error, T1, T2, T3, R](f)
}
// FromOption converts an Option to an Either, using the provided function to generate a Left value for None.
//
// Example:
//
// opt := option.Some(42)
// result := either.FromOption[int](func() error { return errors.New("none") })(opt) // Right(42)
//
//go:inline
func FromOption[A any](onNone func() error) func(Option[A]) Result[A] {
return either.FromOption[A, error](onNone)
}
// ToOption converts an Either to an Option, discarding the Left value.
//
// Example:
//
// result := either.ToOption(either.Right[error](42)) // Some(42)
// result := either.ToOption(either.Left[int](errors.New("err"))) // None
//
//go:inline
func ToOption[A any](ma Result[A]) Option[A] {
return either.ToOption[error, A](ma)
}
// FromError creates an Either from a function that may return an error.
//
// Example:
//
// validate := func(x int) error {
// if x < 0 { return errors.New("negative") }
// return nil
// }
// toEither := either.FromError(validate)
// result := toEither(42) // Right(42)
//
//go:inline
func FromError[A any](f func(a A) error) Kleisli[A, A] {
return either.FromError[A](f)
}
// ToError converts an Result[A] to an error, returning nil for Right values.
//
// Example:
//
// err := either.ToError(either.Left[int](errors.New("fail"))) // error
// err := either.ToError(either.Right[error](42)) // nil
//
//go:inline
func ToError[A any](e Result[A]) error {
return either.ToError[A](e)
}
// Fold is the curried version of [MonadFold].
// Extracts the value from an Either by providing handlers for both cases.
//
// Example:
//
// result := either.Fold(
// func(err error) string { return "Error: " + err.Error() },
// func(n int) string { return fmt.Sprintf("Value: %d", n) },
// )(either.Right[error](42)) // "Value: 42"
//
//go:inline
func Fold[A, B any](onLeft func(error) B, onRight func(A) B) func(Result[A]) B {
return either.Fold[error, A, B](onLeft, onRight)
}
// UnwrapError converts an Result[A] into the idiomatic Go tuple (A, error).
//
// Example:
//
// val, err := either.UnwrapError(either.Right[error](42)) // 42, nil
// val, err := either.UnwrapError(either.Left[int](errors.New("fail"))) // zero, error
//
//go:inline
func UnwrapError[A any](ma Result[A]) (A, error) {
return either.UnwrapError[A](ma)
}
// FromPredicate creates an Either based on a predicate.
// If the predicate returns true, creates a Right; otherwise creates a Left using onFalse.
//
// Example:
//
// isPositive := either.FromPredicate(
// func(x int) bool { return x > 0 },
// func(x int) error { return errors.New("not positive") },
// )
// result := isPositive(42) // Right(42)
// result := isPositive(-1) // Left(error)
//
//go:inline
func FromPredicate[A any](pred func(A) bool, onFalse func(A) error) Kleisli[A, A] {
return either.FromPredicate[error, A](pred, onFalse)
}
// FromNillable creates an Either from a pointer, using the provided error for nil pointers.
//
// Example:
//
// var ptr *int = nil
// result := either.FromNillable[int](errors.New("nil"))(ptr) // Left(error)
// val := 42
// result := either.FromNillable[int](errors.New("nil"))(&val) // Right(&42)
//
//go:inline
func FromNillable[A any](e error) func(*A) Result[*A] {
return either.FromNillable[A, error](e)
}
// GetOrElse extracts the Right value or computes a default from the Left value.
//
// Example:
//
// result := either.GetOrElse(func(err error) int { return 0 })(either.Right[error](42)) // 42
// result := either.GetOrElse(func(err error) int { return 0 })(either.Left[int](err)) // 0
//
//go:inline
func GetOrElse[A any](onLeft func(error) A) func(Result[A]) A {
return either.GetOrElse[error, A](onLeft)
}
// Reduce folds an Either into a single value using a reducer function.
// Returns the initial value for Left, or applies the reducer to the Right value.
//
//go:inline
func Reduce[A, B any](f func(B, A) B, initial B) func(Result[A]) B {
return either.Reduce[error](f, initial)
}
// AltW provides an alternative Either if the first is Left, allowing different error types.
// The 'W' suffix indicates "widening" of the error type.
//
// Example:
//
// alternative := either.AltW[error, string](func() either.Either[string, int] {
// return either.Right[string](99)
// })
// result := alternative(either.Left[int](errors.New("fail"))) // Right(99)
//
//go:inline
func AltW[E1, A any](that Lazy[Either[E1, A]]) func(Result[A]) Either[E1, A] {
return either.AltW[error, E1, A](that)
}
// Alt provides an alternative Either if the first is Left.
//
// Example:
//
// alternative := either.Alt[error](func() either.Result[int] {
// return either.Right[error](99)
// })
// result := alternative(either.Left[int](errors.New("fail"))) // Right(99)
//
//go:inline
func Alt[A any](that Lazy[Result[A]]) Operator[A, A] {
return either.Alt[error](that)
}
// OrElse recovers from a Left by providing an alternative computation.
//
// Example:
//
// recover := either.OrElse(func(err error) either.Result[int] {
// return either.Right[error](0) // default value
// })
// result := recover(either.Left[int](errors.New("fail"))) // Right(0)
//
//go:inline
func OrElse[A any](onLeft Kleisli[error, A]) Operator[A, A] {
return either.OrElse[error, A](onLeft)
}
// ToType attempts to convert an any value to a specific type, returning Either.
//
// Example:
//
// convert := either.ToType[int](func(v any) error {
// return fmt.Errorf("cannot convert %v to int", v)
// })
// result := convert(42) // Right(42)
// result := convert("string") // Left(error)
//
//go:inline
func ToType[A any](onError func(any) error) Kleisli[any, A] {
return either.ToType[A, error](onError)
}
// Memoize returns the Either unchanged (Either values are already memoized).
//
//go:inline
func Memoize[A any](val Result[A]) Result[A] {
return either.Memoize[error, A](val)
}
// MonadSequence2 sequences two Either values using a combining function.
// Short-circuits on the first Left encountered.
//
//go:inline
func MonadSequence2[T1, T2, R any](e1 Result[T1], e2 Result[T2], f func(T1, T2) Result[R]) Result[R] {
return either.MonadSequence2[error, T1, T2, R](e1, e2, f)
}
// MonadSequence3 sequences three Either values using a combining function.
// Short-circuits on the first Left encountered.
//
//go:inline
func MonadSequence3[T1, T2, T3, R any](e1 Result[T1], e2 Result[T2], e3 Result[T3], f func(T1, T2, T3) Result[R]) Result[R] {
return either.MonadSequence3[error, T1, T2, T3, R](e1, e2, e3, f)
}
// Swap exchanges the Left and Right type parameters.
//
// Example:
//
// result := either.Swap(either.Right[error](42)) // Left(42)
// result := either.Swap(either.Left[int](errors.New("err"))) // Right(error)
//
//go:inline
func Swap[A any](val Result[A]) Either[A, error] {
return either.Swap[error, A](val)
}
// MonadFlap applies a value to a function wrapped in Either.
// This is the reverse of [MonadAp].
//
//go:inline
func MonadFlap[B, A any](fab Result[func(A) B], a A) Result[B] {
return either.MonadFlap[error, B, A](fab, a)
}
// Flap is the curried version of [MonadFlap].
//
//go:inline
func Flap[B, A any](a A) Operator[func(A) B, B] {
return either.Flap[error, B, A](a)
}
// MonadAlt provides an alternative Either if the first is Left.
// This is the monadic version of [Alt].
//
//go:inline
func MonadAlt[A any](fa Result[A], that Lazy[Result[A]]) Result[A] {
return either.MonadAlt[error, A](fa, that)
}

142
v2/result/either_test.go Normal file
View File

@@ -0,0 +1,142 @@
// 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 result
import (
"errors"
"fmt"
"testing"
F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/internal/utils"
IO "github.com/IBM/fp-go/v2/io"
O "github.com/IBM/fp-go/v2/option"
S "github.com/IBM/fp-go/v2/string"
"github.com/stretchr/testify/assert"
)
func TestIsLeft(t *testing.T) {
err := errors.New("Some error")
withError := Left[string](err)
assert.True(t, IsLeft(withError))
assert.False(t, IsRight(withError))
}
func TestIsRight(t *testing.T) {
noError := Right("Carsten")
assert.True(t, IsRight(noError))
assert.False(t, IsLeft(noError))
}
func TestMapEither(t *testing.T) {
e := errors.New("s")
assert.Equal(t, F.Pipe1(Right("abc"), Map(utils.StringLen)), Right(3))
val2 := F.Pipe1(Left[string](e), Map[string](utils.StringLen))
exp2 := Left[int](e)
assert.Equal(t, val2, exp2)
}
func TestUnwrapError(t *testing.T) {
a := ""
err := errors.New("Some error")
withError := Left[string](err)
res, extracted := UnwrapError(withError)
assert.Equal(t, a, res)
assert.Equal(t, extracted, err)
}
func TestReduce(t *testing.T) {
s := S.Semigroup()
assert.Equal(t, "foobar", F.Pipe1(Right("bar"), Reduce[string](s.Concat, "foo")))
assert.Equal(t, "foo", F.Pipe1(Left[string](errors.New("bar")), Reduce[string](s.Concat, "foo")))
}
func TestAp(t *testing.T) {
f := S.Size
maError := errors.New("maError")
mabError := errors.New("mabError")
assert.Equal(t, Right(3), F.Pipe1(Right(f), Ap[int, string](Right("abc"))))
assert.Equal(t, Left[int](maError), F.Pipe1(Right(f), Ap[int, string](Left[string](maError))))
assert.Equal(t, Left[int](mabError), F.Pipe1(Left[func(string) int](mabError), Ap[int, string](Left[string](maError))))
}
func TestAlt(t *testing.T) {
a := errors.New("a")
b := errors.New("b")
assert.Equal(t, Right(1), F.Pipe1(Right(1), Alt(F.Constant(Right(2)))))
assert.Equal(t, Right(1), F.Pipe1(Right(1), Alt(F.Constant(Left[int](a)))))
assert.Equal(t, Right(2), F.Pipe1(Left[int](b), Alt(F.Constant(Right(2)))))
assert.Equal(t, Left[int](b), F.Pipe1(Left[int](a), Alt(F.Constant(Left[int](b)))))
}
func TestChainFirst(t *testing.T) {
f := F.Flow2(S.Size, Right[int])
maError := errors.New("maError")
assert.Equal(t, Right("abc"), F.Pipe1(Right("abc"), ChainFirst(f)))
assert.Equal(t, Left[string](maError), F.Pipe1(Left[string](maError), ChainFirst(f)))
}
func TestChainOptionK(t *testing.T) {
a := errors.New("a")
b := errors.New("b")
f := ChainOptionK[int, int](F.Constant(a))(func(n int) Option[int] {
if n > 0 {
return O.Some(n)
}
return O.None[int]()
})
assert.Equal(t, Right(1), f(Right(1)))
assert.Equal(t, Left[int](a), f(Right(-1)))
assert.Equal(t, Left[int](b), f(Left[int](b)))
}
func TestFromOption(t *testing.T) {
none := errors.New("none")
assert.Equal(t, Left[int](none), FromOption[int](F.Constant(none))(O.None[int]()))
assert.Equal(t, Right(1), FromOption[int](F.Constant(none))(O.Some(1)))
}
func TestStringer(t *testing.T) {
e := Of("foo")
exp := "Right[string](foo)"
assert.Equal(t, exp, e.String())
var s fmt.Stringer = &e
assert.Equal(t, exp, s.String())
}
func TestFromIO(t *testing.T) {
f := IO.Of("abc")
e := FromIO(f)
assert.Equal(t, Right("abc"), e)
}

50
v2/result/eq.go Normal file
View File

@@ -0,0 +1,50 @@
// 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 result
import (
"github.com/IBM/fp-go/v2/either"
"github.com/IBM/fp-go/v2/eq"
EQ "github.com/IBM/fp-go/v2/eq"
)
// Eq constructs an equality predicate for Either values.
// Two Either values are equal if they are both Left with equal error values,
// or both Right with equal success values.
//
// Parameters:
// - e: Equality predicate for the Left (error) type
// - a: Equality predicate for the Right (success) type
//
// Example:
//
// eq := either.Eq(eq.FromStrictEquals[error](), eq.FromStrictEquals[int]())
// result := eq.Equals(either.Right[error](42), either.Right[error](42)) // true
// result2 := eq.Equals(either.Right[error](42), either.Right[error](43)) // false
func Eq[A any](a EQ.Eq[A]) EQ.Eq[Result[A]] {
return either.Eq[error, A](eq.FromStrictEquals[error](), a)
}
// FromStrictEquals constructs an equality predicate using Go's == operator.
// Both the Left and Right types must be comparable.
//
// Example:
//
// eq := either.FromStrictEquals[error, int]()
// result := eq.Equals(either.Right[error](42), either.Right[error](42)) // true
func FromStrictEquals[A comparable]() EQ.Eq[Result[A]] {
return either.FromStrictEquals[error, A]()
}

49
v2/result/eq_test.go Normal file
View File

@@ -0,0 +1,49 @@
// 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 result
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
func TestEq(t *testing.T) {
a := errors.New("a")
b := errors.New("b")
r1 := Of(1)
r2 := Of(1)
r3 := Of(2)
e1 := Left[int](a)
e2 := Left[int](a)
e3 := Left[int](b)
eq := FromStrictEquals[int]()
assert.True(t, eq.Equals(r1, r1))
assert.True(t, eq.Equals(r1, r2))
assert.False(t, eq.Equals(r1, r3))
assert.False(t, eq.Equals(r1, e1))
assert.True(t, eq.Equals(e1, e1))
assert.True(t, eq.Equals(e1, e2))
assert.False(t, eq.Equals(e1, e3))
assert.False(t, eq.Equals(e2, r2))
}

View File

@@ -0,0 +1,58 @@
// 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 result
import (
"fmt"
"github.com/IBM/fp-go/v2/errors"
)
func ExampleEither_creation() {
// Build an Either
leftValue := Left[string](fmt.Errorf("some error"))
rightValue := Right("value")
// Build from a value
fromNillable := FromNillable[string](fmt.Errorf("value was nil"))
leftFromNil := fromNillable(nil)
value := "value"
rightFromPointer := fromNillable(&value)
// some predicate
isEven := func(num int) bool {
return num%2 == 0
}
fromEven := FromPredicate(isEven, errors.OnSome[int]("%d is an odd number"))
leftFromPred := fromEven(3)
rightFromPred := fromEven(4)
fmt.Println(leftValue)
fmt.Println(rightValue)
fmt.Println(leftFromNil)
fmt.Println(IsRight(rightFromPointer))
fmt.Println(leftFromPred)
fmt.Println(rightFromPred)
// Output:
// Left[*errors.errorString](some error)
// Right[string](value)
// Left[*errors.errorString](value was nil)
// true
// Left[*errors.errorString](3 is an odd number)
// Right[int](4)
}

View File

@@ -0,0 +1,64 @@
// 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 result
import (
"fmt"
F "github.com/IBM/fp-go/v2/function"
N "github.com/IBM/fp-go/v2/number"
)
func ExampleEither_extraction() {
leftValue := Left[int](fmt.Errorf("Division by Zero!"))
rightValue := Right(10)
// Convert Result[A] to A with a default value
leftWithDefault := GetOrElse(F.Constant1[error](0))(leftValue) // 0
rightWithDefault := GetOrElse(F.Constant1[error](0))(rightValue) // 10
// Apply a different function on Left(...)/Right(...)
doubleOrZero := Fold(F.Constant1[error](0), N.Mul(2)) // func(Result[int]) int
doubleFromLeft := doubleOrZero(leftValue) // 0
doubleFromRight := doubleOrZero(rightValue) // 20
// Pro-tip: Fold is short for the following:
doubleOrZeroBis := F.Flow2(
Map(N.Mul(2)),
GetOrElse(F.Constant1[error](0)),
)
doubleFromLeftBis := doubleOrZeroBis(leftValue) // 0
doubleFromRightBis := doubleOrZeroBis(rightValue) // 20
fmt.Println(leftValue)
fmt.Println(rightValue)
fmt.Println(leftWithDefault)
fmt.Println(rightWithDefault)
fmt.Println(doubleFromLeft)
fmt.Println(doubleFromRight)
fmt.Println(doubleFromLeftBis)
fmt.Println(doubleFromRightBis)
// Output:
// Left[*errors.errorString](Division by Zero!)
// Right[int](10)
// 0
// 10
// 0
// 20
// 0
// 20
}

41
v2/result/exec/exec.go Normal file
View File

@@ -0,0 +1,41 @@
// 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 exec provides utilities for executing system commands with Either-based error handling.
package exec
import (
"github.com/IBM/fp-go/v2/either/exec"
)
var (
// Command executes a system command and returns the result as an Either.
// Use this version if the command does not produce any side effects,
// i.e., if the output is uniquely determined by the input.
// For commands with side effects, typically you'd use the IOEither version instead.
//
// Parameters (curried):
// - name: The command name/path
// - args: Command arguments
// - in: Input bytes to send to the command's stdin
//
// Returns Result[CommandOutput] containing the command's output or an error.
//
// Example:
//
// result := exec.Command("echo")( []string{"hello"})([]byte{})
// // result is Right(CommandOutput{Stdout: "hello\n", ...})
Command = exec.Command
)

7
v2/result/exec/types.go Normal file
View File

@@ -0,0 +1,7 @@
package exec
import "github.com/IBM/fp-go/v2/result"
type (
Result[T any] = result.Result[T]
)

39
v2/result/functor.go Normal file
View File

@@ -0,0 +1,39 @@
// Copyright (c) 2024 - 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 result
import (
"github.com/IBM/fp-go/v2/internal/functor"
)
type eitherFunctor[A, B any] struct{}
func (o *eitherFunctor[A, B]) Map(f func(A) B) Operator[A, B] {
return Map[A, B](f)
}
// Functor implements the functoric operations for Either.
// A functor provides the Map operation that transforms values inside a context
// while preserving the structure.
//
// Example:
//
// f := either.Functor[error, int, string]()
// result := f.Map(strconv.Itoa)(either.Right[error](42))
// // result is Right("42")
func Functor[A, B any]() functor.Functor[A, B, Result[A], Result[B]] {
return &eitherFunctor[A, B]{}
}

589
v2/result/gen.go Normal file
View File

@@ -0,0 +1,589 @@
// Code generated by go generate; DO NOT EDIT.
// This file was generated by robots at
// 2025-03-09 23:52:50.4396159 +0100 CET m=+0.001548701
//
// This file contains generated functions for converting between Either and tuple-based functions.
// It provides Eitherize/Uneitherize functions for functions with 0-15 parameters,
// as well as SequenceT/SequenceTuple/TraverseTuple functions for working with tuples of Either values.
// Code generated by go generate; DO NOT EDIT.
// This file was generated by robots at
// 2025-03-09 23:52:50.4396159 +0100 CET m=+0.001548701
package result
import (
"github.com/IBM/fp-go/v2/either"
T "github.com/IBM/fp-go/v2/tuple"
)
// Eitherize0 converts a function with 0 parameters returning a tuple into a function with 0 parameters returning an Either
// The inverse function is [Uneitherize0]
//
//go:inline
func Eitherize0[F ~func() (R, error), R any](f F) func() Result[R] {
return either.Eitherize0(f)
}
// Uneitherize0 converts a function with 0 parameters returning an Either into a function with 0 parameters returning a tuple
// The inverse function is [Eitherize0]
//
//go:inline
func Uneitherize0[F ~func() Result[R], R any](f F) func() (R, error) {
return either.Uneitherize0(f)
}
// Eitherize1 converts a function with 1 parameters returning a tuple into a function with 1 parameters returning an Either
// The inverse function is [Uneitherize1]
//
//go:inline
func Eitherize1[F ~func(T0) (R, error), T0, R any](f F) func(T0) Result[R] {
return either.Eitherize1(f)
}
// Uneitherize1 converts a function with 1 parameters returning an Either into a function with 1 parameters returning a tuple
// The inverse function is [Eitherize1]
//
//go:inline
func Uneitherize1[F ~func(T0) Result[R], T0, R any](f F) func(T0) (R, error) {
return either.Uneitherize1(f)
}
// SequenceT1 converts 1 parameters of [Result[T]] into a [Result[Tuple1]].
//
//go:inline
func SequenceT1[T1 any](t1 Result[T1]) Result[T.Tuple1[T1]] {
return either.SequenceT1(t1)
}
// SequenceTuple1 converts a [Tuple1] of [Result[T]] into an [Result[Tuple1]].
//
//go:inline
func SequenceTuple1[T1 any](t T.Tuple1[Result[T1]]) Result[T.Tuple1[T1]] {
return either.SequenceTuple1(t)
}
// TraverseTuple1 converts a [Tuple1] of [A] via transformation functions transforming [A] to [Result[A]] into a [Result[Tuple1]].
//
//go:inline
func TraverseTuple1[F1 ~func(A1) Result[T1], A1, T1 any](f1 F1) func(T.Tuple1[A1]) Result[T.Tuple1[T1]] {
return either.TraverseTuple1(f1)
}
// Eitherize2 converts a function with 2 parameters returning a tuple into a function with 2 parameters returning an Either
// The inverse function is [Uneitherize2]
//
//go:inline
func Eitherize2[F ~func(T0, T1) (R, error), T0, T1, R any](f F) func(T0, T1) Result[R] {
return either.Eitherize2(f)
}
// Uneitherize2 converts a function with 2 parameters returning an Either into a function with 2 parameters returning a tuple
// The inverse function is [Eitherize2]
//
//go:inline
func Uneitherize2[F ~func(T0, T1) Result[R], T0, T1, R any](f F) func(T0, T1) (R, error) {
return either.Uneitherize2(f)
}
// SequenceT2 converts 2 parameters of [Result[T]] into a [Result[Tuple2]].
//
//go:inline
func SequenceT2[T1, T2 any](t1 Result[T1], t2 Result[T2]) Result[T.Tuple2[T1, T2]] {
return either.SequenceT2(t1, t2)
}
// SequenceTuple2 converts a [Tuple2] of [Result[T]] into an [Result[Tuple2]].
//
//go:inline
func SequenceTuple2[T1, T2 any](t T.Tuple2[Result[T1], Result[T2]]) Result[T.Tuple2[T1, T2]] {
return either.SequenceTuple2(t)
}
// TraverseTuple2 converts a [Tuple2] of [A] via transformation functions transforming [A] to [Result[A]] into a [Result[Tuple2]].
//
//go:inline
func TraverseTuple2[F1 ~func(A1) Result[T1], F2 ~func(A2) Result[T2], A1, T1, A2, T2 any](f1 F1, f2 F2) func(T.Tuple2[A1, A2]) Result[T.Tuple2[T1, T2]] {
return either.TraverseTuple2(f1, f2)
}
// Eitherize3 converts a function with 3 parameters returning a tuple into a function with 3 parameters returning an Either
// The inverse function is [Uneitherize3]
//
//go:inline
func Eitherize3[F ~func(T0, T1, T2) (R, error), T0, T1, T2, R any](f F) func(T0, T1, T2) Result[R] {
return either.Eitherize3(f)
}
// Uneitherize3 converts a function with 3 parameters returning an Either into a function with 3 parameters returning a tuple
// The inverse function is [Eitherize3]
//
//go:inline
func Uneitherize3[F ~func(T0, T1, T2) Result[R], T0, T1, T2, R any](f F) func(T0, T1, T2) (R, error) {
return either.Uneitherize3(f)
}
// SequenceT3 converts 3 parameters of [Result[T]] into a [Result[Tuple3]].
//
//go:inline
func SequenceT3[T1, T2, T3 any](t1 Result[T1], t2 Result[T2], t3 Result[T3]) Result[T.Tuple3[T1, T2, T3]] {
return either.SequenceT3(t1, t2, t3)
}
// SequenceTuple3 converts a [Tuple3] of [Result[T]] into an [Result[Tuple3]].
//
//go:inline
func SequenceTuple3[T1, T2, T3 any](t T.Tuple3[Result[T1], Result[T2], Result[T3]]) Result[T.Tuple3[T1, T2, T3]] {
return either.SequenceTuple3(t)
}
// TraverseTuple3 converts a [Tuple3] of [A] via transformation functions transforming [A] to [Result[A]] into a [Result[Tuple3]].
//
//go:inline
func TraverseTuple3[F1 ~func(A1) Result[T1], F2 ~func(A2) Result[T2], F3 ~func(A3) Result[T3], A1, T1, A2, T2, A3, T3 any](f1 F1, f2 F2, f3 F3) func(T.Tuple3[A1, A2, A3]) Result[T.Tuple3[T1, T2, T3]] {
return either.TraverseTuple3(f1, f2, f3)
}
// Eitherize4 converts a function with 4 parameters returning a tuple into a function with 4 parameters returning an Either
// The inverse function is [Uneitherize4]
//
//go:inline
func Eitherize4[F ~func(T0, T1, T2, T3) (R, error), T0, T1, T2, T3, R any](f F) func(T0, T1, T2, T3) Result[R] {
return either.Eitherize4(f)
}
// Uneitherize4 converts a function with 4 parameters returning an Either into a function with 4 parameters returning a tuple
// The inverse function is [Eitherize4]
//
//go:inline
func Uneitherize4[F ~func(T0, T1, T2, T3) Result[R], T0, T1, T2, T3, R any](f F) func(T0, T1, T2, T3) (R, error) {
return either.Uneitherize4(f)
}
// SequenceT4 converts 4 parameters of [Result[T]] into a [Result[Tuple4]].
//
//go:inline
func SequenceT4[T1, T2, T3, T4 any](t1 Result[T1], t2 Result[T2], t3 Result[T3], t4 Result[T4]) Result[T.Tuple4[T1, T2, T3, T4]] {
return either.SequenceT4(t1, t2, t3, t4)
}
// SequenceTuple4 converts a [Tuple4] of [Result[T]] into an [Result[Tuple4]].
//
//go:inline
func SequenceTuple4[T1, T2, T3, T4 any](t T.Tuple4[Result[T1], Result[T2], Result[T3], Result[T4]]) Result[T.Tuple4[T1, T2, T3, T4]] {
return either.SequenceTuple4(t)
}
// TraverseTuple4 converts a [Tuple4] of [A] via transformation functions transforming [A] to [Result[A]] into a [Result[Tuple4]].
//
//go:inline
func TraverseTuple4[F1 ~func(A1) Result[T1], F2 ~func(A2) Result[T2], F3 ~func(A3) Result[T3], F4 ~func(A4) Result[T4], A1, T1, A2, T2, A3, T3, A4, T4 any](f1 F1, f2 F2, f3 F3, f4 F4) func(T.Tuple4[A1, A2, A3, A4]) Result[T.Tuple4[T1, T2, T3, T4]] {
return either.TraverseTuple4(f1, f2, f3, f4)
}
// Eitherize5 converts a function with 5 parameters returning a tuple into a function with 5 parameters returning an Either
// The inverse function is [Uneitherize5]
//
//go:inline
func Eitherize5[F ~func(T0, T1, T2, T3, T4) (R, error), T0, T1, T2, T3, T4, R any](f F) func(T0, T1, T2, T3, T4) Result[R] {
return either.Eitherize5(f)
}
// Uneitherize5 converts a function with 5 parameters returning an Either into a function with 5 parameters returning a tuple
// The inverse function is [Eitherize5]
//
//go:inline
func Uneitherize5[F ~func(T0, T1, T2, T3, T4) Result[R], T0, T1, T2, T3, T4, R any](f F) func(T0, T1, T2, T3, T4) (R, error) {
return either.Uneitherize5(f)
}
// SequenceT5 converts 5 parameters of [Result[T]] into a [Result[Tuple5]].
//
//go:inline
func SequenceT5[T1, T2, T3, T4, T5 any](t1 Result[T1], t2 Result[T2], t3 Result[T3], t4 Result[T4], t5 Result[T5]) Result[T.Tuple5[T1, T2, T3, T4, T5]] {
return either.SequenceT5(t1, t2, t3, t4, t5)
}
// SequenceTuple5 converts a [Tuple5] of [Result[T]] into an [Result[Tuple5]].
//
//go:inline
func SequenceTuple5[T1, T2, T3, T4, T5 any](t T.Tuple5[Result[T1], Result[T2], Result[T3], Result[T4], Result[T5]]) Result[T.Tuple5[T1, T2, T3, T4, T5]] {
return either.SequenceTuple5(t)
}
// TraverseTuple5 converts a [Tuple5] of [A] via transformation functions transforming [A] to [Result[A]] into a [Result[Tuple5]].
//
//go:inline
func TraverseTuple5[F1 ~func(A1) Result[T1], F2 ~func(A2) Result[T2], F3 ~func(A3) Result[T3], F4 ~func(A4) Result[T4], F5 ~func(A5) Result[T5], A1, T1, A2, T2, A3, T3, A4, T4, A5, T5 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) func(T.Tuple5[A1, A2, A3, A4, A5]) Result[T.Tuple5[T1, T2, T3, T4, T5]] {
return either.TraverseTuple5(f1, f2, f3, f4, f5)
}
// Eitherize6 converts a function with 6 parameters returning a tuple into a function with 6 parameters returning an Either
// The inverse function is [Uneitherize6]
//
//go:inline
func Eitherize6[F ~func(T0, T1, T2, T3, T4, T5) (R, error), T0, T1, T2, T3, T4, T5, R any](f F) func(T0, T1, T2, T3, T4, T5) Result[R] {
return either.Eitherize6(f)
}
// Uneitherize6 converts a function with 6 parameters returning an Either into a function with 6 parameters returning a tuple
// The inverse function is [Eitherize6]
//
//go:inline
func Uneitherize6[F ~func(T0, T1, T2, T3, T4, T5) Result[R], T0, T1, T2, T3, T4, T5, R any](f F) func(T0, T1, T2, T3, T4, T5) (R, error) {
return either.Uneitherize6(f)
}
// SequenceT6 converts 6 parameters of [Result[T]] into a [Result[Tuple6]].
//
//go:inline
func SequenceT6[T1, T2, T3, T4, T5, T6 any](t1 Result[T1], t2 Result[T2], t3 Result[T3], t4 Result[T4], t5 Result[T5], t6 Result[T6]) Result[T.Tuple6[T1, T2, T3, T4, T5, T6]] {
return either.SequenceT6(t1, t2, t3, t4, t5, t6)
}
// SequenceTuple6 converts a [Tuple6] of [Result[T]] into an [Result[Tuple6]].
//
//go:inline
func SequenceTuple6[T1, T2, T3, T4, T5, T6 any](t T.Tuple6[Result[T1], Result[T2], Result[T3], Result[T4], Result[T5], Result[T6]]) Result[T.Tuple6[T1, T2, T3, T4, T5, T6]] {
return either.SequenceTuple6(t)
}
// TraverseTuple6 converts a [Tuple6] of [A] via transformation functions transforming [A] to [Result[A]] into a [Result[Tuple6]].
//
//go:inline
func TraverseTuple6[F1 ~func(A1) Result[T1], F2 ~func(A2) Result[T2], F3 ~func(A3) Result[T3], F4 ~func(A4) Result[T4], F5 ~func(A5) Result[T5], F6 ~func(A6) Result[T6], A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6) func(T.Tuple6[A1, A2, A3, A4, A5, A6]) Result[T.Tuple6[T1, T2, T3, T4, T5, T6]] {
return either.TraverseTuple6(f1, f2, f3, f4, f5, f6)
}
// Eitherize7 converts a function with 7 parameters returning a tuple into a function with 7 parameters returning an Either
// The inverse function is [Uneitherize7]
//
//go:inline
func Eitherize7[F ~func(T0, T1, T2, T3, T4, T5, T6) (R, error), T0, T1, T2, T3, T4, T5, T6, R any](f F) func(T0, T1, T2, T3, T4, T5, T6) Result[R] {
return either.Eitherize7(f)
}
// Uneitherize7 converts a function with 7 parameters returning an Either into a function with 7 parameters returning a tuple
// The inverse function is [Eitherize7]
//
//go:inline
func Uneitherize7[F ~func(T0, T1, T2, T3, T4, T5, T6) Result[R], T0, T1, T2, T3, T4, T5, T6, R any](f F) func(T0, T1, T2, T3, T4, T5, T6) (R, error) {
return either.Uneitherize7(f)
}
// SequenceT7 converts 7 parameters of [Result[T]] into a [Result[Tuple7]].
//
//go:inline
func SequenceT7[T1, T2, T3, T4, T5, T6, T7 any](t1 Result[T1], t2 Result[T2], t3 Result[T3], t4 Result[T4], t5 Result[T5], t6 Result[T6], t7 Result[T7]) Result[T.Tuple7[T1, T2, T3, T4, T5, T6, T7]] {
return either.SequenceT7(t1, t2, t3, t4, t5, t6, t7)
}
// SequenceTuple7 converts a [Tuple7] of [Result[T]] into an [Result[Tuple7]].
//
//go:inline
func SequenceTuple7[T1, T2, T3, T4, T5, T6, T7 any](t T.Tuple7[Result[T1], Result[T2], Result[T3], Result[T4], Result[T5], Result[T6], Result[T7]]) Result[T.Tuple7[T1, T2, T3, T4, T5, T6, T7]] {
return either.SequenceTuple7(t)
}
// TraverseTuple7 converts a [Tuple7] of [A] via transformation functions transforming [A] to [Result[A]] into a [Result[Tuple7]].
//
//go:inline
func TraverseTuple7[F1 ~func(A1) Result[T1], F2 ~func(A2) Result[T2], F3 ~func(A3) Result[T3], F4 ~func(A4) Result[T4], F5 ~func(A5) Result[T5], F6 ~func(A6) Result[T6], F7 ~func(A7) Result[T7], A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7) func(T.Tuple7[A1, A2, A3, A4, A5, A6, A7]) Result[T.Tuple7[T1, T2, T3, T4, T5, T6, T7]] {
return either.TraverseTuple7(f1, f2, f3, f4, f5, f6, f7)
}
// Eitherize8 converts a function with 8 parameters returning a tuple into a function with 8 parameters returning an Either
// The inverse function is [Uneitherize8]
//
//go:inline
func Eitherize8[F ~func(T0, T1, T2, T3, T4, T5, T6, T7) (R, error), T0, T1, T2, T3, T4, T5, T6, T7, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7) Result[R] {
return either.Eitherize8(f)
}
// Uneitherize8 converts a function with 8 parameters returning an Either into a function with 8 parameters returning a tuple
// The inverse function is [Eitherize8]
//
//go:inline
func Uneitherize8[F ~func(T0, T1, T2, T3, T4, T5, T6, T7) Result[R], T0, T1, T2, T3, T4, T5, T6, T7, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7) (R, error) {
return either.Uneitherize8(f)
}
// SequenceT8 converts 8 parameters of [Result[T]] into a [Result[Tuple8]].
//
//go:inline
func SequenceT8[T1, T2, T3, T4, T5, T6, T7, T8 any](t1 Result[T1], t2 Result[T2], t3 Result[T3], t4 Result[T4], t5 Result[T5], t6 Result[T6], t7 Result[T7], t8 Result[T8]) Result[T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] {
return either.SequenceT8(t1, t2, t3, t4, t5, t6, t7, t8)
}
// SequenceTuple8 converts a [Tuple8] of [Result[T]] into an [Result[Tuple8]].
//
//go:inline
func SequenceTuple8[T1, T2, T3, T4, T5, T6, T7, T8 any](t T.Tuple8[Result[T1], Result[T2], Result[T3], Result[T4], Result[T5], Result[T6], Result[T7], Result[T8]]) Result[T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] {
return either.SequenceTuple8(t)
}
// TraverseTuple8 converts a [Tuple8] of [A] via transformation functions transforming [A] to [Result[A]] into a [Result[Tuple8]].
//
//go:inline
func TraverseTuple8[F1 ~func(A1) Result[T1], F2 ~func(A2) Result[T2], F3 ~func(A3) Result[T3], F4 ~func(A4) Result[T4], F5 ~func(A5) Result[T5], F6 ~func(A6) Result[T6], F7 ~func(A7) Result[T7], F8 ~func(A8) Result[T8], A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7, A8, T8 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8) func(T.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) Result[T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] {
return either.TraverseTuple8(f1, f2, f3, f4, f5, f6, f7, f8)
}
// Eitherize9 converts a function with 9 parameters returning a tuple into a function with 9 parameters returning an Either
// The inverse function is [Uneitherize9]
//
//go:inline
func Eitherize9[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8) (R, error), T0, T1, T2, T3, T4, T5, T6, T7, T8, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8) Result[R] {
return either.Eitherize9(f)
}
// Uneitherize9 converts a function with 9 parameters returning an Either into a function with 9 parameters returning a tuple
// The inverse function is [Eitherize9]
//
//go:inline
func Uneitherize9[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8) Result[R], T0, T1, T2, T3, T4, T5, T6, T7, T8, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8) (R, error) {
return either.Uneitherize9(f)
}
// SequenceT9 converts 9 parameters of [Result[T]] into a [Result[Tuple9]].
//
//go:inline
func SequenceT9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any](t1 Result[T1], t2 Result[T2], t3 Result[T3], t4 Result[T4], t5 Result[T5], t6 Result[T6], t7 Result[T7], t8 Result[T8], t9 Result[T9]) Result[T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] {
return either.SequenceT9(t1, t2, t3, t4, t5, t6, t7, t8, t9)
}
// SequenceTuple9 converts a [Tuple9] of [Result[T]] into an [Result[Tuple9]].
//
//go:inline
func SequenceTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any](t T.Tuple9[Result[T1], Result[T2], Result[T3], Result[T4], Result[T5], Result[T6], Result[T7], Result[T8], Result[T9]]) Result[T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] {
return either.SequenceTuple9(t)
}
// TraverseTuple9 converts a [Tuple9] of [A] via transformation functions transforming [A] to [Result[A]] into a [Result[Tuple9]].
//
//go:inline
func TraverseTuple9[F1 ~func(A1) Result[T1], F2 ~func(A2) Result[T2], F3 ~func(A3) Result[T3], F4 ~func(A4) Result[T4], F5 ~func(A5) Result[T5], F6 ~func(A6) Result[T6], F7 ~func(A7) Result[T7], F8 ~func(A8) Result[T8], F9 ~func(A9) Result[T9], A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7, A8, T8, A9, T9 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9) func(T.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) Result[T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] {
return either.TraverseTuple9(f1, f2, f3, f4, f5, f6, f7, f8, f9)
}
// Eitherize10 converts a function with 10 parameters returning a tuple into a function with 10 parameters returning an Either
// The inverse function is [Uneitherize10]
//
//go:inline
func Eitherize10[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) (R, error), T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) Result[R] {
return either.Eitherize10(f)
}
// Uneitherize10 converts a function with 10 parameters returning an Either into a function with 10 parameters returning a tuple
// The inverse function is [Eitherize10]
//
//go:inline
func Uneitherize10[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) Result[R], T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) (R, error) {
return either.Uneitherize10(f)
}
// SequenceT10 converts 10 parameters of [Result[T]] into a [Result[Tuple10]].
//
//go:inline
func SequenceT10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](t1 Result[T1], t2 Result[T2], t3 Result[T3], t4 Result[T4], t5 Result[T5], t6 Result[T6], t7 Result[T7], t8 Result[T8], t9 Result[T9], t10 Result[T10]) Result[T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] {
return either.SequenceT10(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10)
}
// SequenceTuple10 converts a [Tuple10] of [Result[T]] into an [Result[Tuple10]].
//
//go:inline
func SequenceTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](t T.Tuple10[Result[T1], Result[T2], Result[T3], Result[T4], Result[T5], Result[T6], Result[T7], Result[T8], Result[T9], Result[T10]]) Result[T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] {
return either.SequenceTuple10(t)
}
// TraverseTuple10 converts a [Tuple10] of [A] via transformation functions transforming [A] to [Result[A]] into a [Result[Tuple10]].
//
//go:inline
func TraverseTuple10[F1 ~func(A1) Result[T1], F2 ~func(A2) Result[T2], F3 ~func(A3) Result[T3], F4 ~func(A4) Result[T4], F5 ~func(A5) Result[T5], F6 ~func(A6) Result[T6], F7 ~func(A7) Result[T7], F8 ~func(A8) Result[T8], F9 ~func(A9) Result[T9], F10 ~func(A10) Result[T10], A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7, A8, T8, A9, T9, A10, T10 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10) func(T.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) Result[T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] {
return either.TraverseTuple10(f1, f2, f3, f4, f5, f6, f7, f8, f9, f10)
}
// Eitherize11 converts a function with 11 parameters returning a tuple into a function with 11 parameters returning an Either
// The inverse function is [Uneitherize11]
//
//go:inline
func Eitherize11[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) (R, error), T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) Result[R] {
return either.Eitherize11(f)
}
// Uneitherize11 converts a function with 11 parameters returning an Either into a function with 11 parameters returning a tuple
// The inverse function is [Eitherize11]
//
//go:inline
func Uneitherize11[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) Result[R], T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) (R, error) {
return either.Uneitherize11(f)
}
// SequenceT11 converts 11 parameters of [Result[T]] into a [Result[Tuple11]].
//
//go:inline
func SequenceT11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any](t1 Result[T1], t2 Result[T2], t3 Result[T3], t4 Result[T4], t5 Result[T5], t6 Result[T6], t7 Result[T7], t8 Result[T8], t9 Result[T9], t10 Result[T10], t11 Result[T11]) Result[T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] {
return either.SequenceT11(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11)
}
// SequenceTuple11 converts a [Tuple11] of [Result[T]] into an [Result[Tuple11]].
//
//go:inline
func SequenceTuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any](t T.Tuple11[Result[T1], Result[T2], Result[T3], Result[T4], Result[T5], Result[T6], Result[T7], Result[T8], Result[T9], Result[T10], Result[T11]]) Result[T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] {
return either.SequenceTuple11(t)
}
// TraverseTuple11 converts a [Tuple11] of [A] via transformation functions transforming [A] to [Result[A]] into a [Result[Tuple11]].
//
//go:inline
func TraverseTuple11[F1 ~func(A1) Result[T1], F2 ~func(A2) Result[T2], F3 ~func(A3) Result[T3], F4 ~func(A4) Result[T4], F5 ~func(A5) Result[T5], F6 ~func(A6) Result[T6], F7 ~func(A7) Result[T7], F8 ~func(A8) Result[T8], F9 ~func(A9) Result[T9], F10 ~func(A10) Result[T10], F11 ~func(A11) Result[T11], A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7, A8, T8, A9, T9, A10, T10, A11, T11 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11) func(T.Tuple11[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11]) Result[T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] {
return either.TraverseTuple11(f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11)
}
// Eitherize12 converts a function with 12 parameters returning a tuple into a function with 12 parameters returning an Either
// The inverse function is [Uneitherize12]
//
//go:inline
func Eitherize12[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) (R, error), T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) Result[R] {
return either.Eitherize12(f)
}
// Uneitherize12 converts a function with 12 parameters returning an Either into a function with 12 parameters returning a tuple
// The inverse function is [Eitherize12]
//
//go:inline
func Uneitherize12[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) Result[R], T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) (R, error) {
return either.Uneitherize12(f)
}
// SequenceT12 converts 12 parameters of [Result[T]] into a [Result[Tuple12]].
//
//go:inline
func SequenceT12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any](t1 Result[T1], t2 Result[T2], t3 Result[T3], t4 Result[T4], t5 Result[T5], t6 Result[T6], t7 Result[T7], t8 Result[T8], t9 Result[T9], t10 Result[T10], t11 Result[T11], t12 Result[T12]) Result[T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] {
return either.SequenceT12(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12)
}
// SequenceTuple12 converts a [Tuple12] of [Result[T]] into an [Result[Tuple12]].
//
//go:inline
func SequenceTuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any](t T.Tuple12[Result[T1], Result[T2], Result[T3], Result[T4], Result[T5], Result[T6], Result[T7], Result[T8], Result[T9], Result[T10], Result[T11], Result[T12]]) Result[T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] {
return either.SequenceTuple12(t)
}
// TraverseTuple12 converts a [Tuple12] of [A] via transformation functions transforming [A] to [Result[A]] into a [Result[Tuple12]].
//
//go:inline
func TraverseTuple12[F1 ~func(A1) Result[T1], F2 ~func(A2) Result[T2], F3 ~func(A3) Result[T3], F4 ~func(A4) Result[T4], F5 ~func(A5) Result[T5], F6 ~func(A6) Result[T6], F7 ~func(A7) Result[T7], F8 ~func(A8) Result[T8], F9 ~func(A9) Result[T9], F10 ~func(A10) Result[T10], F11 ~func(A11) Result[T11], F12 ~func(A12) Result[T12], A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7, A8, T8, A9, T9, A10, T10, A11, T11, A12, T12 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12) func(T.Tuple12[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12]) Result[T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] {
return either.TraverseTuple12(f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12)
}
// Eitherize13 converts a function with 13 parameters returning a tuple into a function with 13 parameters returning an Either
// The inverse function is [Uneitherize13]
//
//go:inline
func Eitherize13[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) (R, error), T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) Result[R] {
return either.Eitherize13(f)
}
// Uneitherize13 converts a function with 13 parameters returning an Either into a function with 13 parameters returning a tuple
// The inverse function is [Eitherize13]
//
//go:inline
func Uneitherize13[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) Result[R], T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) (R, error) {
return either.Uneitherize13(f)
}
// SequenceT13 converts 13 parameters of [Result[T]] into a [Result[Tuple13]].
//
//go:inline
func SequenceT13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any](t1 Result[T1], t2 Result[T2], t3 Result[T3], t4 Result[T4], t5 Result[T5], t6 Result[T6], t7 Result[T7], t8 Result[T8], t9 Result[T9], t10 Result[T10], t11 Result[T11], t12 Result[T12], t13 Result[T13]) Result[T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] {
return either.SequenceT13(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13)
}
// SequenceTuple13 converts a [Tuple13] of [Result[T]] into an [Result[Tuple13]].
//
//go:inline
func SequenceTuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any](t T.Tuple13[Result[T1], Result[T2], Result[T3], Result[T4], Result[T5], Result[T6], Result[T7], Result[T8], Result[T9], Result[T10], Result[T11], Result[T12], Result[T13]]) Result[T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] {
return either.SequenceTuple13(t)
}
// TraverseTuple13 converts a [Tuple13] of [A] via transformation functions transforming [A] to [Result[A]] into a [Result[Tuple13]].
//
//go:inline
func TraverseTuple13[F1 ~func(A1) Result[T1], F2 ~func(A2) Result[T2], F3 ~func(A3) Result[T3], F4 ~func(A4) Result[T4], F5 ~func(A5) Result[T5], F6 ~func(A6) Result[T6], F7 ~func(A7) Result[T7], F8 ~func(A8) Result[T8], F9 ~func(A9) Result[T9], F10 ~func(A10) Result[T10], F11 ~func(A11) Result[T11], F12 ~func(A12) Result[T12], F13 ~func(A13) Result[T13], A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7, A8, T8, A9, T9, A10, T10, A11, T11, A12, T12, A13, T13 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13) func(T.Tuple13[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13]) Result[T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] {
return either.TraverseTuple13(f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13)
}
// Eitherize14 converts a function with 14 parameters returning a tuple into a function with 14 parameters returning an Either
// The inverse function is [Uneitherize14]
//
//go:inline
func Eitherize14[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) (R, error), T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) Result[R] {
return either.Eitherize14(f)
}
// Uneitherize14 converts a function with 14 parameters returning an Either into a function with 14 parameters returning a tuple
// The inverse function is [Eitherize14]
//
//go:inline
func Uneitherize14[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) Result[R], T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) (R, error) {
return either.Uneitherize14(f)
}
// SequenceT14 converts 14 parameters of [Result[T]] into a [Result[Tuple14]].
//
//go:inline
func SequenceT14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any](t1 Result[T1], t2 Result[T2], t3 Result[T3], t4 Result[T4], t5 Result[T5], t6 Result[T6], t7 Result[T7], t8 Result[T8], t9 Result[T9], t10 Result[T10], t11 Result[T11], t12 Result[T12], t13 Result[T13], t14 Result[T14]) Result[T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] {
return either.SequenceT14(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14)
}
// SequenceTuple14 converts a [Tuple14] of [Result[T]] into an [Result[Tuple14]].
//
//go:inline
func SequenceTuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any](t T.Tuple14[Result[T1], Result[T2], Result[T3], Result[T4], Result[T5], Result[T6], Result[T7], Result[T8], Result[T9], Result[T10], Result[T11], Result[T12], Result[T13], Result[T14]]) Result[T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] {
return either.SequenceTuple14(t)
}
// TraverseTuple14 converts a [Tuple14] of [A] via transformation functions transforming [A] to [Result[A]] into a [Result[Tuple14]].
//
//go:inline
func TraverseTuple14[F1 ~func(A1) Result[T1], F2 ~func(A2) Result[T2], F3 ~func(A3) Result[T3], F4 ~func(A4) Result[T4], F5 ~func(A5) Result[T5], F6 ~func(A6) Result[T6], F7 ~func(A7) Result[T7], F8 ~func(A8) Result[T8], F9 ~func(A9) Result[T9], F10 ~func(A10) Result[T10], F11 ~func(A11) Result[T11], F12 ~func(A12) Result[T12], F13 ~func(A13) Result[T13], F14 ~func(A14) Result[T14], A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7, A8, T8, A9, T9, A10, T10, A11, T11, A12, T12, A13, T13, A14, T14 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14) func(T.Tuple14[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14]) Result[T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] {
return either.TraverseTuple14(f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14)
}
// Eitherize15 converts a function with 15 parameters returning a tuple into a function with 15 parameters returning an Either
// The inverse function is [Uneitherize15]
//
//go:inline
func Eitherize15[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) (R, error), T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) Result[R] {
return either.Eitherize15(f)
}
// Uneitherize15 converts a function with 15 parameters returning an Either into a function with 15 parameters returning a tuple
// The inverse function is [Eitherize15]
//
//go:inline
func Uneitherize15[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) Result[R], T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) (R, error) {
return either.Uneitherize15(f)
}
// SequenceT15 converts 15 parameters of [Result[T]] into a [Result[Tuple15]].
//
//go:inline
func SequenceT15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 any](t1 Result[T1], t2 Result[T2], t3 Result[T3], t4 Result[T4], t5 Result[T5], t6 Result[T6], t7 Result[T7], t8 Result[T8], t9 Result[T9], t10 Result[T10], t11 Result[T11], t12 Result[T12], t13 Result[T13], t14 Result[T14], t15 Result[T15]) Result[T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] {
return either.SequenceT15(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15)
}
// SequenceTuple15 converts a [Tuple15] of [Result[T]] into an [Result[Tuple15]].
//
//go:inline
func SequenceTuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 any](t T.Tuple15[Result[T1], Result[T2], Result[T3], Result[T4], Result[T5], Result[T6], Result[T7], Result[T8], Result[T9], Result[T10], Result[T11], Result[T12], Result[T13], Result[T14], Result[T15]]) Result[T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] {
return either.SequenceTuple15(t)
}
// TraverseTuple15 converts a [Tuple15] of [A] via transformation functions transforming [A] to [Result[A]] into a [Result[Tuple15]].
//
//go:inline
func TraverseTuple15[F1 ~func(A1) Result[T1], F2 ~func(A2) Result[T2], F3 ~func(A3) Result[T3], F4 ~func(A4) Result[T4], F5 ~func(A5) Result[T5], F6 ~func(A6) Result[T6], F7 ~func(A7) Result[T7], F8 ~func(A8) Result[T8], F9 ~func(A9) Result[T9], F10 ~func(A10) Result[T10], F11 ~func(A11) Result[T11], F12 ~func(A12) Result[T12], F13 ~func(A13) Result[T13], F14 ~func(A14) Result[T14], F15 ~func(A15) Result[T15], A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7, A8, T8, A9, T9, A10, T10, A11, T11, A12, T12, A13, T13, A14, T14, A15, T15 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14, f15 F15) func(T.Tuple15[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15]) Result[T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] {
return either.TraverseTuple15(f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15)
}

View File

@@ -0,0 +1,558 @@
// 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 result
import (
"errors"
"testing"
O "github.com/IBM/fp-go/v2/option"
T "github.com/IBM/fp-go/v2/tuple"
"github.com/stretchr/testify/assert"
)
// Test MonadChainOptionK
func TestMonadChainOptionK(t *testing.T) {
f := func(n int) O.Option[int] {
if n > 0 {
return O.Some(n * 2)
}
return O.None[int]()
}
onNone := func() error { return errors.New("none") }
result := MonadChainOptionK(onNone, Right(5), f)
assert.Equal(t, Right(10), result)
result = MonadChainOptionK(onNone, Right(-1), f)
assert.Equal(t, Left[int](errors.New("none")), result)
result = MonadChainOptionK(onNone, Left[int](errors.New("error")), f)
assert.Equal(t, Left[int](errors.New("error")), result)
}
// Test Uneitherize1
func TestUneitherize1(t *testing.T) {
f := func(x int) Result[string] {
if x > 0 {
return Right("positive")
}
return Left[string](errors.New("negative"))
}
uneitherized := Uneitherize1(f)
result, err := uneitherized(5)
assert.NoError(t, err)
assert.Equal(t, "positive", result)
result, err = uneitherized(-1)
assert.Error(t, err)
}
// Test Uneitherize2
func TestUneitherize2(t *testing.T) {
f := func(x, y int) Result[int] {
if x > 0 && y > 0 {
return Right(x + y)
}
return Left[int](errors.New("invalid"))
}
uneitherized := Uneitherize2(f)
result, err := uneitherized(5, 3)
assert.NoError(t, err)
assert.Equal(t, 8, result)
result, err = uneitherized(-1, 3)
assert.Error(t, err)
}
// Test Uneitherize3
func TestUneitherize3(t *testing.T) {
f := func(x, y, z int) Result[int] {
return Right(x + y + z)
}
uneitherized := Uneitherize3(f)
result, err := uneitherized(1, 2, 3)
assert.NoError(t, err)
assert.Equal(t, 6, result)
}
// Test Uneitherize4
func TestUneitherize4(t *testing.T) {
f := func(a, b, c, d int) Result[int] {
return Right(a + b + c + d)
}
uneitherized := Uneitherize4(f)
result, err := uneitherized(1, 2, 3, 4)
assert.NoError(t, err)
assert.Equal(t, 10, result)
}
// Test SequenceT1
func TestSequenceT1(t *testing.T) {
result := SequenceT1(
Right(1),
)
expected := Right(T.MakeTuple1(1))
assert.Equal(t, expected, result)
result = SequenceT1(
Left[int](errors.New("error")),
)
assert.True(t, IsLeft(result))
}
// Test SequenceT2
func TestSequenceT2(t *testing.T) {
result := SequenceT2(
Right(1),
Right("hello"),
)
expected := Right(T.MakeTuple2(1, "hello"))
assert.Equal(t, expected, result)
result = SequenceT2(
Left[int](errors.New("error")),
Right("hello"),
)
assert.True(t, IsLeft(result))
}
// Test SequenceT3
func TestSequenceT3(t *testing.T) {
result := SequenceT3(
Right(1),
Right("hello"),
Right(true),
)
expected := Right(T.MakeTuple3(1, "hello", true))
assert.Equal(t, expected, result)
}
// Test SequenceT4
func TestSequenceT4(t *testing.T) {
result := SequenceT4(
Right(1),
Right(2),
Right(3),
Right(4),
)
expected := Right(T.MakeTuple4(1, 2, 3, 4))
assert.Equal(t, expected, result)
}
// Test SequenceTuple1
func TestSequenceTuple1(t *testing.T) {
tuple := T.MakeTuple1(Right(42))
result := SequenceTuple1(tuple)
expected := Right(T.MakeTuple1(42))
assert.Equal(t, expected, result)
}
// Test SequenceTuple2
func TestSequenceTuple2(t *testing.T) {
tuple := T.MakeTuple2(Right(1), Right("hello"))
result := SequenceTuple2(tuple)
expected := Right(T.MakeTuple2(1, "hello"))
assert.Equal(t, expected, result)
}
// Test SequenceTuple3
func TestSequenceTuple3(t *testing.T) {
tuple := T.MakeTuple3(Right(1), Right(2), Right(3))
result := SequenceTuple3(tuple)
expected := Right(T.MakeTuple3(1, 2, 3))
assert.Equal(t, expected, result)
}
// Test SequenceTuple4
func TestSequenceTuple4(t *testing.T) {
tuple := T.MakeTuple4(Right(1), Right(2), Right(3), Right(4))
result := SequenceTuple4(tuple)
expected := Right(T.MakeTuple4(1, 2, 3, 4))
assert.Equal(t, expected, result)
}
// Test TraverseTuple1
func TestTraverseTuple1(t *testing.T) {
f := func(x int) Result[string] {
if x > 0 {
return Right("positive")
}
return Left[string](errors.New("negative"))
}
tuple := T.MakeTuple1(5)
result := TraverseTuple1(f)(tuple)
expected := Right(T.MakeTuple1("positive"))
assert.Equal(t, expected, result)
}
// Test TraverseTuple2
func TestTraverseTuple2(t *testing.T) {
f1 := func(x int) Result[int] {
return Right(x * 2)
}
f2 := func(x int) Result[int] {
return Right(x * 2)
}
tuple := T.MakeTuple2(1, 2)
result := TraverseTuple2(f1, f2)(tuple)
expected := Right(T.MakeTuple2(2, 4))
assert.Equal(t, expected, result)
}
// Test TraverseTuple3
func TestTraverseTuple3(t *testing.T) {
f1 := func(x int) Result[int] {
return Right(x * 2)
}
f2 := func(x int) Result[int] {
return Right(x * 2)
}
f3 := func(x int) Result[int] {
return Right(x * 2)
}
tuple := T.MakeTuple3(1, 2, 3)
result := TraverseTuple3(f1, f2, f3)(tuple)
expected := Right(T.MakeTuple3(2, 4, 6))
assert.Equal(t, expected, result)
}
// Test TraverseTuple4
func TestTraverseTuple4(t *testing.T) {
f1 := func(x int) Result[int] {
return Right(x * 2)
}
f2 := func(x int) Result[int] {
return Right(x * 2)
}
f3 := func(x int) Result[int] {
return Right(x * 2)
}
f4 := func(x int) Result[int] {
return Right(x * 2)
}
tuple := T.MakeTuple4(1, 2, 3, 4)
result := TraverseTuple4(f1, f2, f3, f4)(tuple)
expected := Right(T.MakeTuple4(2, 4, 6, 8))
assert.Equal(t, expected, result)
}
// Test Eitherize5
func TestEitherize5(t *testing.T) {
f := func(a, b, c, d, e int) (int, error) {
return a + b + c + d + e, nil
}
eitherized := Eitherize5(f)
result := eitherized(1, 2, 3, 4, 5)
assert.Equal(t, Right(15), result)
}
// Test Uneitherize5
func TestUneitherize5(t *testing.T) {
f := func(a, b, c, d, e int) Result[int] {
return Right(a + b + c + d + e)
}
uneitherized := Uneitherize5(f)
result, err := uneitherized(1, 2, 3, 4, 5)
assert.NoError(t, err)
assert.Equal(t, 15, result)
}
// Test SequenceT5
func TestSequenceT5(t *testing.T) {
result := SequenceT5(
Right(1),
Right(2),
Right(3),
Right(4),
Right(5),
)
expected := Right(T.MakeTuple5(1, 2, 3, 4, 5))
assert.Equal(t, expected, result)
}
// Test SequenceTuple5
func TestSequenceTuple5(t *testing.T) {
tuple := T.MakeTuple5(
Right(1),
Right(2),
Right(3),
Right(4),
Right(5),
)
result := SequenceTuple5(tuple)
expected := Right(T.MakeTuple5(1, 2, 3, 4, 5))
assert.Equal(t, expected, result)
}
// Test TraverseTuple5
func TestTraverseTuple5(t *testing.T) {
f1 := func(x int) Result[int] {
return Right(x * 2)
}
f2 := func(x int) Result[int] {
return Right(x * 2)
}
f3 := func(x int) Result[int] {
return Right(x * 2)
}
f4 := func(x int) Result[int] {
return Right(x * 2)
}
f5 := func(x int) Result[int] {
return Right(x * 2)
}
tuple := T.MakeTuple5(1, 2, 3, 4, 5)
result := TraverseTuple5(f1, f2, f3, f4, f5)(tuple)
expected := Right(T.MakeTuple5(2, 4, 6, 8, 10))
assert.Equal(t, expected, result)
}
// Test higher arity functions (6-10) - sample tests
func TestEitherize6(t *testing.T) {
f := func(a, b, c, d, e, f int) (int, error) {
return a + b + c + d + e + f, nil
}
eitherized := Eitherize6(f)
result := eitherized(1, 2, 3, 4, 5, 6)
assert.Equal(t, Right(21), result)
}
func TestSequenceT6(t *testing.T) {
result := SequenceT6(
Right(1),
Right(2),
Right(3),
Right(4),
Right(5),
Right(6),
)
assert.True(t, IsRight(result))
}
func TestEitherize7(t *testing.T) {
f := func(a, b, c, d, e, f, g int) (int, error) {
return a + b + c + d + e + f + g, nil
}
eitherized := Eitherize7(f)
result := eitherized(1, 2, 3, 4, 5, 6, 7)
assert.Equal(t, Right(28), result)
}
func TestEitherize8(t *testing.T) {
f := func(a, b, c, d, e, f, g, h int) (int, error) {
return a + b + c + d + e + f + g + h, nil
}
eitherized := Eitherize8(f)
result := eitherized(1, 2, 3, 4, 5, 6, 7, 8)
assert.Equal(t, Right(36), result)
}
func TestEitherize9(t *testing.T) {
f := func(a, b, c, d, e, f, g, h, i int) (int, error) {
return a + b + c + d + e + f + g + h + i, nil
}
eitherized := Eitherize9(f)
result := eitherized(1, 2, 3, 4, 5, 6, 7, 8, 9)
assert.Equal(t, Right(45), result)
}
func TestEitherize10(t *testing.T) {
f := func(a, b, c, d, e, f, g, h, i, j int) (int, error) {
return a + b + c + d + e + f + g + h + i + j, nil
}
eitherized := Eitherize10(f)
result := eitherized(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
assert.Equal(t, Right(55), result)
}
func TestEitherize11(t *testing.T) {
f := func(a, b, c, d, e, f, g, h, i, j, k int) (int, error) {
return a + b + c + d + e + f + g + h + i + j + k, nil
}
eitherized := Eitherize11(f)
result := eitherized(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
assert.Equal(t, Right(66), result)
}
func TestEitherize12(t *testing.T) {
f := func(a, b, c, d, e, f, g, h, i, j, k, l int) (int, error) {
return a + b + c + d + e + f + g + h + i + j + k + l, nil
}
eitherized := Eitherize12(f)
result := eitherized(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
assert.Equal(t, Right(78), result)
}
func TestEitherize13(t *testing.T) {
f := func(a, b, c, d, e, f, g, h, i, j, k, l, m int) (int, error) {
return a + b + c + d + e + f + g + h + i + j + k + l + m, nil
}
eitherized := Eitherize13(f)
result := eitherized(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)
assert.Equal(t, Right(91), result)
}
func TestEitherize14(t *testing.T) {
f := func(a, b, c, d, e, f, g, h, i, j, k, l, m, n int) (int, error) {
return a + b + c + d + e + f + g + h + i + j + k + l + m + n, nil
}
eitherized := Eitherize14(f)
result := eitherized(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)
assert.Equal(t, Right(105), result)
}
func TestEitherize15(t *testing.T) {
f := func(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o int) (int, error) {
return a + b + c + d + e + f + g + h + i + j + k + l + m + n + o, nil
}
eitherized := Eitherize15(f)
result := eitherized(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)
assert.Equal(t, Right(120), result)
}
// Test Uneitherize functions for higher arities
func TestUneitherize6(t *testing.T) {
f := func(a, b, c, d, e, f int) Result[int] {
return Right(a + b + c + d + e + f)
}
uneitherized := Uneitherize6(f)
result, err := uneitherized(1, 2, 3, 4, 5, 6)
assert.NoError(t, err)
assert.Equal(t, 21, result)
}
func TestUneitherize7(t *testing.T) {
f := func(a, b, c, d, e, f, g int) Result[int] {
return Right(a + b + c + d + e + f + g)
}
uneitherized := Uneitherize7(f)
result, err := uneitherized(1, 2, 3, 4, 5, 6, 7)
assert.NoError(t, err)
assert.Equal(t, 28, result)
}
func TestUneitherize8(t *testing.T) {
f := func(a, b, c, d, e, f, g, h int) Result[int] {
return Right(a + b + c + d + e + f + g + h)
}
uneitherized := Uneitherize8(f)
result, err := uneitherized(1, 2, 3, 4, 5, 6, 7, 8)
assert.NoError(t, err)
assert.Equal(t, 36, result)
}
func TestUneitherize9(t *testing.T) {
f := func(a, b, c, d, e, f, g, h, i int) Result[int] {
return Right(a + b + c + d + e + f + g + h + i)
}
uneitherized := Uneitherize9(f)
result, err := uneitherized(1, 2, 3, 4, 5, 6, 7, 8, 9)
assert.NoError(t, err)
assert.Equal(t, 45, result)
}
func TestUneitherize10(t *testing.T) {
f := func(a, b, c, d, e, f, g, h, i, j int) Result[int] {
return Right(a + b + c + d + e + f + g + h + i + j)
}
uneitherized := Uneitherize10(f)
result, err := uneitherized(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
assert.NoError(t, err)
assert.Equal(t, 55, result)
}
func TestUneitherize11(t *testing.T) {
f := func(a, b, c, d, e, f, g, h, i, j, k int) Result[int] {
return Right(a + b + c + d + e + f + g + h + i + j + k)
}
uneitherized := Uneitherize11(f)
result, err := uneitherized(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
assert.NoError(t, err)
assert.Equal(t, 66, result)
}
func TestUneitherize12(t *testing.T) {
f := func(a, b, c, d, e, f, g, h, i, j, k, l int) Result[int] {
return Right(a + b + c + d + e + f + g + h + i + j + k + l)
}
uneitherized := Uneitherize12(f)
result, err := uneitherized(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
assert.NoError(t, err)
assert.Equal(t, 78, result)
}
func TestUneitherize13(t *testing.T) {
f := func(a, b, c, d, e, f, g, h, i, j, k, l, m int) Result[int] {
return Right(a + b + c + d + e + f + g + h + i + j + k + l + m)
}
uneitherized := Uneitherize13(f)
result, err := uneitherized(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)
assert.NoError(t, err)
assert.Equal(t, 91, result)
}
func TestUneitherize14(t *testing.T) {
f := func(a, b, c, d, e, f, g, h, i, j, k, l, m, n int) Result[int] {
return Right(a + b + c + d + e + f + g + h + i + j + k + l + m + n)
}
uneitherized := Uneitherize14(f)
result, err := uneitherized(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)
assert.NoError(t, err)
assert.Equal(t, 105, result)
}
func TestUneitherize15(t *testing.T) {
f := func(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o int) Result[int] {
return Right(a + b + c + d + e + f + g + h + i + j + k + l + m + n + o)
}
uneitherized := Uneitherize15(f)
result, err := uneitherized(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)
assert.NoError(t, err)
assert.Equal(t, 120, result)
}

55
v2/result/http/request.go Normal file
View File

@@ -0,0 +1,55 @@
// 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 http provides utilities for creating HTTP requests with Either-based error handling.
package http
import (
"github.com/IBM/fp-go/v2/either/http"
)
var (
// PostRequest creates a POST HTTP request with a body.
// Usage: PostRequest(url)(body) returns Result[*http.Request]
//
// Example:
//
// request := http.PostRequest("https://api.example.com/data")([]byte(`{"key":"value"}`))
PostRequest = http.PostRequest
// PutRequest creates a PUT HTTP request with a body.
// Usage: PutRequest(url)(body) returns Result[*http.Request]
PutRequest = http.PutRequest
// GetRequest creates a GET HTTP request without a body.
// Usage: GetRequest(url) returns Result[*http.Request]
//
// Example:
//
// request := http.GetRequest("https://api.example.com/data")
GetRequest = http.GetRequest
// DeleteRequest creates a DELETE HTTP request without a body.
// Usage: DeleteRequest(url) returns Result[*http.Request]
DeleteRequest = http.DeleteRequest
// OptionsRequest creates an OPTIONS HTTP request without a body.
// Usage: OptionsRequest(url) returns Result[*http.Request]
OptionsRequest = http.OptionsRequest
// HeadRequest creates a HEAD HTTP request without a body.
// Usage: HeadRequest(url) returns Result[*http.Request]
HeadRequest = http.HeadRequest
)

7
v2/result/http/types.go Normal file
View File

@@ -0,0 +1,7 @@
package http
import "github.com/IBM/fp-go/v2/result"
type (
Result[T any] = result.Result[T]
)

42
v2/result/logger.go Normal file
View File

@@ -0,0 +1,42 @@
// 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 result
import (
"log"
"github.com/IBM/fp-go/v2/either"
)
// Logger creates a logging function for Either values that logs both Left and Right cases.
// The function logs the value and then returns the original Either unchanged.
//
// Parameters:
// - loggers: Optional log.Logger instances. If none provided, uses default logger.
//
// Example:
//
// logger := either.Logger[error, int]()
// result := F.Pipe2(
// either.Right[error](42),
// logger("Processing"),
// either.Map(func(x int) int { return x * 2 }),
// )
// // Logs: "Processing: 42"
// // result is Right(84)
func Logger[A any](loggers ...*log.Logger) func(string) Operator[A, A] {
return either.Logger[error, A](loggers...)
}

37
v2/result/logger_test.go Normal file
View File

@@ -0,0 +1,37 @@
// 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 result
import (
"testing"
F "github.com/IBM/fp-go/v2/function"
"github.com/stretchr/testify/assert"
)
func TestLogger(t *testing.T) {
l := Logger[string]()
r := Right("test")
res := F.Pipe1(
r,
l("out"),
)
assert.Equal(t, r, res)
}

56
v2/result/monad.go Normal file
View File

@@ -0,0 +1,56 @@
// Copyright (c) 2024 - 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 result
import (
"github.com/IBM/fp-go/v2/internal/monad"
)
type eitherMonad[A, B any] struct{}
func (o *eitherMonad[A, B]) Of(a A) Result[A] {
return Of[A](a)
}
func (o *eitherMonad[A, B]) Map(f func(A) B) Operator[A, B] {
return Map[A, B](f)
}
func (o *eitherMonad[A, B]) Chain(f func(A) Result[B]) Operator[A, B] {
return Chain[A, B](f)
}
func (o *eitherMonad[A, B]) Ap(fa Result[A]) Operator[func(A) B, B] {
return Ap[B, A](fa)
}
// Monad implements the monadic operations for Either.
// A monad combines the capabilities of Functor (Map), Applicative (Ap), and Chain (flatMap/bind).
// This allows for sequential composition of computations that may fail.
//
// Example:
//
// m := either.Monad[error, int, string]()
// result := m.Chain(func(x int) either.Result[string] {
// if x > 0 {
// return either.Right[error](strconv.Itoa(x))
// }
// return either.Left[string](errors.New("negative"))
// })(either.Right[error](42))
// // result is Right("42")
func Monad[A, B any]() monad.Monad[A, B, Result[A], Result[B], Result[func(A) B]] {
return &eitherMonad[A, B]{}
}

54
v2/result/monoid.go Normal file
View File

@@ -0,0 +1,54 @@
// 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 result
import (
"github.com/IBM/fp-go/v2/either"
M "github.com/IBM/fp-go/v2/monoid"
)
// AlternativeMonoid creates a monoid for Either using applicative semantics.
// The empty value is Right with the monoid's empty value.
// Combines values using applicative operations.
//
// Example:
//
// import "github.com/IBM/fp-go/v2/monoid"
// intAdd := monoid.MakeMonoid(0, func(a, b int) int { return a + b })
// m := either.AlternativeMonoid[error](intAdd)
// result := m.Concat(either.Right[error](1), either.Right[error](2))
// // result is Right(3)
//
//go:inline
func AlternativeMonoid[A any](m M.Monoid[A]) Monoid[A] {
return either.AlternativeMonoid[error, A](m)
}
// AltMonoid creates a monoid for Either using the Alt operation.
// The empty value is provided as a lazy computation.
// When combining, returns the first Right value, or the second if the first is Left.
//
// Example:
//
// zero := func() either.Result[int] { return either.Left[int](errors.New("empty")) }
// m := either.AltMonoid[error, int](zero)
// result := m.Concat(either.Left[int](errors.New("err1")), either.Right[error](42))
// // result is Right(42)
//
//go:inline
func AltMonoid[A any](zero Lazy[Result[A]]) Monoid[A] {
return either.AltMonoid[error, A](zero)
}

37
v2/result/pointed.go Normal file
View File

@@ -0,0 +1,37 @@
// Copyright (c) 2024 - 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 result
import (
"github.com/IBM/fp-go/v2/internal/pointed"
)
type eitherPointed[A any] struct{}
func (o *eitherPointed[A]) Of(a A) Result[A] {
return Of[A](a)
}
// Pointed implements the pointed functor operations for Either.
// A pointed functor provides the Of operation to lift a value into the Either context.
//
// Example:
//
// p := either.Pointed[error, int]()
// result := p.Of(42) // Right(42)
func Pointed[A any]() pointed.Pointed[A, Result[A]] {
return &eitherPointed[A]{}
}

154
v2/result/record.go Normal file
View 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.
package result
import (
"github.com/IBM/fp-go/v2/either"
)
// TraverseRecordG transforms a map by applying a function that returns an Either to each value.
// If any value produces a Left, the entire result is that Left (short-circuits).
// Otherwise, returns Right containing the map of all Right values.
// The G suffix indicates support for generic map types.
//
// Example:
//
// parse := func(s string) either.Result[int] {
// v, err := strconv.Atoi(s)
// return either.FromError(v, err)
// }
// result := either.TraverseRecordG[map[string]string, map[string]int](parse)(map[string]string{"a": "1", "b": "2"})
// // result is Right(map[string]int{"a": 1, "b": 2})
//
//go:inline
func TraverseRecordG[GA ~map[K]A, GB ~map[K]B, K comparable, A, B any](f Kleisli[A, B]) Kleisli[GA, GB] {
return either.TraverseRecordG[GA, GB, K, error, A, B](f)
}
// TraverseRecord transforms a map by applying a function that returns an Either to each value.
// If any value produces a Left, the entire result is that Left (short-circuits).
// Otherwise, returns Right containing the map of all Right values.
//
// Example:
//
// parse := func(s string) either.Result[int] {
// v, err := strconv.Atoi(s)
// return either.FromError(v, err)
// }
// result := either.TraverseRecord[string](parse)(map[string]string{"a": "1", "b": "2"})
// // result is Right(map[string]int{"a": 1, "b": 2})
//
//go:inline
func TraverseRecord[K comparable, A, B any](f Kleisli[A, B]) Kleisli[map[K]A, map[K]B] {
return either.TraverseRecord[K, error, A, B](f)
}
// TraverseRecordWithIndexG transforms a map by applying an indexed function that returns an Either.
// The function receives both the key and the value.
// If any value produces a Left, the entire result is that Left (short-circuits).
// The G suffix indicates support for generic map types.
//
// Example:
//
// validate := func(k string, v string) either.Result[string] {
// if len(v) > 0 {
// return either.Right[error](k + ":" + v)
// }
// return either.Left[string](fmt.Errorf("empty value for key %s", k))
// }
// result := either.TraverseRecordWithIndexG[map[string]string, map[string]string](validate)(map[string]string{"a": "1"})
// // result is Right(map[string]string{"a": "a:1"})
//
//go:inline
func TraverseRecordWithIndexG[GA ~map[K]A, GB ~map[K]B, K comparable, A, B any](f func(K, A) Result[B]) Kleisli[GA, GB] {
return either.TraverseRecordWithIndexG[GA, GB, K, error, A, B](f)
}
// TraverseRecordWithIndex transforms a map by applying an indexed function that returns an Either.
// The function receives both the key and the value.
// If any value produces a Left, the entire result is that Left (short-circuits).
//
// Example:
//
// validate := func(k string, v string) either.Result[string] {
// if len(v) > 0 {
// return either.Right[error](k + ":" + v)
// }
// return either.Left[string](fmt.Errorf("empty value for key %s", k))
// }
// result := either.TraverseRecordWithIndex[string](validate)(map[string]string{"a": "1"})
// // result is Right(map[string]string{"a": "a:1"})
//
//go:inline
func TraverseRecordWithIndex[K comparable, A, B any](f func(K, A) Result[B]) Kleisli[map[K]A, map[K]B] {
return either.TraverseRecordWithIndex[K, error, A, B](f)
}
//go:inline
func SequenceRecordG[GA ~map[K]A, GOA ~map[K]Result[A], K comparable, A any](ma GOA) Result[GA] {
return either.SequenceRecordG[GA, GOA, K, error, A](ma)
}
// SequenceRecord converts a map of Either values into an Either of a map.
// If any value is Left, returns that Left (short-circuits).
// Otherwise, returns Right containing a map of all the Right values.
//
// Example:
//
// eithers := map[string]either.Result[int]{
// "a": either.Right[error](1),
// "b": either.Right[error](2),
// }
// result := either.SequenceRecord(eithers)
// // result is Right(map[string]int{"a": 1, "b": 2})
//
//go:inline
func SequenceRecord[K comparable, A any](ma map[K]Result[A]) Result[map[K]A] {
return either.SequenceRecord[K, error, A](ma)
}
// CompactRecordG discards all Left values and keeps only the Right values.
// The G suffix indicates support for generic map types.
//
// Example:
//
// eithers := map[string]either.Result[int]{
// "a": either.Right[error](1),
// "b": either.Left[int](errors.New("error")),
// "c": either.Right[error](3),
// }
// result := either.CompactRecordG[map[string]either.Result[int], map[string]int](eithers)
// // result is map[string]int{"a": 1, "c": 3}
func CompactRecordG[M1 ~map[K]Result[A], M2 ~map[K]A, K comparable, A any](m M1) M2 {
return either.CompactRecordG[M1, M2, K, error, A](m)
}
// CompactRecord discards all Left values and keeps only the Right values.
//
// Example:
//
// eithers := map[string]either.Result[int]{
// "a": either.Right[error](1),
// "b": either.Left[int](errors.New("error")),
// "c": either.Right[error](3),
// }
// result := either.CompactRecord(eithers)
// // result is map[string]int{"a": 1, "c": 3}
//
//go:inline
func CompactRecord[K comparable, A any](m map[K]Result[A]) map[K]A {
return either.CompactRecord[K, error, A](m)
}

41
v2/result/record_test.go Normal file
View File

@@ -0,0 +1,41 @@
// 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 result
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
func TestCompactRecord(t *testing.T) {
e := errors.New("error")
// make the map
m := make(map[string]Result[int])
m["foo"] = Left[int](e)
m["bar"] = Right(1)
// compact it
m1 := CompactRecord(m)
// check expected
exp := map[string]int{
"bar": 1,
}
assert.Equal(t, exp, m1)
}

48
v2/result/resource.go Normal file
View File

@@ -0,0 +1,48 @@
// 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 result
import (
"github.com/IBM/fp-go/v2/either"
)
// WithResource constructs a function that creates a resource, operates on it, and then releases it.
// This ensures proper resource cleanup even if operations fail.
// The resource is released immediately after the operation completes.
//
// Parameters:
// - onCreate: Function to create/acquire the resource
// - onRelease: Function to release/cleanup the resource
//
// Returns a function that takes an operation to perform on the resource.
//
// Example:
//
// withFile := either.WithResource(
// func() either.Result[*os.File] {
// return either.TryCatchError(os.Open("file.txt"))
// },
// func(f *os.File) either.Result[any] {
// return either.TryCatchError(f.Close())
// },
// )
// result := withFile(func(f *os.File) either.Result[string] {
// // Use file here
// return either.Right[error]("data")
// })
func WithResource[R, A any](onCreate func() Result[R], onRelease Kleisli[R, any]) Kleisli[Kleisli[R, A], A] {
return either.WithResource[error, R, A](onCreate, onRelease)
}

View File

@@ -0,0 +1,48 @@
// 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 result
import (
"os"
"testing"
F "github.com/IBM/fp-go/v2/function"
"github.com/stretchr/testify/assert"
)
func TestWithResource(t *testing.T) {
onCreate := func() Result[*os.File] {
return TryCatchError(os.CreateTemp("", "*"))
}
onDelete := F.Flow2(
func(f *os.File) Result[string] {
return TryCatchError(f.Name(), f.Close())
},
Chain(func(name string) Result[any] {
return TryCatchError(any(name), os.Remove(name))
}),
)
onHandler := func(f *os.File) Result[string] {
return Of(f.Name())
}
tempFile := WithResource[*os.File, string](onCreate, onDelete)
resE := tempFile(onHandler)
assert.True(t, IsRight(resE))
}

37
v2/result/semigroup.go Normal file
View File

@@ -0,0 +1,37 @@
// 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 result
import (
"github.com/IBM/fp-go/v2/either"
S "github.com/IBM/fp-go/v2/semigroup"
)
// AltSemigroup creates a semigroup for Either that uses the Alt operation for combining values.
// When combining two Either values, it returns the first Right value, or the second value if the first is Left.
//
// Example:
//
// sg := either.AltSemigroup[error, int]()
// result := sg.Concat(either.Left[int](errors.New("error")), either.Right[error](42))
// // result is Right(42)
// result2 := sg.Concat(either.Right[error](1), either.Right[error](2))
// // result2 is Right(1) - first Right wins
//
//go:inline
func AltSemigroup[A any]() S.Semigroup[Result[A]] {
return either.AltSemigroup[error, A]()
}

63
v2/result/testing/laws.go Normal file
View File

@@ -0,0 +1,63 @@
// 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 testing provides utilities for testing Either monad laws.
// This is useful for verifying that custom Either implementations satisfy the monad laws.
package testing
import (
"testing"
ET "github.com/IBM/fp-go/v2/either/testing"
EQ "github.com/IBM/fp-go/v2/eq"
)
// AssertLaws asserts that the Either monad satisfies the monad laws.
// This includes testing:
// - Identity laws (left and right identity)
// - Associativity law
// - Functor laws
// - Applicative laws
//
// Parameters:
// - t: Testing context
// - eqe, eqa, eqb, eqc: Equality predicates for the types
// - ab: Function from A to B for testing
// - bc: Function from B to C for testing
//
// Returns a function that takes a value of type A and returns true if all laws hold.
//
// Example:
//
// func TestEitherLaws(t *testing.T) {
// eqInt := eq.FromStrictEquals[int]()
// eqString := eq.FromStrictEquals[string]()
// eqError := eq.FromStrictEquals[error]()
//
// ab := func(x int) string { return strconv.Itoa(x) }
// bc := func(s string) bool { return len(s) > 0 }
//
// testing.AssertLaws(t, eqError, eqInt, eqString, eq.FromStrictEquals[bool](), ab, bc)(42)
// }
func AssertLaws[A, B, C any](t *testing.T,
eqa EQ.Eq[A],
eqb EQ.Eq[B],
eqc EQ.Eq[C],
ab func(A) B,
bc func(B) C,
) func(a A) bool {
return ET.AssertLaws(t, EQ.FromStrictEquals[error](), eqa, eqb, eqc, ab, bc)
}

View File

@@ -0,0 +1,47 @@
// 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 testing
import (
"fmt"
"testing"
EQ "github.com/IBM/fp-go/v2/eq"
"github.com/stretchr/testify/assert"
)
func TestMonadLaws(t *testing.T) {
// some comparison
eqa := EQ.FromStrictEquals[bool]()
eqb := EQ.FromStrictEquals[int]()
eqc := EQ.FromStrictEquals[string]()
ab := func(a bool) int {
if a {
return 1
}
return 0
}
bc := func(b int) string {
return fmt.Sprintf("value %d", b)
}
laws := AssertLaws(t, eqa, eqb, eqc, ab, bc)
assert.True(t, laws(true))
assert.True(t, laws(false))
}

View File

@@ -0,0 +1,7 @@
package testing
import "github.com/IBM/fp-go/v2/result"
type (
Result[T any] = result.Result[T]
)

66
v2/result/traverse.go Normal file
View File

@@ -0,0 +1,66 @@
// 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 result
import (
"github.com/IBM/fp-go/v2/either"
)
// Traverse converts an Either of some higher kinded type into the higher kinded type of an Either.
// This is a generic traversal operation that works with any applicative functor.
//
// Parameters:
// - mof: Lifts an Either into the target higher-kinded type
// - mmap: Maps over the target higher-kinded type
//
// Example (conceptual - requires understanding of higher-kinded types):
//
// // Traverse an Result[Option[int]] to Option[Result[int]]
// result := either.Traverse[int, error, int, option.Option[int], option.Option[either.Result[int]]](
// option.Of[either.Result[int]],
// option.Map[int, either.Result[int]],
// )(f)(eitherOfOption)
//
//go:inline
func Traverse[A, B, HKTB, HKTRB any](
mof func(Result[B]) HKTRB,
mmap func(Kleisli[B, B]) func(HKTB) HKTRB,
) func(func(A) HKTB) func(Result[A]) HKTRB {
return either.Traverse[A, error, B](mof, mmap)
}
// Sequence converts an Either of some higher kinded type into the higher kinded type of an Either.
// This is the identity version of Traverse - it doesn't transform the values, just swaps the type constructors.
//
// Parameters:
// - mof: Lifts an Either into the target higher-kinded type
// - mmap: Maps over the target higher-kinded type
//
// Example (conceptual - requires understanding of higher-kinded types):
//
// // Sequence an Result[Option[int]] to Option[Result[int]]
// result := either.Sequence[error, int, option.Option[int], option.Option[either.Result[int]]](
// option.Of[either.Result[int]],
// option.Map[int, either.Result[int]],
// )(eitherOfOption)
//
//go:inline
func Sequence[A, HKTA, HKTRA any](
mof func(Result[A]) HKTRA,
mmap func(Kleisli[A, A]) func(HKTA) HKTRA,
) func(Result[HKTA]) HKTRA {
return either.Sequence[error, A, HKTA, HKTRA](mof, mmap)
}

View File

@@ -0,0 +1,58 @@
// 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 result
import (
"errors"
"testing"
F "github.com/IBM/fp-go/v2/function"
O "github.com/IBM/fp-go/v2/option"
"github.com/stretchr/testify/assert"
)
func TestTraverse(t *testing.T) {
f := func(n int) Option[int] {
if n >= 2 {
return O.Of(n)
}
return O.None[int]()
}
trav := Traverse[int](
O.Of[Result[int]],
O.Map[int, Result[int]],
)(f)
a := errors.New("a")
assert.Equal(t, O.Of(Left[int](a)), F.Pipe1(Left[int](a), trav))
assert.Equal(t, O.None[Result[int]](), F.Pipe1(Right(1), trav))
assert.Equal(t, O.Of(Right(3)), F.Pipe1(Right(3), trav))
}
func TestSequence(t *testing.T) {
seq := Sequence(
O.Of[Result[int]],
O.Map[int, Result[int]],
)
a := errors.New("a")
assert.Equal(t, O.Of(Right(1)), seq(Right(O.Of(1))))
assert.Equal(t, O.Of(Left[int](a)), seq(Left[Option[int]](a)))
assert.Equal(t, O.None[Result[int]](), seq(Right(O.None[int]())))
}

41
v2/result/types.go Normal file
View File

@@ -0,0 +1,41 @@
// Copyright (c) 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 result
import (
"github.com/IBM/fp-go/v2/either"
"github.com/IBM/fp-go/v2/endomorphism"
"github.com/IBM/fp-go/v2/lazy"
"github.com/IBM/fp-go/v2/monoid"
"github.com/IBM/fp-go/v2/optics/lens"
"github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/reader"
)
// Option is a type alias for option.Option, provided for convenience
// when working with Either and Option together.
type (
Option[A any] = option.Option[A]
Lens[S, T any] = lens.Lens[S, T]
Endomorphism[T any] = endomorphism.Endomorphism[T]
Either[E, T any] = either.Either[E, T]
Lazy[T any] = lazy.Lazy[T]
Result[T any] = Either[error, T]
Kleisli[A, B any] = reader.Reader[A, Result[B]]
Operator[A, B any] = Kleisli[Result[A], B]
Monoid[A any] = monoid.Monoid[Result[A]]
)

96
v2/result/variadic.go Normal file
View File

@@ -0,0 +1,96 @@
// 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 result
// Variadic0 converts a function taking a slice and returning (R, error) into a variadic function returning Either.
//
// Example:
//
// sum := func(nums []int) (int, error) {
// total := 0
// for _, n := range nums { total += n }
// return total, nil
// }
// variadicSum := either.Variadic0(sum)
// result := variadicSum(1, 2, 3) // Right(6)
func Variadic0[V, R any](f func([]V) (R, error)) func(...V) Result[R] {
return func(v ...V) Result[R] {
return TryCatchError(f(v))
}
}
// Variadic1 converts a function with 1 fixed parameter and a slice into a variadic function returning Either.
func Variadic1[T1, V, R any](f func(T1, []V) (R, error)) func(T1, ...V) Result[R] {
return func(t1 T1, v ...V) Result[R] {
return TryCatchError(f(t1, v))
}
}
// Variadic2 converts a function with 2 fixed parameters and a slice into a variadic function returning Either.
func Variadic2[T1, T2, V, R any](f func(T1, T2, []V) (R, error)) func(T1, T2, ...V) Result[R] {
return func(t1 T1, t2 T2, v ...V) Result[R] {
return TryCatchError(f(t1, t2, v))
}
}
// Variadic3 converts a function with 3 fixed parameters and a slice into a variadic function returning Either.
func Variadic3[T1, T2, T3, V, R any](f func(T1, T2, T3, []V) (R, error)) func(T1, T2, T3, ...V) Result[R] {
return func(t1 T1, t2 T2, t3 T3, v ...V) Result[R] {
return TryCatchError(f(t1, t2, t3, v))
}
}
// Variadic4 converts a function with 4 fixed parameters and a slice into a variadic function returning Either.
func Variadic4[T1, T2, T3, T4, V, R any](f func(T1, T2, T3, T4, []V) (R, error)) func(T1, T2, T3, T4, ...V) Result[R] {
return func(t1 T1, t2 T2, t3 T3, t4 T4, v ...V) Result[R] {
return TryCatchError(f(t1, t2, t3, t4, v))
}
}
// Unvariadic0 converts a variadic function returning (R, error) into a function taking a slice and returning Either.
func Unvariadic0[V, R any](f func(...V) (R, error)) func([]V) Result[R] {
return func(v []V) Result[R] {
return TryCatchError(f(v...))
}
}
// Unvariadic1 converts a variadic function with 1 fixed parameter into a function taking a slice and returning Either.
func Unvariadic1[T1, V, R any](f func(T1, ...V) (R, error)) func(T1, []V) Result[R] {
return func(t1 T1, v []V) Result[R] {
return TryCatchError(f(t1, v...))
}
}
// Unvariadic2 converts a variadic function with 2 fixed parameters into a function taking a slice and returning Either.
func Unvariadic2[T1, T2, V, R any](f func(T1, T2, ...V) (R, error)) func(T1, T2, []V) Result[R] {
return func(t1 T1, t2 T2, v []V) Result[R] {
return TryCatchError(f(t1, t2, v...))
}
}
// Unvariadic3 converts a variadic function with 3 fixed parameters into a function taking a slice and returning Either.
func Unvariadic3[T1, T2, T3, V, R any](f func(T1, T2, T3, ...V) (R, error)) func(T1, T2, T3, []V) Result[R] {
return func(t1 T1, t2 T2, t3 T3, v []V) Result[R] {
return TryCatchError(f(t1, t2, t3, v...))
}
}
// Unvariadic4 converts a variadic function with 4 fixed parameters into a function taking a slice and returning Either.
func Unvariadic4[T1, T2, T3, T4, V, R any](f func(T1, T2, T3, T4, ...V) (R, error)) func(T1, T2, T3, T4, []V) Result[R] {
return func(t1 T1, t2 T2, t3 T3, t4 T4, v []V) Result[R] {
return TryCatchError(f(t1, t2, t3, t4, v...))
}
}