mirror of
https://github.com/IBM/fp-go.git
synced 2025-12-07 23:03:15 +02:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d92df83ad | ||
|
|
0c742b81e6 |
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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
154
v2/either/core_any.go
Normal file
@@ -0,0 +1,154 @@
|
||||
// Copyright (c) 2023 - 2025 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//go:build either_any
|
||||
|
||||
package either
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type (
|
||||
either struct {
|
||||
value any
|
||||
isRight bool
|
||||
}
|
||||
|
||||
// Either defines a data structure that logically holds either an E or an A. The flag discriminates the cases
|
||||
Either[E, A any] either
|
||||
)
|
||||
|
||||
// String prints some debug info for the object
|
||||
//
|
||||
//go:noinline
|
||||
func eitherString(s *either) string {
|
||||
if s.isRight {
|
||||
return fmt.Sprintf("Right[%T](%v)", s.value, s.value)
|
||||
}
|
||||
return fmt.Sprintf("Left[%T](%v)", s.value, s.value)
|
||||
}
|
||||
|
||||
// Format prints some debug info for the object
|
||||
//
|
||||
//go:noinline
|
||||
func eitherFormat(e *either, f fmt.State, c rune) {
|
||||
switch c {
|
||||
case 's':
|
||||
fmt.Fprint(f, eitherString(e))
|
||||
default:
|
||||
fmt.Fprint(f, eitherString(e))
|
||||
}
|
||||
}
|
||||
|
||||
// String prints some debug info for the object
|
||||
func (s Either[E, A]) String() string {
|
||||
return eitherString((*either)(&s))
|
||||
}
|
||||
|
||||
// Format prints some debug info for the object
|
||||
func (s Either[E, A]) Format(f fmt.State, c rune) {
|
||||
eitherFormat((*either)(&s), f, c)
|
||||
}
|
||||
|
||||
// IsLeft tests if the Either is a Left value.
|
||||
// Rather use [Fold] or [MonadFold] if you need to access the values.
|
||||
// Inverse is [IsRight].
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// either.IsLeft(either.Left[int](errors.New("err"))) // true
|
||||
// either.IsLeft(either.Right[error](42)) // false
|
||||
//
|
||||
//go:inline
|
||||
func IsLeft[E, A any](val Either[E, A]) bool {
|
||||
return !val.isRight
|
||||
}
|
||||
|
||||
// IsRight tests if the Either is a Right value.
|
||||
// Rather use [Fold] or [MonadFold] if you need to access the values.
|
||||
// Inverse is [IsLeft].
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// either.IsRight(either.Right[error](42)) // true
|
||||
// either.IsRight(either.Left[int](errors.New("err"))) // false
|
||||
//
|
||||
//go:inline
|
||||
func IsRight[E, A any](val Either[E, A]) bool {
|
||||
return val.isRight
|
||||
}
|
||||
|
||||
// Left creates a new Either representing a Left (error/failure) value.
|
||||
// By convention, Left represents the error case.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// result := either.Left[int](errors.New("something went wrong"))
|
||||
//
|
||||
//go:inline
|
||||
func Left[A, E any](value E) Either[E, A] {
|
||||
return Either[E, A]{value, false}
|
||||
}
|
||||
|
||||
// Right creates a new Either representing a Right (success) value.
|
||||
// By convention, Right represents the success case.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// result := either.Right[error](42)
|
||||
//
|
||||
//go:inline
|
||||
func Right[E, A any](value A) Either[E, A] {
|
||||
return Either[E, A]{value, true}
|
||||
}
|
||||
|
||||
// MonadFold extracts the value from an Either by providing handlers for both cases.
|
||||
// This is the fundamental pattern matching operation for Either.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// result := either.MonadFold(
|
||||
// either.Right[error](42),
|
||||
// func(err error) string { return "Error: " + err.Error() },
|
||||
// func(n int) string { return fmt.Sprintf("Value: %d", n) },
|
||||
// ) // "Value: 42"
|
||||
//
|
||||
//go:inline
|
||||
func MonadFold[E, A, B any](ma Either[E, A], onLeft func(e E) B, onRight func(a A) B) B {
|
||||
if ma.isRight {
|
||||
return onRight(ma.value.(A))
|
||||
}
|
||||
return onLeft(ma.value.(E))
|
||||
}
|
||||
|
||||
// Unwrap converts an Either into the idiomatic Go tuple (value, error).
|
||||
// For Right values, returns (value, zero-error).
|
||||
// For Left values, returns (zero-value, error).
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// val, err := either.Unwrap(either.Right[error](42)) // 42, nil
|
||||
// val, err := either.Unwrap(either.Left[int](errors.New("fail"))) // 0, error
|
||||
//
|
||||
//go:inline
|
||||
func Unwrap[E, A any](ma Either[E, A]) (A, E) {
|
||||
if ma.isRight {
|
||||
var e E
|
||||
return ma.value.(A), e
|
||||
} else {
|
||||
var a A
|
||||
return a, ma.value.(E)
|
||||
}
|
||||
}
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
|
||||
@@ -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]))
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package lens
|
||||
|
||||
import (
|
||||
EQ "github.com/IBM/fp-go/v2/eq"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
)
|
||||
|
||||
@@ -29,6 +30,17 @@ func setCopy[SET ~func(*S, A) *S, S, A any](setter SET) func(s *S, a A) *S {
|
||||
}
|
||||
}
|
||||
|
||||
func setCopyWithEq[GET ~func(*S) A, SET ~func(*S, A) *S, S, A any](pred EQ.Eq[A], getter GET, setter SET) func(s *S, a A) *S {
|
||||
return func(s *S, a A) *S {
|
||||
if pred.Equals(getter(s), a) {
|
||||
return s
|
||||
}
|
||||
// we need to make a copy
|
||||
cpy := *s
|
||||
return setter(&cpy, a)
|
||||
}
|
||||
}
|
||||
|
||||
// setCopyCurried wraps a setter for a pointer into a setter that first creates a copy before
|
||||
// modifying that copy
|
||||
func setCopyCurried[SET ~func(A) Endomorphism[*S], S, A any](setter SET) func(a A) Endomorphism[*S] {
|
||||
@@ -161,6 +173,115 @@ func MakeLensRef[GET ~func(*S) A, SET func(*S, A) *S, S, A any](get GET, set SET
|
||||
return MakeLens(get, setCopy(set))
|
||||
}
|
||||
|
||||
// MakeLensWithEq creates a [Lens] for pointer-based structures with equality optimization.
|
||||
//
|
||||
// This is similar to [MakeLensRef] but includes an optimization: if the new value equals
|
||||
// the current value (according to the provided Eq predicate), the original pointer is returned
|
||||
// unchanged instead of creating a copy. This can improve performance and reduce allocations
|
||||
// when setting values that don't actually change the structure.
|
||||
//
|
||||
// The setter does not need to create a copy manually; this function automatically wraps it
|
||||
// to ensure immutability when changes are made.
|
||||
//
|
||||
// This lens assumes that property A always exists in structure S (i.e., it's not optional).
|
||||
//
|
||||
// Type Parameters:
|
||||
// - GET: Getter function type (*S → A)
|
||||
// - SET: Setter function type (*S, A → *S)
|
||||
// - S: Source structure type (will be used as *S)
|
||||
// - A: Focus/field type
|
||||
//
|
||||
// Parameters:
|
||||
// - pred: Equality predicate to compare values of type A
|
||||
// - get: Function to extract value A from pointer *S
|
||||
// - set: Function to update value A in pointer *S (copying handled automatically)
|
||||
//
|
||||
// Returns:
|
||||
// - A Lens[*S, A] that can get and set values immutably on pointers with equality optimization
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type Person struct {
|
||||
// Name string
|
||||
// Age int
|
||||
// }
|
||||
//
|
||||
// nameLens := lens.MakeLensWithEq(
|
||||
// eq.FromStrictEquals[string](),
|
||||
// func(p *Person) string { return p.Name },
|
||||
// func(p *Person, name string) *Person {
|
||||
// p.Name = name // No manual copy needed
|
||||
// return p
|
||||
// },
|
||||
// )
|
||||
//
|
||||
// person := &Person{Name: "Alice", Age: 30}
|
||||
//
|
||||
// // Setting the same value returns the original pointer (no copy)
|
||||
// same := nameLens.Set("Alice")(person)
|
||||
// // same == person (same pointer)
|
||||
//
|
||||
// // Setting a different value creates a new copy
|
||||
// updated := nameLens.Set("Bob")(person)
|
||||
// // person.Name is still "Alice", updated is a new pointer with Name "Bob"
|
||||
func MakeLensWithEq[GET ~func(*S) A, SET func(*S, A) *S, S, A any](pred EQ.Eq[A], get GET, set SET) Lens[*S, A] {
|
||||
return MakeLens(get, setCopyWithEq(pred, get, set))
|
||||
}
|
||||
|
||||
// MakeLensStrict creates a [Lens] for pointer-based structures with strict equality optimization.
|
||||
//
|
||||
// This is a convenience function that combines [MakeLensWithEq] with strict equality comparison (==).
|
||||
// It's suitable for comparable types (primitives, strings, pointers, etc.) and provides the same
|
||||
// optimization as MakeLensWithEq: if the new value equals the current value, the original pointer
|
||||
// is returned unchanged instead of creating a copy.
|
||||
//
|
||||
// The setter does not need to create a copy manually; this function automatically wraps it
|
||||
// to ensure immutability when changes are made.
|
||||
//
|
||||
// This lens assumes that property A always exists in structure S (i.e., it's not optional).
|
||||
//
|
||||
// Type Parameters:
|
||||
// - GET: Getter function type (*S → A)
|
||||
// - SET: Setter function type (*S, A → *S)
|
||||
// - S: Source structure type (will be used as *S)
|
||||
// - A: Focus/field type (must be comparable)
|
||||
//
|
||||
// Parameters:
|
||||
// - get: Function to extract value A from pointer *S
|
||||
// - set: Function to update value A in pointer *S (copying handled automatically)
|
||||
//
|
||||
// Returns:
|
||||
// - A Lens[*S, A] that can get and set values immutably on pointers with strict equality optimization
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type Person struct {
|
||||
// Name string
|
||||
// Age int
|
||||
// }
|
||||
//
|
||||
// // Using MakeLensStrict for a string field (comparable type)
|
||||
// nameLens := lens.MakeLensStrict(
|
||||
// func(p *Person) string { return p.Name },
|
||||
// func(p *Person, name string) *Person {
|
||||
// p.Name = name // No manual copy needed
|
||||
// return p
|
||||
// },
|
||||
// )
|
||||
//
|
||||
// person := &Person{Name: "Alice", Age: 30}
|
||||
//
|
||||
// // Setting the same value returns the original pointer (no copy)
|
||||
// same := nameLens.Set("Alice")(person)
|
||||
// // same == person (same pointer)
|
||||
//
|
||||
// // Setting a different value creates a new copy
|
||||
// updated := nameLens.Set("Bob")(person)
|
||||
// // person.Name is still "Alice", updated is a new pointer with Name "Bob"
|
||||
func MakeLensStrict[GET ~func(*S) A, SET func(*S, A) *S, S any, A comparable](get GET, set SET) Lens[*S, A] {
|
||||
return MakeLensWithEq(EQ.FromStrictEquals[A](), get, set)
|
||||
}
|
||||
|
||||
// MakeLensRefCurried creates a [Lens] for pointer-based structures with a curried setter.
|
||||
//
|
||||
// This combines the benefits of [MakeLensRef] (automatic copying) with [MakeLensCurried]
|
||||
|
||||
@@ -18,6 +18,7 @@ package lens
|
||||
import (
|
||||
"testing"
|
||||
|
||||
EQ "github.com/IBM/fp-go/v2/eq"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -231,3 +232,315 @@ func TestMakeLensRefCurried(t *testing.T) {
|
||||
assert.Equal(t, "Oak", updated.name)
|
||||
assert.Equal(t, "Main", street.name)
|
||||
}
|
||||
|
||||
func TestMakeLensWithEq(t *testing.T) {
|
||||
// Create a lens with equality check for string
|
||||
nameLens := MakeLensWithEq(
|
||||
EQ.FromStrictEquals[string](),
|
||||
func(s *Street) string { return s.name },
|
||||
func(s *Street, name string) *Street {
|
||||
s.name = name
|
||||
return s
|
||||
},
|
||||
)
|
||||
|
||||
street := &Street{num: 1, name: "Main"}
|
||||
|
||||
// Test getting value
|
||||
assert.Equal(t, "Main", nameLens.Get(street))
|
||||
|
||||
// Test setting a different value - should create a new copy
|
||||
updated := nameLens.Set("Oak")(street)
|
||||
assert.Equal(t, "Oak", updated.name)
|
||||
assert.Equal(t, "Main", street.name) // Original unchanged
|
||||
assert.NotSame(t, street, updated) // Different pointers
|
||||
|
||||
// Test setting the same value - should return original pointer (optimization)
|
||||
same := nameLens.Set("Main")(street)
|
||||
assert.Equal(t, "Main", same.name)
|
||||
assert.Same(t, street, same) // Same pointer (no copy made)
|
||||
}
|
||||
|
||||
func TestMakeLensWithEq_IntField(t *testing.T) {
|
||||
// Create a lens with equality check for int
|
||||
numLens := MakeLensWithEq(
|
||||
EQ.FromStrictEquals[int](),
|
||||
func(s *Street) int { return s.num },
|
||||
func(s *Street, num int) *Street {
|
||||
s.num = num
|
||||
return s
|
||||
},
|
||||
)
|
||||
|
||||
street := &Street{num: 42, name: "Main"}
|
||||
|
||||
// Test getting value
|
||||
assert.Equal(t, 42, numLens.Get(street))
|
||||
|
||||
// Test setting a different value
|
||||
updated := numLens.Set(100)(street)
|
||||
assert.Equal(t, 100, updated.num)
|
||||
assert.Equal(t, 42, street.num)
|
||||
assert.NotSame(t, street, updated)
|
||||
|
||||
// Test setting the same value - should return original pointer
|
||||
same := numLens.Set(42)(street)
|
||||
assert.Equal(t, 42, same.num)
|
||||
assert.Same(t, street, same)
|
||||
}
|
||||
|
||||
func TestMakeLensWithEq_CustomEq(t *testing.T) {
|
||||
// Create a custom equality that ignores case
|
||||
caseInsensitiveEq := EQ.FromEquals(func(a, b string) bool {
|
||||
return len(a) == len(b) && a == b // Simple equality for this test
|
||||
})
|
||||
|
||||
nameLens := MakeLensWithEq(
|
||||
caseInsensitiveEq,
|
||||
func(s *Street) string { return s.name },
|
||||
func(s *Street, name string) *Street {
|
||||
s.name = name
|
||||
return s
|
||||
},
|
||||
)
|
||||
|
||||
street := &Street{num: 1, name: "Main"}
|
||||
|
||||
// Setting the exact same value should return original pointer
|
||||
same := nameLens.Set("Main")(street)
|
||||
assert.Same(t, street, same)
|
||||
|
||||
// Setting a different value should create a copy
|
||||
updated := nameLens.Set("Oak")(street)
|
||||
assert.NotSame(t, street, updated)
|
||||
assert.Equal(t, "Main", street.name)
|
||||
assert.Equal(t, "Oak", updated.name)
|
||||
}
|
||||
|
||||
func TestMakeLensWithEq_ComposedLens(t *testing.T) {
|
||||
// Create lenses with equality optimization
|
||||
streetLensEq := MakeLensWithEq(
|
||||
EQ.FromStrictEquals[string](),
|
||||
(*Street).GetName,
|
||||
(*Street).SetName,
|
||||
)
|
||||
|
||||
addrLensEq := MakeLensWithEq(
|
||||
EQ.FromStrictEquals[*Street](),
|
||||
(*Address).GetStreet,
|
||||
(*Address).SetStreet,
|
||||
)
|
||||
|
||||
// Compose the lenses
|
||||
streetName := Compose[*Address](streetLensEq)(addrLensEq)
|
||||
|
||||
sampleStreet := Street{num: 220, name: "Schönaicherstr"}
|
||||
sampleAddress := Address{city: "Böblingen", street: &sampleStreet}
|
||||
|
||||
// Test getting value
|
||||
assert.Equal(t, sampleStreet.name, streetName.Get(&sampleAddress))
|
||||
|
||||
// Test setting a different value
|
||||
newName := "Böblingerstr"
|
||||
updated := streetName.Set(newName)(&sampleAddress)
|
||||
assert.Equal(t, newName, streetName.Get(updated))
|
||||
assert.Equal(t, sampleStreet.name, sampleAddress.street.name) // Original unchanged
|
||||
}
|
||||
|
||||
func TestMakeLensWithEq_MultipleUpdates(t *testing.T) {
|
||||
nameLens := MakeLensWithEq(
|
||||
EQ.FromStrictEquals[string](),
|
||||
func(s *Street) string { return s.name },
|
||||
func(s *Street, name string) *Street {
|
||||
s.name = name
|
||||
return s
|
||||
},
|
||||
)
|
||||
|
||||
street := &Street{num: 1, name: "Main"}
|
||||
|
||||
// First update - creates a copy
|
||||
updated1 := nameLens.Set("Oak")(street)
|
||||
assert.NotSame(t, street, updated1)
|
||||
assert.Equal(t, "Oak", updated1.name)
|
||||
|
||||
// Second update with same value - returns same pointer
|
||||
updated2 := nameLens.Set("Oak")(updated1)
|
||||
assert.Same(t, updated1, updated2)
|
||||
|
||||
// Third update with different value - creates new copy
|
||||
updated3 := nameLens.Set("Elm")(updated2)
|
||||
assert.NotSame(t, updated2, updated3)
|
||||
assert.Equal(t, "Elm", updated3.name)
|
||||
assert.Equal(t, "Oak", updated2.name)
|
||||
}
|
||||
|
||||
func TestMakeLensStrict(t *testing.T) {
|
||||
// Create a lens with strict equality for string
|
||||
nameLens := MakeLensStrict(
|
||||
func(s *Street) string { return s.name },
|
||||
func(s *Street, name string) *Street {
|
||||
s.name = name
|
||||
return s
|
||||
},
|
||||
)
|
||||
|
||||
street := &Street{num: 1, name: "Main"}
|
||||
|
||||
// Test getting value
|
||||
assert.Equal(t, "Main", nameLens.Get(street))
|
||||
|
||||
// Test setting a different value - should create a new copy
|
||||
updated := nameLens.Set("Oak")(street)
|
||||
assert.Equal(t, "Oak", updated.name)
|
||||
assert.Equal(t, "Main", street.name) // Original unchanged
|
||||
assert.NotSame(t, street, updated) // Different pointers
|
||||
|
||||
// Test setting the same value - should return original pointer (optimization)
|
||||
same := nameLens.Set("Main")(street)
|
||||
assert.Equal(t, "Main", same.name)
|
||||
assert.Same(t, street, same) // Same pointer (no copy made)
|
||||
}
|
||||
|
||||
func TestMakeLensStrict_IntField(t *testing.T) {
|
||||
// Create a lens with strict equality for int
|
||||
numLens := MakeLensStrict(
|
||||
func(s *Street) int { return s.num },
|
||||
func(s *Street, num int) *Street {
|
||||
s.num = num
|
||||
return s
|
||||
},
|
||||
)
|
||||
|
||||
street := &Street{num: 42, name: "Main"}
|
||||
|
||||
// Test getting value
|
||||
assert.Equal(t, 42, numLens.Get(street))
|
||||
|
||||
// Test setting a different value
|
||||
updated := numLens.Set(100)(street)
|
||||
assert.Equal(t, 100, updated.num)
|
||||
assert.Equal(t, 42, street.num)
|
||||
assert.NotSame(t, street, updated)
|
||||
|
||||
// Test setting the same value - should return original pointer
|
||||
same := numLens.Set(42)(street)
|
||||
assert.Equal(t, 42, same.num)
|
||||
assert.Same(t, street, same)
|
||||
}
|
||||
|
||||
func TestMakeLensStrict_PointerField(t *testing.T) {
|
||||
// Test with pointer field (comparable type)
|
||||
type Container struct {
|
||||
ptr *int
|
||||
}
|
||||
|
||||
ptrLens := MakeLensStrict(
|
||||
func(c *Container) *int { return c.ptr },
|
||||
func(c *Container, ptr *int) *Container {
|
||||
c.ptr = ptr
|
||||
return c
|
||||
},
|
||||
)
|
||||
|
||||
value1 := 42
|
||||
value2 := 100
|
||||
container := &Container{ptr: &value1}
|
||||
|
||||
// Test getting value
|
||||
assert.Equal(t, &value1, ptrLens.Get(container))
|
||||
|
||||
// Test setting a different pointer
|
||||
updated := ptrLens.Set(&value2)(container)
|
||||
assert.Equal(t, &value2, updated.ptr)
|
||||
assert.Equal(t, &value1, container.ptr)
|
||||
assert.NotSame(t, container, updated)
|
||||
|
||||
// Test setting the same pointer - should return original
|
||||
same := ptrLens.Set(&value1)(container)
|
||||
assert.Same(t, container, same)
|
||||
}
|
||||
|
||||
func TestMakeLensStrict_ComposedLens(t *testing.T) {
|
||||
// Create lenses with strict equality optimization
|
||||
streetLensStrict := MakeLensStrict(
|
||||
(*Street).GetName,
|
||||
(*Street).SetName,
|
||||
)
|
||||
|
||||
addrLensStrict := MakeLensStrict(
|
||||
(*Address).GetStreet,
|
||||
(*Address).SetStreet,
|
||||
)
|
||||
|
||||
// Compose the lenses
|
||||
streetName := Compose[*Address](streetLensStrict)(addrLensStrict)
|
||||
|
||||
sampleStreet := Street{num: 220, name: "Schönaicherstr"}
|
||||
sampleAddress := Address{city: "Böblingen", street: &sampleStreet}
|
||||
|
||||
// Test getting value
|
||||
assert.Equal(t, sampleStreet.name, streetName.Get(&sampleAddress))
|
||||
|
||||
// Test setting a different value
|
||||
newName := "Böblingerstr"
|
||||
updated := streetName.Set(newName)(&sampleAddress)
|
||||
assert.Equal(t, newName, streetName.Get(updated))
|
||||
assert.Equal(t, sampleStreet.name, sampleAddress.street.name) // Original unchanged
|
||||
}
|
||||
|
||||
func TestMakeLensStrict_MultipleUpdates(t *testing.T) {
|
||||
nameLens := MakeLensStrict(
|
||||
func(s *Street) string { return s.name },
|
||||
func(s *Street, name string) *Street {
|
||||
s.name = name
|
||||
return s
|
||||
},
|
||||
)
|
||||
|
||||
street := &Street{num: 1, name: "Main"}
|
||||
|
||||
// First update - creates a copy
|
||||
updated1 := nameLens.Set("Oak")(street)
|
||||
assert.NotSame(t, street, updated1)
|
||||
assert.Equal(t, "Oak", updated1.name)
|
||||
|
||||
// Second update with same value - returns same pointer
|
||||
updated2 := nameLens.Set("Oak")(updated1)
|
||||
assert.Same(t, updated1, updated2)
|
||||
|
||||
// Third update with different value - creates new copy
|
||||
updated3 := nameLens.Set("Elm")(updated2)
|
||||
assert.NotSame(t, updated2, updated3)
|
||||
assert.Equal(t, "Elm", updated3.name)
|
||||
assert.Equal(t, "Oak", updated2.name)
|
||||
}
|
||||
|
||||
func TestMakeLensStrict_BoolField(t *testing.T) {
|
||||
type Config struct {
|
||||
enabled bool
|
||||
}
|
||||
|
||||
enabledLens := MakeLensStrict(
|
||||
func(c *Config) bool { return c.enabled },
|
||||
func(c *Config, enabled bool) *Config {
|
||||
c.enabled = enabled
|
||||
return c
|
||||
},
|
||||
)
|
||||
|
||||
config := &Config{enabled: true}
|
||||
|
||||
// Test getting value
|
||||
assert.True(t, enabledLens.Get(config))
|
||||
|
||||
// Test setting a different value
|
||||
updated := enabledLens.Set(false)(config)
|
||||
assert.False(t, updated.enabled)
|
||||
assert.True(t, config.enabled)
|
||||
assert.NotSame(t, config, updated)
|
||||
|
||||
// Test setting the same value - should return original pointer
|
||||
same := enabledLens.Set(true)(config)
|
||||
assert.Same(t, config, same)
|
||||
}
|
||||
|
||||
51
v2/result/apply.go
Normal file
51
v2/result/apply.go
Normal 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
64
v2/result/apply_test.go
Normal 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
157
v2/result/array.go
Normal 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
51
v2/result/array_test.go
Normal 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
351
v2/result/bind.go
Normal 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
361
v2/result/bind_test.go
Normal 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
101
v2/result/core.go
Normal 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
103
v2/result/curry.go
Normal 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
65
v2/result/doc.go
Normal 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
556
v2/result/either.go
Normal 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
142
v2/result/either_test.go
Normal 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
50
v2/result/eq.go
Normal 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
49
v2/result/eq_test.go
Normal 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))
|
||||
}
|
||||
58
v2/result/examples_create_test.go
Normal file
58
v2/result/examples_create_test.go
Normal 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)
|
||||
|
||||
}
|
||||
64
v2/result/examples_extract_test.go
Normal file
64
v2/result/examples_extract_test.go
Normal 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
41
v2/result/exec/exec.go
Normal 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
7
v2/result/exec/types.go
Normal 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
39
v2/result/functor.go
Normal 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
589
v2/result/gen.go
Normal 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)
|
||||
}
|
||||
558
v2/result/gen_coverage_test.go
Normal file
558
v2/result/gen_coverage_test.go
Normal 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
55
v2/result/http/request.go
Normal 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
7
v2/result/http/types.go
Normal 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
42
v2/result/logger.go
Normal 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
37
v2/result/logger_test.go
Normal 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
56
v2/result/monad.go
Normal 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
54
v2/result/monoid.go
Normal 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
37
v2/result/pointed.go
Normal 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
154
v2/result/record.go
Normal file
@@ -0,0 +1,154 @@
|
||||
// Copyright (c) 2023 - 2025 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
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
41
v2/result/record_test.go
Normal 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
48
v2/result/resource.go
Normal 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)
|
||||
}
|
||||
48
v2/result/resource_test.go
Normal file
48
v2/result/resource_test.go
Normal 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
37
v2/result/semigroup.go
Normal 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
63
v2/result/testing/laws.go
Normal 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)
|
||||
}
|
||||
47
v2/result/testing/laws_test.go
Normal file
47
v2/result/testing/laws_test.go
Normal 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))
|
||||
}
|
||||
7
v2/result/testing/types.go
Normal file
7
v2/result/testing/types.go
Normal 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
66
v2/result/traverse.go
Normal 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)
|
||||
}
|
||||
58
v2/result/traverse_test.go
Normal file
58
v2/result/traverse_test.go
Normal 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
41
v2/result/types.go
Normal 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
96
v2/result/variadic.go
Normal 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...))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user