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

Compare commits

...

26 Commits

Author SHA1 Message Date
Dr. Carsten Leue
d2dbce6e8b fix: improve lens handling
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-12 18:23:57 +01:00
Dr. Carsten Leue
6f7ec0768d fix: improve lens generation
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-12 17:28:20 +01:00
Dr. Carsten Leue
ca813b673c fix: better tests and doc
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-12 16:24:12 +01:00
Dr. Carsten Leue
af271e7d10 fix: better endo and lens
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-12 15:03:55 +01:00
Dr. Carsten Leue
567315a31c fix: make a distinction between Chain and Compose for endomorphism
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-12 13:51:00 +01:00
Dr. Carsten Leue
311ed55f06 fix: add Read method to Readers
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-12 11:59:20 +01:00
Dr. Carsten Leue
23333ce52c doc: improve doc
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-12 11:08:18 +01:00
Dr. Carsten Leue
eb7fc9f77b fix: better tests for Lazy
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-12 10:46:07 +01:00
Dr. Carsten Leue
fd0550e71b fix: better test coverage
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-12 10:35:53 +01:00
Dr. Carsten Leue
13063bbd88 fix: doc and tests
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-11 16:36:12 +01:00
Dr. Carsten Leue
4f8a557072 fix: simplify type hints
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-11 15:24:45 +01:00
Dr. Carsten Leue
a4e790ac3d fix: improve bind
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-11 13:05:55 +01:00
Dr. Carsten Leue
1af6501cd8 fix: add bind variations
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-11 12:42:14 +01:00
Dr. Carsten Leue
600521b220 fix: refactor
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-11 11:01:49 +01:00
Dr. Carsten Leue
62fcd186a3 fix: introcuce readeioresult
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-10 18:44:14 +01:00
Dr. Carsten Leue
2db7e83651 fix: introduce IOResult
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-10 16:26:52 +01:00
Dr. Carsten Leue
8d92df83ad fix: introduce Result type for convenience
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-10 12:08:50 +01:00
Dr. Carsten Leue
0c742b81e6 fix: better performance for either
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-08 10:01:14 +01:00
Dr. Carsten Leue
a1d6c94b15 fix: run benchmarks
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-08 09:31:48 +01:00
Dr. Carsten Leue
e4e28a6556 fix: more Kleisli simplified types
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-07 17:53:47 +01:00
Dr. Carsten Leue
7e7cc06f11 fix: add more Kleisli definitions
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-07 17:42:54 +01:00
Dr. Carsten Leue
54d5dbd04a fix: more tests for iso and prism
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-07 17:31:27 +01:00
Dr. Carsten Leue
51adce0c95 fix: better package import
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-07 16:15:16 +01:00
Dr. Carsten Leue
aa5e908810 fix: introduce Kleisli type
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-07 14:35:46 +01:00
Dr. Carsten Leue
b3bd5e9ad3 fix: bind docs
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-06 16:18:15 +01:00
Dr. Carsten Leue
178df09ff7 fix: document ApS
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-06 16:08:35 +01:00
469 changed files with 55379 additions and 13131 deletions

3
.gitignore vendored
View File

@@ -2,4 +2,5 @@ fp-go.exe
fp-go fp-go
main.exe main.exe
build/ build/
.idea .idea
*.exe

View File

@@ -21,8 +21,8 @@ import (
type ( type (
either struct { either struct {
isLeft bool
value any value any
isLeft bool
} }
// Either defines a data structure that logically holds either an E or an A. The flag discriminates the cases // Either defines a data structure that logically holds either an E or an A. The flag discriminates the cases
@@ -73,12 +73,12 @@ func IsRight[E, A any](val Either[E, A]) bool {
// Left creates a new instance of an [Either] representing the left value. // Left creates a new instance of an [Either] representing the left value.
func Left[A, E any](value E) Either[E, A] { func Left[A, E any](value E) Either[E, A] {
return Either[E, A]{true, value} return Either[E, A]{value, true}
} }
// Right creates a new instance of an [Either] representing the right value. // Right creates a new instance of an [Either] representing the right value.
func Right[E, A any](value A) Either[E, A] { func Right[E, A any](value A) Either[E, A] {
return Either[E, A]{false, value} return Either[E, A]{value, false}
} }
// MonadFold extracts the values from an [Either] by invoking the [onLeft] callback or the [onRight] callback depending on the case // MonadFold extracts the values from an [Either] by invoking the [onLeft] callback or the [onRight] callback depending on the case
@@ -94,8 +94,7 @@ func Unwrap[E, A any](ma Either[E, A]) (A, E) {
if ma.isLeft { if ma.isLeft {
var a A var a A
return a, ma.value.(E) return a, ma.value.(E)
} else {
var e E
return ma.value.(A), e
} }
var e E
return ma.value.(A), e
} }

View File

@@ -2,25 +2,152 @@
[![Go Reference](https://pkg.go.dev/badge/github.com/IBM/fp-go/v2.svg)](https://pkg.go.dev/github.com/IBM/fp-go/v2) [![Go Reference](https://pkg.go.dev/badge/github.com/IBM/fp-go/v2.svg)](https://pkg.go.dev/github.com/IBM/fp-go/v2)
[![Coverage Status](https://coveralls.io/repos/github/IBM/fp-go/badge.svg?branch=main&flag=v2)](https://coveralls.io/github/IBM/fp-go?branch=main) [![Coverage Status](https://coveralls.io/repos/github/IBM/fp-go/badge.svg?branch=main&flag=v2)](https://coveralls.io/github/IBM/fp-go?branch=main)
[![Go Report Card](https://goreportcard.com/badge/github.com/IBM/fp-go/v2)](https://goreportcard.com/report/github.com/IBM/fp-go/v2)
Version 2 of fp-go leverages [generic type aliases](https://github.com/golang/go/issues/46477) introduced in Go 1.24, providing a more ergonomic and streamlined API. **fp-go** is a comprehensive functional programming library for Go, bringing type-safe functional patterns inspired by [fp-ts](https://gcanti.github.io/fp-ts/) to the Go ecosystem. Version 2 leverages [generic type aliases](https://github.com/golang/go/issues/46477) introduced in Go 1.24, providing a more ergonomic and streamlined API.
## 📚 Table of Contents ## 📚 Table of Contents
- [Overview](#-overview)
- [Features](#-features)
- [Requirements](#-requirements) - [Requirements](#-requirements)
- [Breaking Changes](#-breaking-changes) - [Installation](#-installation)
- [Quick Start](#-quick-start)
- [Breaking Changes](#️-breaking-changes)
- [Key Improvements](#-key-improvements) - [Key Improvements](#-key-improvements)
- [Migration Guide](#-migration-guide) - [Migration Guide](#-migration-guide)
- [Installation](#-installation)
- [What's New](#-whats-new) - [What's New](#-whats-new)
- [Documentation](#-documentation)
- [Contributing](#-contributing)
- [License](#-license)
## 🎯 Overview
fp-go brings the power of functional programming to Go with:
- **Type-safe abstractions** - Monads, Functors, Applicatives, and more
- **Composable operations** - Build complex logic from simple, reusable functions
- **Error handling** - Elegant error management with `Either`, `Result`, and `IOEither`
- **Lazy evaluation** - Control when and how computations execute
- **Optics** - Powerful lens, prism, and traversal operations for immutable data manipulation
## ✨ Features
- 🔒 **Type Safety** - Leverage Go's generics for compile-time guarantees
- 🧩 **Composability** - Chain operations naturally with functional composition
- 📦 **Rich Type System** - `Option`, `Either`, `Result`, `IO`, `Reader`, and more
- 🎯 **Practical** - Designed for real-world Go applications
- 🚀 **Performance** - Zero-cost abstractions where possible
- 📖 **Well-documented** - Comprehensive API documentation and examples
- 🧪 **Battle-tested** - Extensive test coverage
## 🔧 Requirements ## 🔧 Requirements
- **Go 1.24 or later** (for generic type alias support) - **Go 1.24 or later** (for generic type alias support)
## ⚠️ Breaking Changes ## 📦 Installation
### 1. Generic Type Aliases ```bash
go get github.com/IBM/fp-go/v2
```
## 🚀 Quick Start
### Working with Option
```go
package main
import (
"fmt"
"github.com/IBM/fp-go/v2/option"
)
func main() {
// Create an Option
some := option.Some(42)
none := option.None[int]()
// Map over values
doubled := option.Map(func(x int) int { return x * 2 })(some)
fmt.Println(option.GetOrElse(0)(doubled)) // Output: 84
// Chain operations
result := option.Chain(func(x int) option.Option[string] {
if x > 0 {
return option.Some(fmt.Sprintf("Positive: %d", x))
}
return option.None[string]()
})(some)
fmt.Println(option.GetOrElse("No value")(result)) // Output: Positive: 42
}
```
### Error Handling with Result
```go
package main
import (
"errors"
"fmt"
"github.com/IBM/fp-go/v2/result"
)
func divide(a, b int) result.Result[int] {
if b == 0 {
return result.Error[int](errors.New("division by zero"))
}
return result.Ok(a / b)
}
func main() {
res := divide(10, 2)
// Pattern match on the result
result.Fold(
func(err error) { fmt.Println("Error:", err) },
func(val int) { fmt.Println("Result:", val) },
)(res)
// Output: Result: 5
// Or use GetOrElse for a default value
value := result.GetOrElse(0)(divide(10, 0))
fmt.Println("Value:", value) // Output: Value: 0
}
```
### Composing IO Operations
```go
package main
import (
"fmt"
"github.com/IBM/fp-go/v2/io"
)
func main() {
// Define pure IO operations
readInput := io.MakeIO(func() string {
return "Hello, fp-go!"
})
// Transform the result
uppercase := io.Map(func(s string) string {
return fmt.Sprintf(">>> %s <<<", s)
})(readInput)
// Execute the IO operation
result := uppercase()
fmt.Println(result) // Output: >>> Hello, fp-go! <<<
}
```
### From V1 to V2
#### 1. Generic Type Aliases
V2 uses [generic type aliases](https://github.com/golang/go/issues/46477) which require Go 1.24+. This is the most significant change and enables cleaner type definitions. V2 uses [generic type aliases](https://github.com/golang/go/issues/46477) which require Go 1.24+. This is the most significant change and enables cleaner type definitions.
@@ -34,7 +161,7 @@ type ReaderIOEither[R, E, A any] RD.Reader[R, IOE.IOEither[E, A]]
type ReaderIOEither[R, E, A any] = RD.Reader[R, IOE.IOEither[E, A]] type ReaderIOEither[R, E, A any] = RD.Reader[R, IOE.IOEither[E, A]]
``` ```
### 2. Generic Type Parameter Ordering #### 2. Generic Type Parameter Ordering
Type parameters that **cannot** be inferred from function arguments now come first, improving type inference. Type parameters that **cannot** be inferred from function arguments now come first, improving type inference.
@@ -52,7 +179,7 @@ func Ap[B, R, E, A any](fa ReaderIOEither[R, E, A]) func(ReaderIOEither[R, E, fu
This change allows the Go compiler to infer more types automatically, reducing the need for explicit type parameters. This change allows the Go compiler to infer more types automatically, reducing the need for explicit type parameters.
### 3. Pair Monad Semantics #### 3. Pair Monad Semantics
Monadic operations for `Pair` now operate on the **second argument** to align with the [Haskell definition](https://hackage.haskell.org/package/TypeCompose-0.9.14/docs/Data-Pair.html). Monadic operations for `Pair` now operate on the **second argument** to align with the [Haskell definition](https://hackage.haskell.org/package/TypeCompose-0.9.14/docs/Data-Pair.html).
@@ -70,6 +197,36 @@ pair := MakePair(1, "hello")
result := Map(func(s string) string { return s + "!" })(pair) // Pair(1, "hello!") result := Map(func(s string) string { return s + "!" })(pair) // Pair(1, "hello!")
``` ```
#### 4. Endomorphism Compose Semantics
The `Compose` function for endomorphisms now follows **mathematical function composition** (right-to-left execution), aligning with standard functional programming conventions.
**V1:**
```go
// Compose executed left-to-right
double := func(x int) int { return x * 2 }
increment := func(x int) int { return x + 1 }
composed := Compose(double, increment)
result := composed(5) // (5 * 2) + 1 = 11
```
**V2:**
```go
// Compose executes RIGHT-TO-LEFT (mathematical composition)
double := func(x int) int { return x * 2 }
increment := func(x int) int { return x + 1 }
composed := Compose(double, increment)
result := composed(5) // (5 + 1) * 2 = 12
// Use MonadChain for LEFT-TO-RIGHT execution
chained := MonadChain(double, increment)
result2 := chained(5) // (5 * 2) + 1 = 11
```
**Key Difference:**
- `Compose(f, g)` now means `f ∘ g`, which applies `g` first, then `f` (right-to-left)
- `MonadChain(f, g)` applies `f` first, then `g` (left-to-right)
## ✨ Key Improvements ## ✨ Key Improvements
### 1. Simplified Type Declarations ### 1. Simplified Type Declarations
@@ -91,16 +248,16 @@ func processData(input string) ET.Either[error, OPT.Option[int]] {
**V2 Approach:** **V2 Approach:**
```go ```go
import ( import (
"github.com/IBM/fp-go/v2/either" "github.com/IBM/fp-go/v2/result"
"github.com/IBM/fp-go/v2/option" "github.com/IBM/fp-go/v2/option"
) )
// Define type aliases once // Define type aliases once
type Either[A any] = either.Either[error, A] type Result[A any] = result.Result[A]
type Option[A any] = option.Option[A] type Option[A any] = option.Option[A]
// Use them throughout your codebase // Use them throughout your codebase
func processData(input string) Either[Option[int]] { func processData(input string) Result[Option[int]] {
// implementation // implementation
} }
``` ```
@@ -230,20 +387,14 @@ Create project-wide type aliases for common patterns:
package myapp package myapp
import ( import (
"github.com/IBM/fp-go/v2/either" "github.com/IBM/fp-go/v2/result"
"github.com/IBM/fp-go/v2/option" "github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/ioeither" "github.com/IBM/fp-go/v2/ioresult"
) )
type Either[A any] = either.Either[error, A] type Result[A any] = result.Result[A]
type Option[A any] = option.Option[A] type Option[A any] = option.Option[A]
type IOEither[A any] = ioeither.IOEither[error, A] type IOResult[A any] = ioresult.IOResult[A]
```
## 📦 Installation
```bash
go get github.com/IBM/fp-go/v2
``` ```
## 🆕 What's New ## 🆕 What's New
@@ -269,7 +420,7 @@ import (
func process() IOET.IOEither[error, string] { func process() IOET.IOEither[error, string] {
return IOEG.Map[error, int, string]( return IOEG.Map[error, int, string](
func(x int) string { return fmt.Sprintf("%d", x) }, strconv.Itoa,
)(fetchData()) )(fetchData())
} }
``` ```
@@ -277,25 +428,37 @@ func process() IOET.IOEither[error, string] {
**V2 Simplified Example:** **V2 Simplified Example:**
```go ```go
import ( import (
"github.com/IBM/fp-go/v2/either" "strconv"
"github.com/IBM/fp-go/v2/ioeither" "github.com/IBM/fp-go/v2/ioresult"
) )
type IOEither[A any] = ioeither.IOEither[error, A] type IOResult[A any] = ioresult.IOResult[A]
func process() IOEither[string] { func process() IOResult[string] {
return ioeither.Map( return ioresult.Map(
func(x int) string { return fmt.Sprintf("%d", x) }, strconv.Itoa,
)(fetchData()) )(fetchData())
} }
``` ```
## 📚 Additional Resources ## 📚 Documentation
- [Main README](../README.md) - Core concepts and design philosophy - **[API Documentation](https://pkg.go.dev/github.com/IBM/fp-go/v2)** - Complete API reference
- [API Documentation](https://pkg.go.dev/github.com/IBM/fp-go/v2) - **[Code Samples](./samples/)** - Practical examples and use cases
- [Code Samples](../samples/) - **[Go 1.24 Release Notes](https://tip.golang.org/doc/go1.24)** - Information about generic type aliases
- [Go 1.24 Release Notes](https://tip.golang.org/doc/go1.24)
### Core Modules
- **Option** - Represent optional values without nil
- **Either** - Type-safe error handling with left/right values
- **Result** - Simplified Either with error as left type
- **IO** - Lazy evaluation and side effect management
- **IOEither** - Combine IO with error handling
- **Reader** - Dependency injection pattern
- **ReaderIOEither** - Combine Reader, IO, and Either for complex workflows
- **Array** - Functional array operations
- **Record** - Functional record/map operations
- **Optics** - Lens, Prism, Optional, and Traversal for immutable updates
## 🤔 Should I Migrate? ## 🤔 Should I Migrate?
@@ -310,10 +473,25 @@ func process() IOEither[string] {
- ⚠️ Migration effort outweighs benefits for your project - ⚠️ Migration effort outweighs benefits for your project
- ⚠️ You need stability in production (V2 is newer) - ⚠️ You need stability in production (V2 is newer)
## 🤝 Contributing
Contributions are welcome! Here's how you can help:
1. **Report bugs** - Open an issue with a clear description and reproduction steps
2. **Suggest features** - Share your ideas for improvements
3. **Submit PRs** - Fix bugs or add features (please discuss major changes first)
4. **Improve docs** - Help make the documentation clearer and more comprehensive
Please read our contribution guidelines before submitting pull requests.
## 🐛 Issues and Feedback ## 🐛 Issues and Feedback
Found a bug or have a suggestion? Please [open an issue](https://github.com/IBM/fp-go/issues) on GitHub. Found a bug or have a suggestion? Please [open an issue](https://github.com/IBM/fp-go/issues) on GitHub.
## 📄 License ## 📄 License
This project is licensed under the Apache License 2.0 - see the LICENSE file for details. This project is licensed under the Apache License 2.0. See the [LICENSE](https://github.com/IBM/fp-go/blob/main/LICENSE) file for details.
---
**Made with ❤️ by IBM**

View File

@@ -82,7 +82,7 @@ func MapWithIndex[A, B any](f func(int, A) B) func([]A) []B {
// //
//go:inline //go:inline
func Map[A, B any](f func(a A) B) func([]A) []B { func Map[A, B any](f func(a A) B) func([]A) []B {
return G.Map[[]A, []B, A, B](f) return G.Map[[]A, []B](f)
} }
// MapRef applies a function to a pointer to each element of an array, returning a new array with the results. // MapRef applies a function to a pointer to each element of an array, returning a new array with the results.
@@ -278,7 +278,7 @@ func Of[A any](a A) []A {
// //
//go:inline //go:inline
func MonadChain[A, B any](fa []A, f func(a A) []B) []B { func MonadChain[A, B any](fa []A, f func(a A) []B) []B {
return G.MonadChain[[]A, []B](fa, f) return G.MonadChain(fa, f)
} }
// Chain applies a function that returns an array to each element and flattens the results. // Chain applies a function that returns an array to each element and flattens the results.
@@ -291,7 +291,7 @@ func MonadChain[A, B any](fa []A, f func(a A) []B) []B {
// //
//go:inline //go:inline
func Chain[A, B any](f func(A) []B) func([]A) []B { func Chain[A, B any](f func(A) []B) func([]A) []B {
return G.Chain[[]A, []B](f) return G.Chain[[]A](f)
} }
// MonadAp applies an array of functions to an array of values, producing all combinations. // MonadAp applies an array of functions to an array of values, producing all combinations.
@@ -314,14 +314,14 @@ func Ap[B, A any](fa []A) func([]func(A) B) []B {
// //
//go:inline //go:inline
func Match[A, B any](onEmpty func() B, onNonEmpty func([]A) B) func([]A) B { func Match[A, B any](onEmpty func() B, onNonEmpty func([]A) B) func([]A) B {
return G.Match[[]A](onEmpty, onNonEmpty) return G.Match(onEmpty, onNonEmpty)
} }
// MatchLeft performs pattern matching on an array, calling onEmpty if empty or onNonEmpty with head and tail if not. // MatchLeft performs pattern matching on an array, calling onEmpty if empty or onNonEmpty with head and tail if not.
// //
//go:inline //go:inline
func MatchLeft[A, B any](onEmpty func() B, onNonEmpty func(A, []A) B) func([]A) B { func MatchLeft[A, B any](onEmpty func() B, onNonEmpty func(A, []A) B) func([]A) B {
return G.MatchLeft[[]A](onEmpty, onNonEmpty) return G.MatchLeft(onEmpty, onNonEmpty)
} }
// Tail returns all elements except the first, wrapped in an Option. // Tail returns all elements except the first, wrapped in an Option.
@@ -390,7 +390,7 @@ func Intersperse[A any](middle A) EM.Endomorphism[[]A] {
// Intercalate inserts a separator between elements and concatenates them using a Monoid. // Intercalate inserts a separator between elements and concatenates them using a Monoid.
func Intercalate[A any](m M.Monoid[A]) func(A) func([]A) A { func Intercalate[A any](m M.Monoid[A]) func(A) func([]A) A {
return func(middle A) func([]A) A { return func(middle A) func([]A) A {
return Match(m.Empty, F.Flow2(Intersperse(middle), ConcatAll[A](m))) return Match(m.Empty, F.Flow2(Intersperse(middle), ConcatAll(m)))
} }
} }
@@ -519,7 +519,7 @@ func Push[A any](a A) EM.Endomorphism[[]A] {
// //
//go:inline //go:inline
func MonadFlap[B, A any](fab []func(A) B, a A) []B { func MonadFlap[B, A any](fab []func(A) B, a A) []B {
return G.MonadFlap[func(A) B, []func(A) B, []B, A, B](fab, a) return G.MonadFlap[func(A) B, []func(A) B, []B](fab, a)
} }
// Flap applies a value to an array of functions, producing an array of results. // Flap applies a value to an array of functions, producing an array of results.
@@ -527,7 +527,7 @@ func MonadFlap[B, A any](fab []func(A) B, a A) []B {
// //
//go:inline //go:inline
func Flap[B, A any](a A) func([]func(A) B) []B { func Flap[B, A any](a A) func([]func(A) B) []B {
return G.Flap[func(A) B, []func(A) B, []B, A, B](a) return G.Flap[func(A) B, []func(A) B, []B](a)
} }
// Prepend adds an element to the beginning of an array, returning a new array. // Prepend adds an element to the beginning of an array, returning a new array.

View File

@@ -97,7 +97,7 @@ func TestAp(t *testing.T) {
utils.Double, utils.Double,
utils.Triple, utils.Triple,
}, },
Ap[int, int]([]int{1, 2, 3}), Ap[int]([]int{1, 2, 3}),
), ),
) )
} }

View File

@@ -34,7 +34,7 @@ import (
func Do[S any]( func Do[S any](
empty S, empty S,
) []S { ) []S {
return G.Do[[]S, S](empty) return G.Do[[]S](empty)
} }
// Bind attaches the result of a computation to a context S1 to produce a context S2. // Bind attaches the result of a computation to a context S1 to produce a context S2.
@@ -58,7 +58,7 @@ func Bind[S1, S2, T any](
setter func(T) func(S1) S2, setter func(T) func(S1) S2,
f func(S1) []T, f func(S1) []T,
) func([]S1) []S2 { ) func([]S1) []S2 {
return G.Bind[[]S1, []S2, []T, S1, S2, T](setter, f) return G.Bind[[]S1, []S2](setter, f)
} }
// Let attaches the result of a pure computation to a context S1 to produce a context S2. // Let attaches the result of a pure computation to a context S1 to produce a context S2.
@@ -80,7 +80,7 @@ func Let[S1, S2, T any](
setter func(T) func(S1) S2, setter func(T) func(S1) S2,
f func(S1) T, f func(S1) T,
) func([]S1) []S2 { ) func([]S1) []S2 {
return G.Let[[]S1, []S2, S1, S2, T](setter, f) return G.Let[[]S1, []S2](setter, f)
} }
// LetTo attaches a constant value to a context S1 to produce a context S2. // LetTo attaches a constant value to a context S1 to produce a context S2.
@@ -102,7 +102,7 @@ func LetTo[S1, S2, T any](
setter func(T) func(S1) S2, setter func(T) func(S1) S2,
b T, b T,
) func([]S1) []S2 { ) func([]S1) []S2 {
return G.LetTo[[]S1, []S2, S1, S2, T](setter, b) return G.LetTo[[]S1, []S2](setter, b)
} }
// BindTo initializes a new state S1 from a value T. // BindTo initializes a new state S1 from a value T.
@@ -121,7 +121,7 @@ func LetTo[S1, S2, T any](
func BindTo[S1, T any]( func BindTo[S1, T any](
setter func(T) S1, setter func(T) S1,
) func([]T) []S1 { ) func([]T) []S1 {
return G.BindTo[[]S1, []T, S1, T](setter) return G.BindTo[[]S1, []T](setter)
} }
// ApS attaches a value to a context S1 to produce a context S2 by considering // ApS attaches a value to a context S1 to produce a context S2 by considering
@@ -144,5 +144,5 @@ func ApS[S1, S2, T any](
setter func(T) func(S1) S2, setter func(T) func(S1) S2,
fa []T, fa []T,
) func([]S1) []S2 { ) func([]S1) []S2 {
return G.ApS[[]S1, []S2, []T, S1, S2, T](setter, fa) return G.ApS[[]S1, []S2](setter, fa)
} }

View File

@@ -87,6 +87,6 @@ func Example_sort() {
// [abc klm zyx] // [abc klm zyx]
// [zyx klm abc] // [zyx klm abc]
// [None[int] Some[int](42) Some[int](1337)] // [None[int] Some[int](42) Some[int](1337)]
// [{c {false 0}} {b {true 10}} {d {true 10}} {a {true 30}}] // [{c {0 false}} {b {10 true}} {d {10 true}} {a {30 true}}]
} }

View File

@@ -31,25 +31,25 @@ func Of[GA ~[]A, A any](value A) GA {
func Reduce[GA ~[]A, A, B any](f func(B, A) B, initial B) func(GA) B { func Reduce[GA ~[]A, A, B any](f func(B, A) B, initial B) func(GA) B {
return func(as GA) B { return func(as GA) B {
return MonadReduce[GA](as, f, initial) return MonadReduce(as, f, initial)
} }
} }
func ReduceWithIndex[GA ~[]A, A, B any](f func(int, B, A) B, initial B) func(GA) B { func ReduceWithIndex[GA ~[]A, A, B any](f func(int, B, A) B, initial B) func(GA) B {
return func(as GA) B { return func(as GA) B {
return MonadReduceWithIndex[GA](as, f, initial) return MonadReduceWithIndex(as, f, initial)
} }
} }
func ReduceRight[GA ~[]A, A, B any](f func(A, B) B, initial B) func(GA) B { func ReduceRight[GA ~[]A, A, B any](f func(A, B) B, initial B) func(GA) B {
return func(as GA) B { return func(as GA) B {
return MonadReduceRight[GA](as, f, initial) return MonadReduceRight(as, f, initial)
} }
} }
func ReduceRightWithIndex[GA ~[]A, A, B any](f func(int, A, B) B, initial B) func(GA) B { func ReduceRightWithIndex[GA ~[]A, A, B any](f func(int, A, B) B, initial B) func(GA) B {
return func(as GA) B { return func(as GA) B {
return MonadReduceRightWithIndex[GA](as, f, initial) return MonadReduceRightWithIndex(as, f, initial)
} }
} }

View File

@@ -21,14 +21,56 @@ import (
F "github.com/IBM/fp-go/v2/internal/functor" F "github.com/IBM/fp-go/v2/internal/functor"
) )
// Bind creates an empty context of type [S] to be used with the [Bind] operation // Do creates an empty context of type [S] to be used with the [Bind] operation.
// This is the starting point for do-notation style composition.
//
// Example:
//
// type State struct {
// X int
// Y int
// }
// result := generic.Do[[]State, State](State{})
func Do[GS ~[]S, S any]( func Do[GS ~[]S, S any](
empty S, empty S,
) GS { ) GS {
return Of[GS](empty) return Of[GS](empty)
} }
// Bind attaches the result of a computation to a context [S1] to produce a context [S2] // Bind attaches the result of a computation to a context [S1] to produce a context [S2].
// This enables sequential composition where each step can depend on the results of previous steps.
// For arrays, this produces the cartesian product where later steps can use values from earlier steps.
//
// The setter function takes the result of the computation and returns a function that
// updates the context from S1 to S2.
//
// Example:
//
// type State struct {
// X int
// Y int
// }
//
// result := F.Pipe2(
// generic.Do[[]State, State](State{}),
// generic.Bind[[]State, []State, []int, State, State, int](
// func(x int) func(State) State {
// return func(s State) State { s.X = x; return s }
// },
// func(s State) []int {
// return []int{1, 2, 3}
// },
// ),
// generic.Bind[[]State, []State, []int, State, State, int](
// func(y int) func(State) State {
// return func(s State) State { s.Y = y; return s }
// },
// func(s State) []int {
// // This can access s.X from the previous step
// return []int{s.X * 10, s.X * 20}
// },
// ),
// ) // Produces: {1,10}, {1,20}, {2,20}, {2,40}, {3,30}, {3,60}
func Bind[GS1 ~[]S1, GS2 ~[]S2, GT ~[]T, S1, S2, T any]( func Bind[GS1 ~[]S1, GS2 ~[]S2, GT ~[]T, S1, S2, T any](
setter func(T) func(S1) S2, setter func(T) func(S1) S2,
f func(S1) GT, f func(S1) GT,
@@ -75,7 +117,39 @@ func BindTo[GS1 ~[]S1, GT ~[]T, S1, T any](
) )
} }
// ApS attaches a value to a context [S1] to produce a context [S2] by considering the context and the value concurrently // ApS attaches a value to a context [S1] to produce a context [S2] by considering
// the context and the value concurrently (using Applicative rather than Monad).
// This allows independent computations to be combined without one depending on the result of the other.
//
// Unlike Bind, which sequences operations, ApS can be used when operations are independent
// and can conceptually run in parallel. For arrays, this produces the cartesian product.
//
// Example:
//
// type State struct {
// X int
// Y string
// }
//
// // These operations are independent and can be combined with ApS
// xValues := []int{1, 2}
// yValues := []string{"a", "b"}
//
// result := F.Pipe2(
// generic.Do[[]State, State](State{}),
// generic.ApS[[]State, []State, []int, State, State, int](
// func(x int) func(State) State {
// return func(s State) State { s.X = x; return s }
// },
// xValues,
// ),
// generic.ApS[[]State, []State, []string, State, State, string](
// func(y string) func(State) State {
// return func(s State) State { s.Y = y; return s }
// },
// yValues,
// ),
// ) // [{1,"a"}, {1,"b"}, {2,"a"}, {2,"b"}]
func ApS[GS1 ~[]S1, GS2 ~[]S2, GT ~[]T, S1, S2, T any]( func ApS[GS1 ~[]S1, GS2 ~[]S2, GT ~[]T, S1, S2, T any](
setter func(T) func(S1) S2, setter func(T) func(S1) S2,
fa GT, fa GT,

View File

@@ -22,19 +22,19 @@ import (
type arrayMonad[A, B any, GA ~[]A, GB ~[]B, GAB ~[]func(A) B] struct{} type arrayMonad[A, B any, GA ~[]A, GB ~[]B, GAB ~[]func(A) B] struct{}
func (o *arrayMonad[A, B, GA, GB, GAB]) Of(a A) GA { func (o *arrayMonad[A, B, GA, GB, GAB]) Of(a A) GA {
return Of[GA, A](a) return Of[GA](a)
} }
func (o *arrayMonad[A, B, GA, GB, GAB]) Map(f func(A) B) func(GA) GB { func (o *arrayMonad[A, B, GA, GB, GAB]) Map(f func(A) B) func(GA) GB {
return Map[GA, GB, A, B](f) return Map[GA, GB](f)
} }
func (o *arrayMonad[A, B, GA, GB, GAB]) Chain(f func(A) GB) func(GA) GB { func (o *arrayMonad[A, B, GA, GB, GAB]) Chain(f func(A) GB) func(GA) GB {
return Chain[GA, GB, A, B](f) return Chain[GA](f)
} }
func (o *arrayMonad[A, B, GA, GB, GAB]) Ap(fa GA) func(GAB) GB { func (o *arrayMonad[A, B, GA, GB, GAB]) Ap(fa GA) func(GAB) GB {
return Ap[GB, GAB, GA, B, A](fa) return Ap[GB, GAB](fa)
} }
// Monad implements the monadic operations for an array // Monad implements the monadic operations for an array

View File

@@ -97,11 +97,11 @@ func Flatten[A any](mma NonEmptyArray[NonEmptyArray[A]]) NonEmptyArray[A] {
} }
func MonadChain[A, B any](fa NonEmptyArray[A], f func(a A) NonEmptyArray[B]) NonEmptyArray[B] { func MonadChain[A, B any](fa NonEmptyArray[A], f func(a A) NonEmptyArray[B]) NonEmptyArray[B] {
return G.MonadChain[NonEmptyArray[A], NonEmptyArray[B]](fa, f) return G.MonadChain(fa, f)
} }
func Chain[A, B any](f func(A) NonEmptyArray[B]) func(NonEmptyArray[A]) NonEmptyArray[B] { func Chain[A, B any](f func(A) NonEmptyArray[B]) func(NonEmptyArray[A]) NonEmptyArray[B] {
return G.Chain[NonEmptyArray[A], NonEmptyArray[B]](f) return G.Chain[NonEmptyArray[A]](f)
} }
func MonadAp[B, A any](fab NonEmptyArray[func(A) B], fa NonEmptyArray[A]) NonEmptyArray[B] { func MonadAp[B, A any](fab NonEmptyArray[func(A) B], fa NonEmptyArray[A]) NonEmptyArray[B] {

View File

@@ -94,5 +94,5 @@ func SortByKey[K, T any](ord O.Ord[K], f func(T) K) func(ma []T) []T {
// //
//go:inline //go:inline
func SortBy[T any](ord []O.Ord[T]) func(ma []T) []T { func SortBy[T any](ord []O.Ord[T]) func(ma []T) []T {
return G.SortBy[[]T, []O.Ord[T]](ord) return G.SortBy[[]T](ord)
} }

View File

@@ -18,7 +18,7 @@ import (
// //
//go:inline //go:inline
func StrictUniq[A comparable](as []A) []A { func StrictUniq[A comparable](as []A) []A {
return G.StrictUniq[[]A](as) return G.StrictUniq(as)
} }
// Uniq converts an array of arbitrary items into an array of unique items // Uniq converts an array of arbitrary items into an array of unique items

View File

@@ -36,7 +36,7 @@ import (
// //
//go:inline //go:inline
func ZipWith[FCT ~func(A, B) C, A, B, C any](fa []A, fb []B, f FCT) []C { func ZipWith[FCT ~func(A, B) C, A, B, C any](fa []A, fb []B, f FCT) []C {
return G.ZipWith[[]A, []B, []C, FCT](fa, fb, f) return G.ZipWith[[]A, []B, []C](fa, fb, f)
} }
// Zip takes two arrays and returns an array of corresponding pairs (tuples). // Zip takes two arrays and returns an array of corresponding pairs (tuples).
@@ -79,5 +79,5 @@ func Zip[A, B any](fb []B) func([]A) []T.Tuple2[A, B] {
// //
//go:inline //go:inline
func Unzip[A, B any](cs []T.Tuple2[A, B]) T.Tuple2[[]A, []B] { func Unzip[A, B any](cs []T.Tuple2[A, B]) T.Tuple2[[]A, []B] {
return G.Unzip[[]A, []B, []T.Tuple2[A, B]](cs) return G.Unzip[[]A, []B](cs)
} }

View File

@@ -19,8 +19,8 @@ import (
"fmt" "fmt"
"testing" "testing"
E "github.com/IBM/fp-go/v2/either" "github.com/IBM/fp-go/v2/eq"
EQ "github.com/IBM/fp-go/v2/eq" "github.com/IBM/fp-go/v2/result"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -28,82 +28,82 @@ var (
errTest = fmt.Errorf("test failure") errTest = fmt.Errorf("test failure")
// Eq is the equal predicate checking if objects are equal // Eq is the equal predicate checking if objects are equal
Eq = EQ.FromEquals(assert.ObjectsAreEqual) Eq = eq.FromEquals(assert.ObjectsAreEqual)
) )
func wrap1[T any](wrapped func(t assert.TestingT, expected, actual any, msgAndArgs ...any) bool, t *testing.T, expected T) func(actual T) E.Either[error, T] { func wrap1[T any](wrapped func(t assert.TestingT, expected, actual any, msgAndArgs ...any) bool, t *testing.T, expected T) result.Kleisli[T, T] {
return func(actual T) E.Either[error, T] { return func(actual T) Result[T] {
ok := wrapped(t, expected, actual) ok := wrapped(t, expected, actual)
if ok { if ok {
return E.Of[error](actual) return result.Of(actual)
} }
return E.Left[T](errTest) return result.Left[T](errTest)
} }
} }
// NotEqual tests if the expected and the actual values are not equal // NotEqual tests if the expected and the actual values are not equal
func NotEqual[T any](t *testing.T, expected T) func(actual T) E.Either[error, T] { func NotEqual[T any](t *testing.T, expected T) result.Kleisli[T, T] {
return wrap1(assert.NotEqual, t, expected) return wrap1(assert.NotEqual, t, expected)
} }
// Equal tests if the expected and the actual values are equal // Equal tests if the expected and the actual values are equal
func Equal[T any](t *testing.T, expected T) func(actual T) E.Either[error, T] { func Equal[T any](t *testing.T, expected T) result.Kleisli[T, T] {
return wrap1(assert.Equal, t, expected) return wrap1(assert.Equal, t, expected)
} }
// Length tests if an array has the expected length // Length tests if an array has the expected length
func Length[T any](t *testing.T, expected int) func(actual []T) E.Either[error, []T] { func Length[T any](t *testing.T, expected int) result.Kleisli[[]T, []T] {
return func(actual []T) E.Either[error, []T] { return func(actual []T) Result[[]T] {
ok := assert.Len(t, actual, expected) ok := assert.Len(t, actual, expected)
if ok { if ok {
return E.Of[error](actual) return result.Of(actual)
} }
return E.Left[[]T](errTest) return result.Left[[]T](errTest)
} }
} }
// NoError validates that there is no error // NoError validates that there is no error
func NoError[T any](t *testing.T) func(actual E.Either[error, T]) E.Either[error, T] { func NoError[T any](t *testing.T) result.Operator[T, T] {
return func(actual E.Either[error, T]) E.Either[error, T] { return func(actual Result[T]) Result[T] {
return E.MonadFold(actual, func(e error) E.Either[error, T] { return result.MonadFold(actual, func(e error) Result[T] {
assert.NoError(t, e) assert.NoError(t, e)
return E.Left[T](e) return result.Left[T](e)
}, func(value T) E.Either[error, T] { }, func(value T) Result[T] {
assert.NoError(t, nil) assert.NoError(t, nil)
return E.Right[error](value) return result.Of(value)
}) })
} }
} }
// ArrayContains tests if a value is contained in an array // ArrayContains tests if a value is contained in an array
func ArrayContains[T any](t *testing.T, expected T) func(actual []T) E.Either[error, []T] { func ArrayContains[T any](t *testing.T, expected T) result.Kleisli[[]T, []T] {
return func(actual []T) E.Either[error, []T] { return func(actual []T) Result[[]T] {
ok := assert.Contains(t, actual, expected) ok := assert.Contains(t, actual, expected)
if ok { if ok {
return E.Of[error](actual) return result.Of(actual)
} }
return E.Left[[]T](errTest) return result.Left[[]T](errTest)
} }
} }
// ContainsKey tests if a key is contained in a map // ContainsKey tests if a key is contained in a map
func ContainsKey[T any, K comparable](t *testing.T, expected K) func(actual map[K]T) E.Either[error, map[K]T] { func ContainsKey[T any, K comparable](t *testing.T, expected K) result.Kleisli[map[K]T, map[K]T] {
return func(actual map[K]T) E.Either[error, map[K]T] { return func(actual map[K]T) Result[map[K]T] {
ok := assert.Contains(t, actual, expected) ok := assert.Contains(t, actual, expected)
if ok { if ok {
return E.Of[error](actual) return result.Of(actual)
} }
return E.Left[map[K]T](errTest) return result.Left[map[K]T](errTest)
} }
} }
// NotContainsKey tests if a key is not contained in a map // NotContainsKey tests if a key is not contained in a map
func NotContainsKey[T any, K comparable](t *testing.T, expected K) func(actual map[K]T) E.Either[error, map[K]T] { func NotContainsKey[T any, K comparable](t *testing.T, expected K) result.Kleisli[map[K]T, map[K]T] {
return func(actual map[K]T) E.Either[error, map[K]T] { return func(actual map[K]T) Result[map[K]T] {
ok := assert.NotContains(t, actual, expected) ok := assert.NotContains(t, actual, expected)
if ok { if ok {
return E.Of[error](actual) return result.Of(actual)
} }
return E.Left[map[K]T](errTest) return result.Left[map[K]T](errTest)
} }
} }

7
v2/assert/types.go Normal file
View File

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

View File

@@ -53,7 +53,7 @@ func MakeBounded[T any](o ord.Ord[T], t, b T) Bounded[T] {
// Clamp returns a function that clamps against the bounds defined in the bounded type // Clamp returns a function that clamps against the bounds defined in the bounded type
func Clamp[T any](b Bounded[T]) func(T) T { func Clamp[T any](b Bounded[T]) func(T) T {
return ord.Clamp[T](b)(b.Bottom(), b.Top()) return ord.Clamp(b)(b.Bottom(), b.Top())
} }
// Reverse reverses the ordering and swaps the bounds // Reverse reverses the ordering and swaps the bounds

7
v2/builder/builder.go Normal file
View File

@@ -0,0 +1,7 @@
package builder
type (
Builder[T any] interface {
Build() Result[T]
}
)

12
v2/builder/prism.go Normal file
View File

@@ -0,0 +1,12 @@
package builder
import (
F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/optics/prism"
"github.com/IBM/fp-go/v2/result"
)
// BuilderPrism createa a [Prism] that converts between a builder and its type
func BuilderPrism[T any, B Builder[T]](creator func(T) B) Prism[B, T] {
return prism.MakePrism(F.Flow2(B.Build, result.ToOption[T]), creator)
}

15
v2/builder/types.go Normal file
View File

@@ -0,0 +1,15 @@
package builder
import (
"github.com/IBM/fp-go/v2/optics/prism"
"github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/result"
)
type (
Result[T any] = result.Result[T]
Prism[S, A any] = prism.Prism[S, A]
Option[T any] = option.Option[T]
)

View File

@@ -15,14 +15,163 @@
package bytes package bytes
// Empty returns an empty byte slice.
//
// This function returns the identity element for the byte slice Monoid,
// which is an empty byte slice. It's useful as a starting point for
// building byte slices or as a default value.
//
// Returns:
// - An empty byte slice ([]byte{})
//
// Properties:
// - Empty() is the identity element for Monoid.Concat
// - Monoid.Concat(Empty(), x) == x
// - Monoid.Concat(x, Empty()) == x
//
// Example - Basic usage:
//
// empty := Empty()
// fmt.Println(len(empty)) // 0
//
// Example - As identity element:
//
// data := []byte("hello")
// result1 := Monoid.Concat(Empty(), data) // []byte("hello")
// result2 := Monoid.Concat(data, Empty()) // []byte("hello")
//
// Example - Building byte slices:
//
// // Start with empty and build up
// buffer := Empty()
// buffer = Monoid.Concat(buffer, []byte("Hello"))
// buffer = Monoid.Concat(buffer, []byte(" "))
// buffer = Monoid.Concat(buffer, []byte("World"))
// // buffer: []byte("Hello World")
//
// See also:
// - Monoid.Empty(): Alternative way to get empty byte slice
// - ConcatAll(): For concatenating multiple byte slices
func Empty() []byte { func Empty() []byte {
return Monoid.Empty() return Monoid.Empty()
} }
// ToString converts a byte slice to a string.
//
// This function performs a direct conversion from []byte to string.
// The conversion creates a new string with a copy of the byte data.
//
// Parameters:
// - a: The byte slice to convert
//
// Returns:
// - A string containing the same data as the byte slice
//
// Performance Note:
//
// This conversion allocates a new string. For performance-critical code
// that needs to avoid allocations, consider using unsafe.String (Go 1.20+)
// or working directly with byte slices.
//
// Example - Basic conversion:
//
// bytes := []byte("hello")
// str := ToString(bytes)
// fmt.Println(str) // "hello"
//
// Example - Converting binary data:
//
// // ASCII codes for "Hello"
// data := []byte{0x48, 0x65, 0x6c, 0x6c, 0x6f}
// str := ToString(data)
// fmt.Println(str) // "Hello"
//
// Example - Empty byte slice:
//
// empty := Empty()
// str := ToString(empty)
// fmt.Println(str == "") // true
//
// Example - UTF-8 encoded text:
//
// utf8Bytes := []byte("Hello, 世界")
// str := ToString(utf8Bytes)
// fmt.Println(str) // "Hello, 世界"
//
// Example - Round-trip conversion:
//
// original := "test string"
// bytes := []byte(original)
// result := ToString(bytes)
// fmt.Println(original == result) // true
//
// See also:
// - []byte(string): For converting string to byte slice
// - Size(): For getting the length of a byte slice
func ToString(a []byte) string { func ToString(a []byte) string {
return string(a) return string(a)
} }
// Size returns the number of bytes in a byte slice.
//
// This function returns the length of the byte slice, which is the number
// of bytes it contains. This is equivalent to len(as) but provided as a
// named function for use in functional composition.
//
// Parameters:
// - as: The byte slice to measure
//
// Returns:
// - The number of bytes in the slice
//
// Example - Basic usage:
//
// data := []byte("hello")
// size := Size(data)
// fmt.Println(size) // 5
//
// Example - Empty slice:
//
// empty := Empty()
// size := Size(empty)
// fmt.Println(size) // 0
//
// Example - Binary data:
//
// binary := []byte{0x01, 0x02, 0x03, 0x04}
// size := Size(binary)
// fmt.Println(size) // 4
//
// Example - UTF-8 encoded text:
//
// // Note: Size returns byte count, not character count
// utf8 := []byte("Hello, 世界")
// byteCount := Size(utf8)
// fmt.Println(byteCount) // 13 (not 9 characters)
//
// Example - Using in functional composition:
//
// import "github.com/IBM/fp-go/v2/array"
//
// slices := [][]byte{
// []byte("a"),
// []byte("bb"),
// []byte("ccc"),
// }
//
// // Map to get sizes
// sizes := array.Map(Size)(slices)
// // sizes: []int{1, 2, 3}
//
// Example - Checking if slice is empty:
//
// data := []byte("test")
// isEmpty := Size(data) == 0
// fmt.Println(isEmpty) // false
//
// See also:
// - len(): Built-in function for getting slice length
// - ToString(): For converting byte slice to string
func Size(as []byte) int { func Size(as []byte) int {
return len(as) return len(as)
} }

View File

@@ -187,6 +187,299 @@ func TestOrd(t *testing.T) {
}) })
} }
// TestOrdProperties tests mathematical properties of Ord
func TestOrdProperties(t *testing.T) {
t.Run("reflexivity: x == x", func(t *testing.T) {
testCases := [][]byte{
[]byte{},
[]byte("a"),
[]byte("test"),
[]byte{0x01, 0x02, 0x03},
}
for _, tc := range testCases {
assert.Equal(t, 0, Ord.Compare(tc, tc),
"Compare(%v, %v) should be 0", tc, tc)
assert.True(t, Ord.Equals(tc, tc),
"Equals(%v, %v) should be true", tc, tc)
}
})
t.Run("antisymmetry: if x <= y and y <= x then x == y", func(t *testing.T) {
testCases := []struct {
a, b []byte
}{
{[]byte("abc"), []byte("abc")},
{[]byte{}, []byte{}},
{[]byte{0x01}, []byte{0x01}},
}
for _, tc := range testCases {
cmp1 := Ord.Compare(tc.a, tc.b)
cmp2 := Ord.Compare(tc.b, tc.a)
if cmp1 <= 0 && cmp2 <= 0 {
assert.True(t, Ord.Equals(tc.a, tc.b),
"If %v <= %v and %v <= %v, they should be equal", tc.a, tc.b, tc.b, tc.a)
}
}
})
t.Run("transitivity: if x <= y and y <= z then x <= z", func(t *testing.T) {
x := []byte("a")
y := []byte("b")
z := []byte("c")
cmpXY := Ord.Compare(x, y)
cmpYZ := Ord.Compare(y, z)
cmpXZ := Ord.Compare(x, z)
if cmpXY <= 0 && cmpYZ <= 0 {
assert.True(t, cmpXZ <= 0,
"If %v <= %v and %v <= %v, then %v <= %v", x, y, y, z, x, z)
}
})
t.Run("totality: either x <= y or y <= x", func(t *testing.T) {
testCases := []struct {
a, b []byte
}{
{[]byte("abc"), []byte("abd")},
{[]byte("xyz"), []byte("abc")},
{[]byte{}, []byte("a")},
{[]byte{0x01}, []byte{0x02}},
}
for _, tc := range testCases {
cmp1 := Ord.Compare(tc.a, tc.b)
cmp2 := Ord.Compare(tc.b, tc.a)
assert.True(t, cmp1 <= 0 || cmp2 <= 0,
"Either %v <= %v or %v <= %v must be true", tc.a, tc.b, tc.b, tc.a)
}
})
}
// TestEdgeCases tests edge cases and boundary conditions
func TestEdgeCases(t *testing.T) {
t.Run("very large byte slices", func(t *testing.T) {
large := make([]byte, 1000000)
for i := range large {
large[i] = byte(i % 256)
}
size := Size(large)
assert.Equal(t, 1000000, size)
str := ToString(large)
assert.Equal(t, 1000000, len(str))
})
t.Run("concatenating many slices", func(t *testing.T) {
slices := make([][]byte, 100)
for i := range slices {
slices[i] = []byte{byte(i)}
}
result := ConcatAll(slices...)
assert.Equal(t, 100, Size(result))
})
t.Run("null bytes in slice", func(t *testing.T) {
data := []byte{0x00, 0x01, 0x00, 0x02}
size := Size(data)
assert.Equal(t, 4, size)
str := ToString(data)
assert.Equal(t, 4, len(str))
})
t.Run("comparing slices with null bytes", func(t *testing.T) {
a := []byte{0x00, 0x01}
b := []byte{0x00, 0x02}
assert.Equal(t, -1, Ord.Compare(a, b))
})
}
// TestMonoidConcatPerformance tests concatenation performance characteristics
func TestMonoidConcatPerformance(t *testing.T) {
t.Run("ConcatAll vs repeated Concat", func(t *testing.T) {
slices := [][]byte{
[]byte("a"),
[]byte("b"),
[]byte("c"),
[]byte("d"),
[]byte("e"),
}
// Using ConcatAll
result1 := ConcatAll(slices...)
// Using repeated Concat
result2 := Monoid.Empty()
for _, s := range slices {
result2 = Monoid.Concat(result2, s)
}
assert.Equal(t, result1, result2)
assert.Equal(t, []byte("abcde"), result1)
})
}
// TestRoundTrip tests round-trip conversions
func TestRoundTrip(t *testing.T) {
t.Run("string to bytes to string", func(t *testing.T) {
original := "Hello, World! 世界"
bytes := []byte(original)
result := ToString(bytes)
assert.Equal(t, original, result)
})
t.Run("bytes to string to bytes", func(t *testing.T) {
original := []byte{0x48, 0x65, 0x6c, 0x6c, 0x6f}
str := ToString(original)
result := []byte(str)
assert.Equal(t, original, result)
})
}
// TestConcatAllVariadic tests ConcatAll with various argument counts
func TestConcatAllVariadic(t *testing.T) {
t.Run("zero arguments", func(t *testing.T) {
result := ConcatAll()
assert.Equal(t, []byte{}, result)
})
t.Run("one argument", func(t *testing.T) {
result := ConcatAll([]byte("test"))
assert.Equal(t, []byte("test"), result)
})
t.Run("two arguments", func(t *testing.T) {
result := ConcatAll([]byte("hello"), []byte("world"))
assert.Equal(t, []byte("helloworld"), result)
})
t.Run("many arguments", func(t *testing.T) {
result := ConcatAll(
[]byte("a"),
[]byte("b"),
[]byte("c"),
[]byte("d"),
[]byte("e"),
[]byte("f"),
[]byte("g"),
[]byte("h"),
[]byte("i"),
[]byte("j"),
)
assert.Equal(t, []byte("abcdefghij"), result)
})
}
// Benchmark tests
func BenchmarkToString(b *testing.B) {
data := []byte("Hello, World!")
b.Run("small", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = ToString(data)
}
})
b.Run("large", func(b *testing.B) {
large := make([]byte, 10000)
for i := range large {
large[i] = byte(i % 256)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ToString(large)
}
})
}
func BenchmarkSize(b *testing.B) {
data := []byte("Hello, World!")
for i := 0; i < b.N; i++ {
_ = Size(data)
}
}
func BenchmarkMonoidConcat(b *testing.B) {
a := []byte("Hello")
c := []byte(" World")
b.Run("small slices", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = Monoid.Concat(a, c)
}
})
b.Run("large slices", func(b *testing.B) {
large1 := make([]byte, 10000)
large2 := make([]byte, 10000)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = Monoid.Concat(large1, large2)
}
})
}
func BenchmarkConcatAll(b *testing.B) {
slices := [][]byte{
[]byte("Hello"),
[]byte(" "),
[]byte("World"),
[]byte("!"),
}
b.Run("few slices", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = ConcatAll(slices...)
}
})
b.Run("many slices", func(b *testing.B) {
many := make([][]byte, 100)
for i := range many {
many[i] = []byte{byte(i)}
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ConcatAll(many...)
}
})
}
func BenchmarkOrdCompare(b *testing.B) {
a := []byte("abc")
c := []byte("abd")
b.Run("equal", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = Ord.Compare(a, a)
}
})
b.Run("different", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = Ord.Compare(a, c)
}
})
b.Run("large slices", func(b *testing.B) {
large1 := make([]byte, 10000)
large2 := make([]byte, 10000)
large2[9999] = 1
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = Ord.Compare(large1, large2)
}
})
}
// Example tests // Example tests
func ExampleEmpty() { func ExampleEmpty() {
empty := Empty() empty := Empty()
@@ -219,3 +512,17 @@ func ExampleConcatAll() {
// Output: // Output:
} }
func ExampleMonoid_concat() {
result := Monoid.Concat([]byte("Hello"), []byte(" World"))
println(string(result)) // Hello World
// Output:
}
func ExampleOrd_compare() {
cmp := Ord.Compare([]byte("abc"), []byte("abd"))
println(cmp) // -1 (abc < abd)
// Output:
}

4
v2/bytes/coverage.out Normal file
View File

@@ -0,0 +1,4 @@
mode: set
github.com/IBM/fp-go/v2/bytes/bytes.go:55.21,57.2 1 1
github.com/IBM/fp-go/v2/bytes/bytes.go:111.32,113.2 1 1
github.com/IBM/fp-go/v2/bytes/bytes.go:175.26,177.2 1 1

View File

@@ -23,12 +23,219 @@ import (
) )
var ( var (
// monoid for byte arrays // Monoid is the Monoid instance for byte slices.
//
// This Monoid combines byte slices through concatenation, with an empty
// byte slice as the identity element. It satisfies the monoid laws:
//
// Identity laws:
// - Monoid.Concat(Monoid.Empty(), x) == x (left identity)
// - Monoid.Concat(x, Monoid.Empty()) == x (right identity)
//
// Associativity law:
// - Monoid.Concat(Monoid.Concat(a, b), c) == Monoid.Concat(a, Monoid.Concat(b, c))
//
// Operations:
// - Empty(): Returns an empty byte slice []byte{}
// - Concat(a, b []byte): Concatenates two byte slices
//
// Example - Basic concatenation:
//
// result := Monoid.Concat([]byte("Hello"), []byte(" World"))
// // result: []byte("Hello World")
//
// Example - Identity element:
//
// empty := Monoid.Empty()
// data := []byte("test")
// result1 := Monoid.Concat(empty, data) // []byte("test")
// result2 := Monoid.Concat(data, empty) // []byte("test")
//
// Example - Building byte buffers:
//
// buffer := Monoid.Empty()
// buffer = Monoid.Concat(buffer, []byte("Line 1\n"))
// buffer = Monoid.Concat(buffer, []byte("Line 2\n"))
// buffer = Monoid.Concat(buffer, []byte("Line 3\n"))
//
// Example - Associativity:
//
// a := []byte("a")
// b := []byte("b")
// c := []byte("c")
// left := Monoid.Concat(Monoid.Concat(a, b), c) // []byte("abc")
// right := Monoid.Concat(a, Monoid.Concat(b, c)) // []byte("abc")
// // left == right
//
// See also:
// - ConcatAll: For concatenating multiple byte slices at once
// - Empty(): Convenience function for getting empty byte slice
Monoid = A.Monoid[byte]() Monoid = A.Monoid[byte]()
// ConcatAll concatenates all bytes // ConcatAll efficiently concatenates multiple byte slices into a single slice.
//
// This function takes a variadic number of byte slices and combines them
// into a single byte slice. It pre-allocates the exact amount of memory
// needed, making it more efficient than repeated concatenation.
//
// Parameters:
// - slices: Zero or more byte slices to concatenate
//
// Returns:
// - A new byte slice containing all input slices concatenated in order
//
// Performance:
//
// ConcatAll is more efficient than using Monoid.Concat repeatedly because
// it calculates the total size upfront and allocates memory once, avoiding
// multiple allocations and copies.
//
// Example - Basic usage:
//
// result := ConcatAll(
// []byte("Hello"),
// []byte(" "),
// []byte("World"),
// )
// // result: []byte("Hello World")
//
// Example - Empty input:
//
// result := ConcatAll()
// // result: []byte{}
//
// Example - Single slice:
//
// result := ConcatAll([]byte("test"))
// // result: []byte("test")
//
// Example - Building protocol messages:
//
// import "encoding/binary"
//
// header := []byte{0x01, 0x02}
// length := make([]byte, 4)
// binary.BigEndian.PutUint32(length, 100)
// payload := []byte("data")
// footer := []byte{0xFF}
//
// message := ConcatAll(header, length, payload, footer)
//
// Example - With empty slices:
//
// result := ConcatAll(
// []byte("a"),
// []byte{},
// []byte("b"),
// []byte{},
// []byte("c"),
// )
// // result: []byte("abc")
//
// Example - Building CSV line:
//
// fields := [][]byte{
// []byte("John"),
// []byte("Doe"),
// []byte("30"),
// }
// separator := []byte(",")
//
// // Interleave fields with separators
// parts := [][]byte{
// fields[0], separator,
// fields[1], separator,
// fields[2],
// }
// line := ConcatAll(parts...)
// // line: []byte("John,Doe,30")
//
// See also:
// - Monoid.Concat: For concatenating exactly two byte slices
// - bytes.Join: Standard library function for joining with separator
ConcatAll = A.ArrayConcatAll[byte] ConcatAll = A.ArrayConcatAll[byte]
// Ord implements the default ordering on bytes // Ord is the Ord instance for byte slices providing lexicographic ordering.
//
// This Ord instance compares byte slices lexicographically (dictionary order),
// comparing bytes from left to right until a difference is found or one slice
// ends. It uses the standard library's bytes.Compare and bytes.Equal functions.
//
// Comparison rules:
// - Compares byte-by-byte from left to right
// - First differing byte determines the order
// - Shorter slice is less than longer slice if all bytes match
// - Empty slice is less than any non-empty slice
//
// Operations:
// - Compare(a, b []byte) int: Returns -1 if a < b, 0 if a == b, 1 if a > b
// - Equals(a, b []byte) bool: Returns true if slices are equal
//
// Example - Basic comparison:
//
// cmp := Ord.Compare([]byte("abc"), []byte("abd"))
// // cmp: -1 (abc < abd)
//
// cmp = Ord.Compare([]byte("xyz"), []byte("abc"))
// // cmp: 1 (xyz > abc)
//
// cmp = Ord.Compare([]byte("test"), []byte("test"))
// // cmp: 0 (equal)
//
// Example - Length differences:
//
// cmp := Ord.Compare([]byte("ab"), []byte("abc"))
// // cmp: -1 (shorter is less)
//
// cmp = Ord.Compare([]byte("abc"), []byte("ab"))
// // cmp: 1 (longer is greater)
//
// Example - Empty slices:
//
// cmp := Ord.Compare([]byte{}, []byte("a"))
// // cmp: -1 (empty is less)
//
// cmp = Ord.Compare([]byte{}, []byte{})
// // cmp: 0 (both empty)
//
// Example - Equality check:
//
// equal := Ord.Equals([]byte("test"), []byte("test"))
// // equal: true
//
// equal = Ord.Equals([]byte("test"), []byte("Test"))
// // equal: false (case-sensitive)
//
// Example - Sorting byte slices:
//
// import "github.com/IBM/fp-go/v2/array"
//
// data := [][]byte{
// []byte("zebra"),
// []byte("apple"),
// []byte("mango"),
// }
//
// sorted := array.Sort(Ord)(data)
// // sorted: [[]byte("apple"), []byte("mango"), []byte("zebra")]
//
// Example - Binary data comparison:
//
// cmp := Ord.Compare([]byte{0x01, 0x02}, []byte{0x01, 0x03})
// // cmp: -1 (0x02 < 0x03)
//
// Example - Finding minimum:
//
// import O "github.com/IBM/fp-go/v2/ord"
//
// a := []byte("xyz")
// b := []byte("abc")
// min := O.Min(Ord)(a, b)
// // min: []byte("abc")
//
// See also:
// - bytes.Compare: Standard library comparison function
// - bytes.Equal: Standard library equality function
// - array.Sort: For sorting slices using an Ord instance
Ord = O.MakeOrd(bytes.Compare, bytes.Equal) Ord = O.MakeOrd(bytes.Compare, bytes.Equal)
) )

View File

@@ -35,5 +35,6 @@ func Commands() []*C.Command {
IOCommand(), IOCommand(),
IOOptionCommand(), IOOptionCommand(),
DICommand(), DICommand(),
LensCommand(),
} }
} }

645
v2/cli/lens.go Normal file
View File

@@ -0,0 +1,645 @@
// 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 cli
import (
"bytes"
"go/ast"
"go/parser"
"go/token"
"log"
"os"
"path/filepath"
"reflect"
"strings"
"text/template"
C "github.com/urfave/cli/v2"
)
const (
keyLensDir = "dir"
keyVerbose = "verbose"
lensAnnotation = "fp-go:Lens"
)
var (
flagLensDir = &C.StringFlag{
Name: keyLensDir,
Value: ".",
Usage: "Directory to scan for Go files",
}
flagVerbose = &C.BoolFlag{
Name: keyVerbose,
Aliases: []string{"v"},
Value: false,
Usage: "Enable verbose output",
}
)
// structInfo holds information about a struct that needs lens generation
type structInfo struct {
Name string
Fields []fieldInfo
Imports map[string]string // package path -> alias
}
// fieldInfo holds information about a struct field
type fieldInfo struct {
Name string
TypeName string
BaseType string // TypeName without leading * for pointer types
IsOptional bool // true if field is a pointer or has json omitempty tag
IsComparable bool // true if the type is comparable (can use ==)
}
// templateData holds data for template rendering
type templateData struct {
PackageName string
Structs []structInfo
}
const lensStructTemplate = `
// {{.Name}}Lenses provides lenses for accessing fields of {{.Name}}
type {{.Name}}Lenses struct {
{{- range .Fields}}
{{.Name}} {{if .IsOptional}}LO.LensO[{{$.Name}}, {{.TypeName}}]{{else}}L.Lens[{{$.Name}}, {{.TypeName}}]{{end}}
{{- end}}
}
// {{.Name}}RefLenses provides lenses for accessing fields of {{.Name}} via a reference to {{.Name}}
type {{.Name}}RefLenses struct {
{{- range .Fields}}
{{.Name}} {{if .IsOptional}}LO.LensO[*{{$.Name}}, {{.TypeName}}]{{else}}L.Lens[*{{$.Name}}, {{.TypeName}}]{{end}}
{{- end}}
}
`
const lensConstructorTemplate = `
// Make{{.Name}}Lenses creates a new {{.Name}}Lenses with lenses for all fields
func Make{{.Name}}Lenses() {{.Name}}Lenses {
{{- range .Fields}}
{{- if .IsOptional}}
iso{{.Name}} := I.FromZero[{{.TypeName}}]()
{{- end}}
{{- end}}
return {{.Name}}Lenses{
{{- range .Fields}}
{{- if .IsOptional}}
{{.Name}}: L.MakeLens(
func(s {{$.Name}}) O.Option[{{.TypeName}}] { return iso{{.Name}}.Get(s.{{.Name}}) },
func(s {{$.Name}}, v O.Option[{{.TypeName}}]) {{$.Name}} { s.{{.Name}} = iso{{.Name}}.ReverseGet(v); return s },
),
{{- else}}
{{.Name}}: L.MakeLens(
func(s {{$.Name}}) {{.TypeName}} { return s.{{.Name}} },
func(s {{$.Name}}, v {{.TypeName}}) {{$.Name}} { s.{{.Name}} = v; return s },
),
{{- end}}
{{- end}}
}
}
// Make{{.Name}}RefLenses creates a new {{.Name}}RefLenses with lenses for all fields
func Make{{.Name}}RefLenses() {{.Name}}RefLenses {
return {{.Name}}RefLenses{
{{- range .Fields}}
{{- if .IsOptional}}
{{- if .IsComparable}}
{{.Name}}: LO.FromIso[*{{$.Name}}](I.FromZero[{{.TypeName}}]())(L.MakeLensStrict(
func(s *{{$.Name}}) {{.TypeName}} { return s.{{.Name}} },
func(s *{{$.Name}}, v {{.TypeName}}) *{{$.Name}} { s.{{.Name}} = v; return s },
)),
{{- else}}
{{.Name}}: LO.FromIso[*{{$.Name}}](I.FromZero[{{.TypeName}}]())(L.MakeLensRef(
func(s *{{$.Name}}) {{.TypeName}} { return s.{{.Name}} },
func(s *{{$.Name}}, v {{.TypeName}}) *{{$.Name}} { s.{{.Name}} = v; return s },
)),
{{- end}}
{{- else}}
{{- if .IsComparable}}
{{.Name}}: L.MakeLensStrict(
func(s *{{$.Name}}) {{.TypeName}} { return s.{{.Name}} },
func(s *{{$.Name}}, v {{.TypeName}}) *{{$.Name}} { s.{{.Name}} = v; return s },
),
{{- else}}
{{.Name}}: L.MakeLensRef(
func(s *{{$.Name}}) {{.TypeName}} { return s.{{.Name}} },
func(s *{{$.Name}}, v {{.TypeName}}) *{{$.Name}} { s.{{.Name}} = v; return s },
),
{{- end}}
{{- end}}
{{- end}}
}
}
`
var (
structTmpl *template.Template
constructorTmpl *template.Template
)
func init() {
var err error
structTmpl, err = template.New("struct").Parse(lensStructTemplate)
if err != nil {
panic(err)
}
constructorTmpl, err = template.New("constructor").Parse(lensConstructorTemplate)
if err != nil {
panic(err)
}
}
// hasLensAnnotation checks if a comment group contains the lens annotation
func hasLensAnnotation(doc *ast.CommentGroup) bool {
if doc == nil {
return false
}
for _, comment := range doc.List {
if strings.Contains(comment.Text, lensAnnotation) {
return true
}
}
return false
}
// getTypeName extracts the type name from a field type expression
func getTypeName(expr ast.Expr) string {
switch t := expr.(type) {
case *ast.Ident:
return t.Name
case *ast.StarExpr:
return "*" + getTypeName(t.X)
case *ast.ArrayType:
return "[]" + getTypeName(t.Elt)
case *ast.MapType:
return "map[" + getTypeName(t.Key) + "]" + getTypeName(t.Value)
case *ast.SelectorExpr:
return getTypeName(t.X) + "." + t.Sel.Name
case *ast.InterfaceType:
return "interface{}"
case *ast.IndexExpr:
// Generic type with single type parameter (Go 1.18+)
// e.g., Option[string]
return getTypeName(t.X) + "[" + getTypeName(t.Index) + "]"
case *ast.IndexListExpr:
// Generic type with multiple type parameters (Go 1.18+)
// e.g., Map[string, int]
var params []string
for _, index := range t.Indices {
params = append(params, getTypeName(index))
}
return getTypeName(t.X) + "[" + strings.Join(params, ", ") + "]"
default:
return "any"
}
}
// extractImports extracts package imports from a type expression
// Returns a map of package path -> package name
func extractImports(expr ast.Expr, imports map[string]string) {
switch t := expr.(type) {
case *ast.StarExpr:
extractImports(t.X, imports)
case *ast.ArrayType:
extractImports(t.Elt, imports)
case *ast.MapType:
extractImports(t.Key, imports)
extractImports(t.Value, imports)
case *ast.SelectorExpr:
// This is a qualified identifier like "option.Option"
if ident, ok := t.X.(*ast.Ident); ok {
// ident.Name is the package name (e.g., "option")
// We need to track this for import resolution
imports[ident.Name] = ident.Name
}
case *ast.IndexExpr:
// Generic type with single type parameter
extractImports(t.X, imports)
extractImports(t.Index, imports)
case *ast.IndexListExpr:
// Generic type with multiple type parameters
extractImports(t.X, imports)
for _, index := range t.Indices {
extractImports(index, imports)
}
}
}
// hasOmitEmpty checks if a struct tag contains json omitempty
func hasOmitEmpty(tag *ast.BasicLit) bool {
if tag == nil {
return false
}
// Parse the struct tag
tagValue := strings.Trim(tag.Value, "`")
structTag := reflect.StructTag(tagValue)
jsonTag := structTag.Get("json")
// Check if omitempty is present
parts := strings.Split(jsonTag, ",")
for _, part := range parts {
if strings.TrimSpace(part) == "omitempty" {
return true
}
}
return false
}
// isPointerType checks if a type expression is a pointer
func isPointerType(expr ast.Expr) bool {
_, ok := expr.(*ast.StarExpr)
return ok
}
// isComparableType checks if a type expression represents a comparable type.
// Comparable types in Go include:
// - Basic types (bool, numeric types, string)
// - Pointer types
// - Channel types
// - Interface types
// - Structs where all fields are comparable
// - Arrays where the element type is comparable
//
// Non-comparable types include:
// - Slices
// - Maps
// - Functions
func isComparableType(expr ast.Expr) bool {
switch t := expr.(type) {
case *ast.Ident:
// Basic types and named types
// We assume named types are comparable unless they're known non-comparable types
name := t.Name
// Known non-comparable built-in types
if name == "error" {
// error is an interface, which is comparable
return true
}
// Most basic types and named types are comparable
// We can't determine if a custom type is comparable without type checking,
// so we assume it is (conservative approach)
return true
case *ast.StarExpr:
// Pointer types are always comparable
return true
case *ast.ArrayType:
// Arrays are comparable if their element type is comparable
if t.Len == nil {
// This is a slice (no length), slices are not comparable
return false
}
// Fixed-size array, check element type
return isComparableType(t.Elt)
case *ast.MapType:
// Maps are not comparable
return false
case *ast.FuncType:
// Functions are not comparable
return false
case *ast.InterfaceType:
// Interface types are comparable
return true
case *ast.StructType:
// Structs are comparable if all fields are comparable
// We can't easily determine this without full type information,
// so we conservatively return false for struct literals
return false
case *ast.SelectorExpr:
// Qualified identifier (e.g., pkg.Type)
// We can't determine comparability without type information
// Check for known non-comparable types from standard library
if ident, ok := t.X.(*ast.Ident); ok {
pkgName := ident.Name
typeName := t.Sel.Name
// Check for known non-comparable types
if pkgName == "context" && typeName == "Context" {
// context.Context is an interface, which is comparable
return true
}
// For other qualified types, we assume they're comparable
// This is a conservative approach
}
return true
case *ast.IndexExpr, *ast.IndexListExpr:
// Generic types - we can't determine comparability without type information
// For common generic types, we can make educated guesses
var baseExpr ast.Expr
if idx, ok := t.(*ast.IndexExpr); ok {
baseExpr = idx.X
} else if idxList, ok := t.(*ast.IndexListExpr); ok {
baseExpr = idxList.X
}
if sel, ok := baseExpr.(*ast.SelectorExpr); ok {
if ident, ok := sel.X.(*ast.Ident); ok {
pkgName := ident.Name
typeName := sel.Sel.Name
// Check for known non-comparable generic types
if pkgName == "option" && typeName == "Option" {
// Option types are not comparable (they contain a slice internally)
return false
}
if pkgName == "either" && typeName == "Either" {
// Either types are not comparable
return false
}
}
}
// For other generic types, conservatively assume not comparable
return false
case *ast.ChanType:
// Channel types are comparable
return true
default:
// Unknown type, conservatively assume not comparable
return false
}
}
// parseFile parses a Go file and extracts structs with lens annotations
func parseFile(filename string) ([]structInfo, string, error) {
fset := token.NewFileSet()
node, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
if err != nil {
return nil, "", err
}
var structs []structInfo
packageName := node.Name.Name
// Build import map: package name -> import path
fileImports := make(map[string]string)
for _, imp := range node.Imports {
path := strings.Trim(imp.Path.Value, `"`)
var name string
if imp.Name != nil {
name = imp.Name.Name
} else {
// Extract package name from path (last component)
parts := strings.Split(path, "/")
name = parts[len(parts)-1]
}
fileImports[name] = path
}
// First pass: collect all GenDecls with their doc comments
declMap := make(map[*ast.TypeSpec]*ast.CommentGroup)
ast.Inspect(node, func(n ast.Node) bool {
if gd, ok := n.(*ast.GenDecl); ok {
for _, spec := range gd.Specs {
if ts, ok := spec.(*ast.TypeSpec); ok {
declMap[ts] = gd.Doc
}
}
}
return true
})
// Second pass: process type specs
ast.Inspect(node, func(n ast.Node) bool {
// Look for type declarations
typeSpec, ok := n.(*ast.TypeSpec)
if !ok {
return true
}
// Check if it's a struct type
structType, ok := typeSpec.Type.(*ast.StructType)
if !ok {
return true
}
// Get the doc comment from our map
doc := declMap[typeSpec]
if !hasLensAnnotation(doc) {
return true
}
// Extract field information and collect imports
var fields []fieldInfo
structImports := make(map[string]string)
for _, field := range structType.Fields.List {
if len(field.Names) == 0 {
// Embedded field, skip for now
continue
}
for _, name := range field.Names {
// Only export lenses for exported fields
if name.IsExported() {
typeName := getTypeName(field.Type)
isOptional := false
baseType := typeName
isComparable := false
// Check if field is optional:
// 1. Pointer types are always optional
// 2. Non-pointer types with json omitempty tag are optional
if isPointerType(field.Type) {
isOptional = true
// Strip leading * for base type
baseType = strings.TrimPrefix(typeName, "*")
} else if hasOmitEmpty(field.Tag) {
// Non-pointer type with omitempty is also optional
isOptional = true
}
// Check if the type is comparable (for non-optional fields)
// For optional fields, we don't need to check since they use LensO
isComparable = isComparableType(field.Type)
// Extract imports from this field's type
fieldImports := make(map[string]string)
extractImports(field.Type, fieldImports)
// Resolve package names to full import paths
for pkgName := range fieldImports {
if importPath, ok := fileImports[pkgName]; ok {
structImports[importPath] = pkgName
}
}
fields = append(fields, fieldInfo{
Name: name.Name,
TypeName: typeName,
BaseType: baseType,
IsOptional: isOptional,
IsComparable: isComparable,
})
}
}
}
if len(fields) > 0 {
structs = append(structs, structInfo{
Name: typeSpec.Name.Name,
Fields: fields,
Imports: structImports,
})
}
return true
})
return structs, packageName, nil
}
// generateLensHelpers scans a directory for Go files and generates lens code
func generateLensHelpers(dir, filename string, verbose bool) error {
// Get absolute path
absDir, err := filepath.Abs(dir)
if err != nil {
return err
}
if verbose {
log.Printf("Scanning directory: %s", absDir)
}
// Find all Go files in the directory
files, err := filepath.Glob(filepath.Join(absDir, "*.go"))
if err != nil {
return err
}
if verbose {
log.Printf("Found %d Go files", len(files))
}
// Parse all files and collect structs
var allStructs []structInfo
var packageName string
for _, file := range files {
// Skip generated files and test files
if strings.HasSuffix(file, "_test.go") || strings.Contains(file, "gen.go") {
if verbose {
log.Printf("Skipping file: %s", filepath.Base(file))
}
continue
}
if verbose {
log.Printf("Parsing file: %s", filepath.Base(file))
}
structs, pkg, err := parseFile(file)
if err != nil {
log.Printf("Warning: failed to parse %s: %v", file, err)
continue
}
if verbose && len(structs) > 0 {
log.Printf("Found %d annotated struct(s) in %s", len(structs), filepath.Base(file))
for _, s := range structs {
log.Printf(" - %s (%d fields)", s.Name, len(s.Fields))
}
}
if packageName == "" {
packageName = pkg
}
allStructs = append(allStructs, structs...)
}
if len(allStructs) == 0 {
log.Printf("No structs with %s annotation found in %s", lensAnnotation, absDir)
return nil
}
// Collect all unique imports from all structs
allImports := make(map[string]string) // import path -> alias
for _, s := range allStructs {
for importPath, alias := range s.Imports {
allImports[importPath] = alias
}
}
// Create output file
outPath := filepath.Join(absDir, filename)
f, err := os.Create(filepath.Clean(outPath))
if err != nil {
return err
}
defer f.Close()
log.Printf("Generating lens code in [%s] for package [%s] with [%d] structs ...", outPath, packageName, len(allStructs))
// Write header
writePackage(f, packageName)
// Write imports
f.WriteString("import (\n")
// Standard fp-go imports always needed
f.WriteString("\tL \"github.com/IBM/fp-go/v2/optics/lens\"\n")
f.WriteString("\tLO \"github.com/IBM/fp-go/v2/optics/lens/option\"\n")
f.WriteString("\tO \"github.com/IBM/fp-go/v2/option\"\n")
f.WriteString("\tI \"github.com/IBM/fp-go/v2/optics/iso/option\"\n")
// Add additional imports collected from field types
for importPath, alias := range allImports {
f.WriteString("\t" + alias + " \"" + importPath + "\"\n")
}
f.WriteString(")\n")
// Generate lens code for each struct using templates
for _, s := range allStructs {
var buf bytes.Buffer
// Generate struct type
if err := structTmpl.Execute(&buf, s); err != nil {
return err
}
// Generate constructor
if err := constructorTmpl.Execute(&buf, s); err != nil {
return err
}
// Write to file
if _, err := f.Write(buf.Bytes()); err != nil {
return err
}
}
return nil
}
// LensCommand creates the CLI command for lens generation
func LensCommand() *C.Command {
return &C.Command{
Name: "lens",
Usage: "generate lens code for annotated structs",
Description: "Scans Go files for structs annotated with 'fp-go:Lens' and generates lens types. Pointer types and non-pointer types with json omitempty tag generate LensO (optional lens).",
Flags: []C.Flag{
flagLensDir,
flagFilename,
flagVerbose,
},
Action: func(ctx *C.Context) error {
return generateLensHelpers(
ctx.String(keyLensDir),
ctx.String(keyFilename),
ctx.Bool(keyVerbose),
)
},
}
}

749
v2/cli/lens_test.go Normal file
View File

@@ -0,0 +1,749 @@
// 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 cli
import (
"bytes"
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestHasLensAnnotation(t *testing.T) {
tests := []struct {
name string
comment string
expected bool
}{
{
name: "has annotation",
comment: "// fp-go:Lens",
expected: true,
},
{
name: "has annotation with other text",
comment: "// This is a struct with fp-go:Lens annotation",
expected: true,
},
{
name: "no annotation",
comment: "// This is just a regular comment",
expected: false,
},
{
name: "nil comment",
comment: "",
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var doc *ast.CommentGroup
if tt.comment != "" {
doc = &ast.CommentGroup{
List: []*ast.Comment{
{Text: tt.comment},
},
}
}
result := hasLensAnnotation(doc)
assert.Equal(t, tt.expected, result)
})
}
}
func TestGetTypeName(t *testing.T) {
tests := []struct {
name string
code string
expected string
}{
{
name: "simple type",
code: "type T struct { F string }",
expected: "string",
},
{
name: "pointer type",
code: "type T struct { F *string }",
expected: "*string",
},
{
name: "slice type",
code: "type T struct { F []int }",
expected: "[]int",
},
{
name: "map type",
code: "type T struct { F map[string]int }",
expected: "map[string]int",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "", "package test\n"+tt.code, 0)
require.NoError(t, err)
var fieldType ast.Expr
ast.Inspect(file, func(n ast.Node) bool {
if field, ok := n.(*ast.Field); ok && len(field.Names) > 0 {
fieldType = field.Type
return false
}
return true
})
require.NotNil(t, fieldType)
result := getTypeName(fieldType)
assert.Equal(t, tt.expected, result)
})
}
}
func TestIsPointerType(t *testing.T) {
tests := []struct {
name string
code string
expected bool
}{
{
name: "pointer type",
code: "type T struct { F *string }",
expected: true,
},
{
name: "non-pointer type",
code: "type T struct { F string }",
expected: false,
},
{
name: "slice type",
code: "type T struct { F []string }",
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "", "package test\n"+tt.code, 0)
require.NoError(t, err)
var fieldType ast.Expr
ast.Inspect(file, func(n ast.Node) bool {
if field, ok := n.(*ast.Field); ok && len(field.Names) > 0 {
fieldType = field.Type
return false
}
return true
})
require.NotNil(t, fieldType)
result := isPointerType(fieldType)
assert.Equal(t, tt.expected, result)
})
}
}
func TestIsComparableType(t *testing.T) {
tests := []struct {
name string
code string
expected bool
}{
{
name: "basic type - string",
code: "type T struct { F string }",
expected: true,
},
{
name: "basic type - int",
code: "type T struct { F int }",
expected: true,
},
{
name: "basic type - bool",
code: "type T struct { F bool }",
expected: true,
},
{
name: "pointer type",
code: "type T struct { F *string }",
expected: true,
},
{
name: "slice type - not comparable",
code: "type T struct { F []string }",
expected: false,
},
{
name: "map type - not comparable",
code: "type T struct { F map[string]int }",
expected: false,
},
{
name: "array type - comparable if element is",
code: "type T struct { F [5]int }",
expected: true,
},
{
name: "interface type",
code: "type T struct { F interface{} }",
expected: true,
},
{
name: "channel type",
code: "type T struct { F chan int }",
expected: true,
},
{
name: "function type - not comparable",
code: "type T struct { F func() }",
expected: false,
},
{
name: "struct literal - conservatively not comparable",
code: "type T struct { F struct{ X int } }",
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "", "package test\n"+tt.code, 0)
require.NoError(t, err)
var fieldType ast.Expr
ast.Inspect(file, func(n ast.Node) bool {
if field, ok := n.(*ast.Field); ok && len(field.Names) > 0 {
fieldType = field.Type
return false
}
return true
})
require.NotNil(t, fieldType)
result := isComparableType(fieldType)
assert.Equal(t, tt.expected, result)
})
}
}
func TestHasOmitEmpty(t *testing.T) {
tests := []struct {
name string
tag string
expected bool
}{
{
name: "has omitempty",
tag: "`json:\"field,omitempty\"`",
expected: true,
},
{
name: "has omitempty with other options",
tag: "`json:\"field,omitempty,string\"`",
expected: true,
},
{
name: "no omitempty",
tag: "`json:\"field\"`",
expected: false,
},
{
name: "no tag",
tag: "",
expected: false,
},
{
name: "different tag",
tag: "`xml:\"field\"`",
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var tag *ast.BasicLit
if tt.tag != "" {
tag = &ast.BasicLit{
Value: tt.tag,
}
}
result := hasOmitEmpty(tag)
assert.Equal(t, tt.expected, result)
})
}
}
func TestParseFile(t *testing.T) {
// Create a temporary test file
tmpDir := t.TempDir()
testFile := filepath.Join(tmpDir, "test.go")
testCode := `package testpkg
// fp-go:Lens
type Person struct {
Name string
Age int
Phone *string
}
// fp-go:Lens
type Address struct {
Street string
City string
}
// Not annotated
type Other struct {
Field string
}
`
err := os.WriteFile(testFile, []byte(testCode), 0644)
require.NoError(t, err)
// Parse the file
structs, pkg, err := parseFile(testFile)
require.NoError(t, err)
// Verify results
assert.Equal(t, "testpkg", pkg)
assert.Len(t, structs, 2)
// Check Person struct
person := structs[0]
assert.Equal(t, "Person", person.Name)
assert.Len(t, person.Fields, 3)
assert.Equal(t, "Name", person.Fields[0].Name)
assert.Equal(t, "string", person.Fields[0].TypeName)
assert.False(t, person.Fields[0].IsOptional)
assert.Equal(t, "Age", person.Fields[1].Name)
assert.Equal(t, "int", person.Fields[1].TypeName)
assert.False(t, person.Fields[1].IsOptional)
assert.Equal(t, "Phone", person.Fields[2].Name)
assert.Equal(t, "*string", person.Fields[2].TypeName)
assert.True(t, person.Fields[2].IsOptional)
// Check Address struct
address := structs[1]
assert.Equal(t, "Address", address.Name)
assert.Len(t, address.Fields, 2)
assert.Equal(t, "Street", address.Fields[0].Name)
assert.Equal(t, "City", address.Fields[1].Name)
}
func TestParseFileWithOmitEmpty(t *testing.T) {
// Create a temporary test file
tmpDir := t.TempDir()
testFile := filepath.Join(tmpDir, "test.go")
testCode := `package testpkg
// fp-go:Lens
type Config struct {
Name string
Value string ` + "`json:\"value,omitempty\"`" + `
Count int ` + "`json:\",omitempty\"`" + `
Optional *string ` + "`json:\"optional,omitempty\"`" + `
Required int ` + "`json:\"required\"`" + `
}
`
err := os.WriteFile(testFile, []byte(testCode), 0644)
require.NoError(t, err)
// Parse the file
structs, pkg, err := parseFile(testFile)
require.NoError(t, err)
// Verify results
assert.Equal(t, "testpkg", pkg)
assert.Len(t, structs, 1)
// Check Config struct
config := structs[0]
assert.Equal(t, "Config", config.Name)
assert.Len(t, config.Fields, 5)
// Name - no tag, not optional
assert.Equal(t, "Name", config.Fields[0].Name)
assert.Equal(t, "string", config.Fields[0].TypeName)
assert.False(t, config.Fields[0].IsOptional)
// Value - has omitempty, should be optional
assert.Equal(t, "Value", config.Fields[1].Name)
assert.Equal(t, "string", config.Fields[1].TypeName)
assert.True(t, config.Fields[1].IsOptional, "Value field with omitempty should be optional")
// Count - has omitempty (no field name in tag), should be optional
assert.Equal(t, "Count", config.Fields[2].Name)
assert.Equal(t, "int", config.Fields[2].TypeName)
assert.True(t, config.Fields[2].IsOptional, "Count field with omitempty should be optional")
// Optional - pointer with omitempty, should be optional
assert.Equal(t, "Optional", config.Fields[3].Name)
assert.Equal(t, "*string", config.Fields[3].TypeName)
assert.True(t, config.Fields[3].IsOptional)
// Required - has json tag but no omitempty, not optional
assert.Equal(t, "Required", config.Fields[4].Name)
assert.Equal(t, "int", config.Fields[4].TypeName)
assert.False(t, config.Fields[4].IsOptional, "Required field without omitempty should not be optional")
}
func TestParseFileWithComparableTypes(t *testing.T) {
// Create a temporary test file
tmpDir := t.TempDir()
testFile := filepath.Join(tmpDir, "test.go")
testCode := `package testpkg
// fp-go:Lens
type TypeTest struct {
Name string
Age int
Pointer *string
Slice []string
Map map[string]int
Channel chan int
}
`
err := os.WriteFile(testFile, []byte(testCode), 0644)
require.NoError(t, err)
// Parse the file
structs, pkg, err := parseFile(testFile)
require.NoError(t, err)
// Verify results
assert.Equal(t, "testpkg", pkg)
assert.Len(t, structs, 1)
// Check TypeTest struct
typeTest := structs[0]
assert.Equal(t, "TypeTest", typeTest.Name)
assert.Len(t, typeTest.Fields, 6)
// Name - string is comparable
assert.Equal(t, "Name", typeTest.Fields[0].Name)
assert.Equal(t, "string", typeTest.Fields[0].TypeName)
assert.False(t, typeTest.Fields[0].IsOptional)
assert.True(t, typeTest.Fields[0].IsComparable, "string should be comparable")
// Age - int is comparable
assert.Equal(t, "Age", typeTest.Fields[1].Name)
assert.Equal(t, "int", typeTest.Fields[1].TypeName)
assert.False(t, typeTest.Fields[1].IsOptional)
assert.True(t, typeTest.Fields[1].IsComparable, "int should be comparable")
// Pointer - pointer is optional, IsComparable not checked for optional fields
assert.Equal(t, "Pointer", typeTest.Fields[2].Name)
assert.Equal(t, "*string", typeTest.Fields[2].TypeName)
assert.True(t, typeTest.Fields[2].IsOptional)
// Slice - not comparable
assert.Equal(t, "Slice", typeTest.Fields[3].Name)
assert.Equal(t, "[]string", typeTest.Fields[3].TypeName)
assert.False(t, typeTest.Fields[3].IsOptional)
assert.False(t, typeTest.Fields[3].IsComparable, "slice should not be comparable")
// Map - not comparable
assert.Equal(t, "Map", typeTest.Fields[4].Name)
assert.Equal(t, "map[string]int", typeTest.Fields[4].TypeName)
assert.False(t, typeTest.Fields[4].IsOptional)
assert.False(t, typeTest.Fields[4].IsComparable, "map should not be comparable")
// Channel - comparable (note: getTypeName returns "any" for channel types, but isComparableType correctly identifies them)
assert.Equal(t, "Channel", typeTest.Fields[5].Name)
assert.Equal(t, "any", typeTest.Fields[5].TypeName) // getTypeName doesn't handle chan types specifically
assert.False(t, typeTest.Fields[5].IsOptional)
assert.True(t, typeTest.Fields[5].IsComparable, "channel should be comparable")
}
func TestLensRefTemplatesWithComparable(t *testing.T) {
s := structInfo{
Name: "TestStruct",
Fields: []fieldInfo{
{Name: "Name", TypeName: "string", IsOptional: false, IsComparable: true},
{Name: "Age", TypeName: "int", IsOptional: false, IsComparable: true},
{Name: "Data", TypeName: "[]byte", IsOptional: false, IsComparable: false},
{Name: "Pointer", TypeName: "*string", IsOptional: true, IsComparable: false},
},
}
// Test constructor template for RefLenses
var constructorBuf bytes.Buffer
err := constructorTmpl.Execute(&constructorBuf, s)
require.NoError(t, err)
constructorStr := constructorBuf.String()
// Check that MakeLensStrict is used for comparable types in RefLenses
assert.Contains(t, constructorStr, "func MakeTestStructRefLenses() TestStructRefLenses")
// Name field - comparable, should use MakeLensStrict
assert.Contains(t, constructorStr, "Name: L.MakeLensStrict(",
"comparable field Name should use MakeLensStrict in RefLenses")
// Age field - comparable, should use MakeLensStrict
assert.Contains(t, constructorStr, "Age: L.MakeLensStrict(",
"comparable field Age should use MakeLensStrict in RefLenses")
// Data field - not comparable, should use MakeLensRef
assert.Contains(t, constructorStr, "Data: L.MakeLensRef(",
"non-comparable field Data should use MakeLensRef in RefLenses")
}
func TestGenerateLensHelpersWithComparable(t *testing.T) {
// Create a temporary directory with test files
tmpDir := t.TempDir()
testCode := `package testpkg
// fp-go:Lens
type TestStruct struct {
Name string
Count int
Data []byte
}
`
testFile := filepath.Join(tmpDir, "test.go")
err := os.WriteFile(testFile, []byte(testCode), 0644)
require.NoError(t, err)
// Generate lens code
outputFile := "gen.go"
err = generateLensHelpers(tmpDir, outputFile, false)
require.NoError(t, err)
// Verify the generated file exists
genPath := filepath.Join(tmpDir, outputFile)
_, err = os.Stat(genPath)
require.NoError(t, err)
// Read and verify the generated content
content, err := os.ReadFile(genPath)
require.NoError(t, err)
contentStr := string(content)
// Check for expected content in RefLenses
assert.Contains(t, contentStr, "MakeTestStructRefLenses")
// Name and Count are comparable, should use MakeLensStrict
assert.Contains(t, contentStr, "L.MakeLensStrict",
"comparable fields should use MakeLensStrict in RefLenses")
// Data is not comparable (slice), should use MakeLensRef
assert.Contains(t, contentStr, "L.MakeLensRef",
"non-comparable fields should use MakeLensRef in RefLenses")
// Verify the pattern appears for Name field (comparable)
namePattern := "Name: L.MakeLensStrict("
assert.Contains(t, contentStr, namePattern,
"Name field should use MakeLensStrict")
// Verify the pattern appears for Data field (not comparable)
dataPattern := "Data: L.MakeLensRef("
assert.Contains(t, contentStr, dataPattern,
"Data field should use MakeLensRef")
}
func TestGenerateLensHelpers(t *testing.T) {
// Create a temporary directory with test files
tmpDir := t.TempDir()
testCode := `package testpkg
// fp-go:Lens
type TestStruct struct {
Name string
Value *int
}
`
testFile := filepath.Join(tmpDir, "test.go")
err := os.WriteFile(testFile, []byte(testCode), 0644)
require.NoError(t, err)
// Generate lens code
outputFile := "gen.go"
err = generateLensHelpers(tmpDir, outputFile, false)
require.NoError(t, err)
// Verify the generated file exists
genPath := filepath.Join(tmpDir, outputFile)
_, err = os.Stat(genPath)
require.NoError(t, err)
// Read and verify the generated content
content, err := os.ReadFile(genPath)
require.NoError(t, err)
contentStr := string(content)
// Check for expected content
assert.Contains(t, contentStr, "package testpkg")
assert.Contains(t, contentStr, "Code generated by go generate")
assert.Contains(t, contentStr, "TestStructLens")
assert.Contains(t, contentStr, "MakeTestStructLens")
assert.Contains(t, contentStr, "L.Lens[TestStruct, string]")
assert.Contains(t, contentStr, "LO.LensO[TestStruct, *int]")
assert.Contains(t, contentStr, "I.FromZero")
}
func TestGenerateLensHelpersNoAnnotations(t *testing.T) {
// Create a temporary directory with test files
tmpDir := t.TempDir()
testCode := `package testpkg
// No annotation
type TestStruct struct {
Name string
}
`
testFile := filepath.Join(tmpDir, "test.go")
err := os.WriteFile(testFile, []byte(testCode), 0644)
require.NoError(t, err)
// Generate lens code (should not create file)
outputFile := "gen.go"
err = generateLensHelpers(tmpDir, outputFile, false)
require.NoError(t, err)
// Verify the generated file does not exist
genPath := filepath.Join(tmpDir, outputFile)
_, err = os.Stat(genPath)
assert.True(t, os.IsNotExist(err))
}
func TestLensTemplates(t *testing.T) {
s := structInfo{
Name: "TestStruct",
Fields: []fieldInfo{
{Name: "Name", TypeName: "string", IsOptional: false},
{Name: "Value", TypeName: "*int", IsOptional: true},
},
}
// Test struct template
var structBuf bytes.Buffer
err := structTmpl.Execute(&structBuf, s)
require.NoError(t, err)
structStr := structBuf.String()
assert.Contains(t, structStr, "type TestStructLenses struct")
assert.Contains(t, structStr, "Name L.Lens[TestStruct, string]")
assert.Contains(t, structStr, "Value LO.LensO[TestStruct, *int]")
// Test constructor template
var constructorBuf bytes.Buffer
err = constructorTmpl.Execute(&constructorBuf, s)
require.NoError(t, err)
constructorStr := constructorBuf.String()
assert.Contains(t, constructorStr, "func MakeTestStructLenses() TestStructLenses")
assert.Contains(t, constructorStr, "return TestStructLenses{")
assert.Contains(t, constructorStr, "Name: L.MakeLens(")
assert.Contains(t, constructorStr, "Value: L.MakeLens(")
assert.Contains(t, constructorStr, "I.FromZero")
}
func TestLensTemplatesWithOmitEmpty(t *testing.T) {
s := structInfo{
Name: "ConfigStruct",
Fields: []fieldInfo{
{Name: "Name", TypeName: "string", IsOptional: false},
{Name: "Value", TypeName: "string", IsOptional: true}, // non-pointer with omitempty
{Name: "Count", TypeName: "int", IsOptional: true}, // non-pointer with omitempty
{Name: "Pointer", TypeName: "*string", IsOptional: true}, // pointer
},
}
// Test struct template
var structBuf bytes.Buffer
err := structTmpl.Execute(&structBuf, s)
require.NoError(t, err)
structStr := structBuf.String()
assert.Contains(t, structStr, "type ConfigStructLenses struct")
assert.Contains(t, structStr, "Name L.Lens[ConfigStruct, string]")
assert.Contains(t, structStr, "Value LO.LensO[ConfigStruct, string]", "non-pointer with omitempty should use LensO")
assert.Contains(t, structStr, "Count LO.LensO[ConfigStruct, int]", "non-pointer with omitempty should use LensO")
assert.Contains(t, structStr, "Pointer LO.LensO[ConfigStruct, *string]")
// Test constructor template
var constructorBuf bytes.Buffer
err = constructorTmpl.Execute(&constructorBuf, s)
require.NoError(t, err)
constructorStr := constructorBuf.String()
assert.Contains(t, constructorStr, "func MakeConfigStructLenses() ConfigStructLenses")
assert.Contains(t, constructorStr, "isoValue := I.FromZero[string]()")
assert.Contains(t, constructorStr, "isoCount := I.FromZero[int]()")
assert.Contains(t, constructorStr, "isoPointer := I.FromZero[*string]()")
}
func TestLensCommandFlags(t *testing.T) {
cmd := LensCommand()
assert.Equal(t, "lens", cmd.Name)
assert.Equal(t, "generate lens code for annotated structs", cmd.Usage)
assert.Contains(t, strings.ToLower(cmd.Description), "fp-go:lens")
assert.Contains(t, strings.ToLower(cmd.Description), "lenso")
// Check flags
assert.Len(t, cmd.Flags, 3)
var hasDir, hasFilename, hasVerbose bool
for _, flag := range cmd.Flags {
switch flag.Names()[0] {
case "dir":
hasDir = true
case "filename":
hasFilename = true
case "verbose":
hasVerbose = true
}
}
assert.True(t, hasDir, "should have dir flag")
assert.True(t, hasFilename, "should have filename flag")
assert.True(t, hasVerbose, "should have verbose flag")
}

View File

@@ -27,7 +27,7 @@ import (
func TestMap(t *testing.T) { func TestMap(t *testing.T) {
fa := Make[string, int]("foo") fa := Make[string, int]("foo")
assert.Equal(t, fa, F.Pipe1(fa, Map[string, int](utils.Double))) assert.Equal(t, fa, F.Pipe1(fa, Map[string](utils.Double)))
} }
func TestOf(t *testing.T) { func TestOf(t *testing.T) {

View File

@@ -13,20 +13,19 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package ioeither package ioresult
import ( import (
"context" "context"
"github.com/IBM/fp-go/v2/either" "github.com/IBM/fp-go/v2/result"
IOE "github.com/IBM/fp-go/v2/ioeither"
) )
// withContext wraps an existing IOEither and performs a context check for cancellation before delegating // withContext wraps an existing IOEither and performs a context check for cancellation before delegating
func WithContext[A any](ctx context.Context, ma IOE.IOEither[error, A]) IOE.IOEither[error, A] { func WithContext[A any](ctx context.Context, ma IOResult[A]) IOResult[A] {
return func() either.Either[error, A] { return func() Result[A] {
if err := context.Cause(ctx); err != nil { if err := context.Cause(ctx); err != nil {
return either.Left[A](err) return result.Left[A](err)
} }
return ma() return ma()
} }

View File

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

View File

@@ -1,68 +0,0 @@
// 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 readereither
import (
"context"
G "github.com/IBM/fp-go/v2/readereither/generic"
)
// Bind creates an empty context of type [S] to be used with the [Bind] operation
func Do[S any](
empty S,
) ReaderEither[S] {
return G.Do[ReaderEither[S], context.Context, error, S](empty)
}
// Bind attaches the result of a computation to a context [S1] to produce a context [S2]
func Bind[S1, S2, T any](
setter func(T) func(S1) S2,
f func(S1) ReaderEither[T],
) func(ReaderEither[S1]) ReaderEither[S2] {
return G.Bind[ReaderEither[S1], ReaderEither[S2], ReaderEither[T], context.Context, error, S1, S2, T](setter, f)
}
// Let attaches the result of a computation to a context [S1] to produce a context [S2]
func Let[S1, S2, T any](
setter func(T) func(S1) S2,
f func(S1) T,
) func(ReaderEither[S1]) ReaderEither[S2] {
return G.Let[ReaderEither[S1], ReaderEither[S2], context.Context, error, S1, S2, T](setter, f)
}
// LetTo attaches the a value to a context [S1] to produce a context [S2]
func LetTo[S1, S2, T any](
setter func(T) func(S1) S2,
b T,
) func(ReaderEither[S1]) ReaderEither[S2] {
return G.LetTo[ReaderEither[S1], ReaderEither[S2], context.Context, error, S1, S2, T](setter, b)
}
// BindTo initializes a new state [S1] from a value [T]
func BindTo[S1, T any](
setter func(T) S1,
) func(ReaderEither[T]) ReaderEither[S1] {
return G.BindTo[ReaderEither[S1], ReaderEither[T], context.Context, error, S1, T](setter)
}
// ApS attaches a value to a context [S1] to produce a context [S2] by considering the context and the value concurrently
func ApS[S1, S2, T any](
setter func(T) func(S1) S2,
fa ReaderEither[T],
) func(ReaderEither[S1]) ReaderEither[S2] {
return G.ApS[ReaderEither[S1], ReaderEither[S2], ReaderEither[T], context.Context, error, S1, S2, T](setter, fa)
}

View File

@@ -1,89 +0,0 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package readerioeither
import (
"github.com/IBM/fp-go/v2/internal/apply"
"github.com/IBM/fp-go/v2/internal/chain"
"github.com/IBM/fp-go/v2/internal/functor"
)
// Bind creates an empty context of type [S] to be used with the [Bind] operation
func Do[S any](
empty S,
) ReaderIOEither[S] {
return Of(empty)
}
// Bind attaches the result of a computation to a context [S1] to produce a context [S2]
func Bind[S1, S2, T any](
setter func(T) func(S1) S2,
f func(S1) ReaderIOEither[T],
) func(ReaderIOEither[S1]) ReaderIOEither[S2] {
return chain.Bind(
Chain[S1, S2],
Map[T, S2],
setter,
f,
)
}
// Let attaches the result of a computation to a context [S1] to produce a context [S2]
func Let[S1, S2, T any](
setter func(T) func(S1) S2,
f func(S1) T,
) func(ReaderIOEither[S1]) ReaderIOEither[S2] {
return functor.Let(
Map[S1, S2],
setter,
f,
)
}
// LetTo attaches the a value to a context [S1] to produce a context [S2]
func LetTo[S1, S2, T any](
setter func(T) func(S1) S2,
b T,
) func(ReaderIOEither[S1]) ReaderIOEither[S2] {
return functor.LetTo(
Map[S1, S2],
setter,
b,
)
}
// BindTo initializes a new state [S1] from a value [T]
func BindTo[S1, T any](
setter func(T) S1,
) Operator[T, S1] {
return chain.BindTo(
Map[T, S1],
setter,
)
}
// ApS attaches a value to a context [S1] to produce a context [S2] by considering the context and the value concurrently
func ApS[S1, S2, T any](
setter func(T) func(S1) S2,
fa ReaderIOEither[T],
) func(ReaderIOEither[S1]) ReaderIOEither[S2] {
return apply.ApS(
Ap[S2, T],
Map[S1, func(T) S2],
setter,
fa,
)
}

View File

@@ -1,251 +0,0 @@
mode: set
github.com/IBM/fp-go/v2/context/readerioeither/bind.go:27.21,29.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/bind.go:35.47,42.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/bind.go:48.47,54.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/bind.go:60.47,66.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/bind.go:71.46,76.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/bind.go:82.47,89.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/bracket.go:33.21,44.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/cancel.go:35.65,36.47 1 1
github.com/IBM/fp-go/v2/context/readerioeither/cancel.go:36.47,37.44 1 1
github.com/IBM/fp-go/v2/context/readerioeither/cancel.go:37.44,39.4 1 1
github.com/IBM/fp-go/v2/context/readerioeither/cancel.go:40.3,40.40 1 1
github.com/IBM/fp-go/v2/context/readerioeither/eq.go:42.84,44.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:18.91,20.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:24.93,26.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:30.101,32.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:36.103,38.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:43.36,48.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:53.36,58.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:63.36,68.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:71.98,76.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:79.101,84.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:87.101,92.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:95.129,96.68 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:96.68,102.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:106.132,107.68 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:107.68,113.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:117.132,118.68 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:118.68,124.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:129.113,131.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:135.115,137.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:143.40,150.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:156.40,163.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:169.40,176.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:179.126,185.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:188.129,194.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:197.129,203.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:206.185,207.76 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:207.76,215.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:219.188,220.76 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:220.76,228.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:232.188,233.76 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:233.76,241.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:246.125,248.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:252.127,254.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:261.44,270.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:277.44,286.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:293.44,302.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:305.154,312.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:315.157,322.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:325.157,332.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:335.241,336.84 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:336.84,346.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:350.244,351.84 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:351.84,361.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:365.244,366.84 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:366.84,376.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:381.137,383.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:387.139,389.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:397.48,408.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:416.48,427.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:435.48,446.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:449.182,457.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:460.185,468.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:471.185,479.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:482.297,483.92 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:483.92,495.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:499.300,500.92 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:500.92,512.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:516.300,517.92 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:517.92,529.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:534.149,536.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:540.151,542.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:551.52,564.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:573.52,586.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:595.52,608.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:611.210,620.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:623.213,632.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:635.213,644.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:647.353,648.100 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:648.100,662.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:666.356,667.100 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:667.100,681.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:685.356,686.100 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:686.100,700.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:705.161,707.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:711.163,713.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:723.56,738.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:748.56,763.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:773.56,788.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:791.238,801.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:804.241,814.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:817.241,827.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:830.409,831.108 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:831.108,847.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:851.412,852.108 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:852.108,868.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:872.412,873.108 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:873.108,889.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:894.173,896.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:900.175,902.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:913.60,930.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:941.60,958.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:969.60,986.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:989.266,1000.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1003.269,1014.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1017.269,1028.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1031.465,1032.116 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1032.116,1050.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1054.468,1055.116 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1055.116,1073.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1077.468,1078.116 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1078.116,1096.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1101.185,1103.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1107.187,1109.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1121.64,1140.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1152.64,1171.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1183.64,1202.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1205.294,1217.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1220.297,1232.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1235.297,1247.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1250.521,1251.124 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1251.124,1271.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1275.524,1276.124 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1276.124,1296.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1300.524,1301.124 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1301.124,1321.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1326.197,1328.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1332.199,1334.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1347.68,1368.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1381.68,1402.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1415.68,1436.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1439.322,1452.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1455.325,1468.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1471.325,1484.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1487.577,1488.132 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1488.132,1510.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1514.580,1515.132 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1515.132,1537.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1541.580,1542.132 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1542.132,1564.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1569.210,1571.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1575.212,1577.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1591.74,1614.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1628.74,1651.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1665.74,1688.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1691.356,1705.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1708.359,1722.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1725.359,1739.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1742.645,1743.144 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1743.144,1767.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1771.648,1772.144 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1772.144,1796.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1800.648,1801.144 1 0
github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1801.144,1825.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/monoid.go:36.61,43.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/monoid.go:52.64,59.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/monoid.go:68.64,75.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/monoid.go:85.61,93.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/monoid.go:103.63,108.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:42.55,44.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:52.45,54.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:62.42,64.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:74.78,76.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:85.75,87.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:97.72,99.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:108.69,110.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:120.96,122.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:131.93,133.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:143.101,145.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:154.71,156.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:165.39,167.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:169.93,173.56 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:173.56,174.32 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:174.32,174.47 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:189.98,194.47 3 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:194.47,196.44 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:196.44,198.4 1 0
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:200.3,200.27 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:200.27,202.45 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:202.45,204.5 1 0
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:207.4,213.47 5 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:227.95,229.17 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:229.17,231.3 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:232.2,232.28 1 0
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:243.98,245.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:254.91,256.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:265.94,267.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:276.94,278.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:288.95,290.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:299.73,301.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:307.44,309.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:319.95,321.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:330.95,332.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:342.100,344.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:353.100,355.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:364.116,366.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:375.75,377.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:386.47,388.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:398.51,400.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:406.39,407.47 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:407.47,408.27 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:408.27,411.4 2 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:423.87,425.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:434.87,436.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:446.92,448.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:457.92,459.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:468.115,470.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:479.85,480.54 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:480.54,481.48 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:481.48,482.28 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:482.28,487.12 3 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:488.30,489.22 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:490.23,491.47 1 0
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:505.59,511.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:520.66,522.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:531.83,533.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:543.97,545.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:554.64,556.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:566.62,568.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:577.78,579.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:589.80,591.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:600.76,602.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:612.136,614.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:623.91,625.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/reader.go:634.71,636.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/resource.go:58.151,63.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/semigroup.go:39.41,43.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/sync.go:46.78,54.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:31.89,39.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:48.103,56.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:65.71,67.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:75.112,83.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:92.124,100.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:108.94,110.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:120.95,128.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:137.92,145.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:148.106,156.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:165.74,167.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:170.118,178.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:181.115,189.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:192.127,200.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:203.97,205.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:215.95,223.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:232.92,240.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:243.106,251.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:260.74,262.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:265.115,273.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:276.127,284.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:287.118,295.2 1 0
github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:304.97,306.2 1 0

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +0,0 @@
mode: set
github.com/IBM/fp-go/v2/context/readerioeither/http/builder/builder.go:117.52,119.103 1 1
github.com/IBM/fp-go/v2/context/readerioeither/http/builder/builder.go:119.103,120.80 1 1
github.com/IBM/fp-go/v2/context/readerioeither/http/builder/builder.go:120.80,121.41 1 1
github.com/IBM/fp-go/v2/context/readerioeither/http/builder/builder.go:121.41,123.19 2 1
github.com/IBM/fp-go/v2/context/readerioeither/http/builder/builder.go:123.19,126.6 2 1
github.com/IBM/fp-go/v2/context/readerioeither/http/builder/builder.go:127.5,127.20 1 1
github.com/IBM/fp-go/v2/context/readerioeither/http/builder/builder.go:132.2,132.93 1 1
github.com/IBM/fp-go/v2/context/readerioeither/http/builder/builder.go:132.93,133.80 1 1
github.com/IBM/fp-go/v2/context/readerioeither/http/builder/builder.go:133.80,134.41 1 1
github.com/IBM/fp-go/v2/context/readerioeither/http/builder/builder.go:134.41,136.19 2 1
github.com/IBM/fp-go/v2/context/readerioeither/http/builder/builder.go:136.19,138.6 1 1
github.com/IBM/fp-go/v2/context/readerioeither/http/builder/builder.go:139.5,139.20 1 1
github.com/IBM/fp-go/v2/context/readerioeither/http/builder/builder.go:144.2,150.50 1 1
github.com/IBM/fp-go/v2/context/readerioeither/http/builder/builder.go:150.50,153.4 2 1

View File

@@ -1,11 +0,0 @@
mode: set
github.com/IBM/fp-go/v2/context/readerioeither/http/request.go:111.76,116.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/http/request.go:134.49,136.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/http/request.go:161.90,162.65 1 1
github.com/IBM/fp-go/v2/context/readerioeither/http/request.go:162.65,166.76 1 1
github.com/IBM/fp-go/v2/context/readerioeither/http/request.go:166.76,176.5 1 1
github.com/IBM/fp-go/v2/context/readerioeither/http/request.go:198.73,203.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/http/request.go:222.74,227.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/http/request.go:234.76,236.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/http/request.go:245.74,254.2 1 1
github.com/IBM/fp-go/v2/context/readerioeither/http/request.go:281.76,286.2 1 1

View File

@@ -1,636 +0,0 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package readerioeither
import (
"context"
"time"
"github.com/IBM/fp-go/v2/either"
"github.com/IBM/fp-go/v2/errors"
"github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/io"
"github.com/IBM/fp-go/v2/ioeither"
"github.com/IBM/fp-go/v2/readerioeither"
)
const (
// useParallel is the feature flag to control if we use the parallel or the sequential implementation of ap
useParallel = true
)
// FromEither converts an [Either] into a [ReaderIOEither].
// The resulting computation ignores the context and immediately returns the Either value.
//
// Parameters:
// - e: The Either value to lift into ReaderIOEither
//
// Returns a ReaderIOEither that produces the given Either value.
func FromEither[A any](e Either[A]) ReaderIOEither[A] {
return readerioeither.FromEither[context.Context](e)
}
// Left creates a [ReaderIOEither] that represents a failed computation with the given error.
//
// Parameters:
// - l: The error value
//
// Returns a ReaderIOEither that always fails with the given error.
func Left[A any](l error) ReaderIOEither[A] {
return readerioeither.Left[context.Context, A](l)
}
// Right creates a [ReaderIOEither] that represents a successful computation with the given value.
//
// Parameters:
// - r: The success value
//
// Returns a ReaderIOEither that always succeeds with the given value.
func Right[A any](r A) ReaderIOEither[A] {
return readerioeither.Right[context.Context, error](r)
}
// MonadMap transforms the success value of a [ReaderIOEither] using the provided function.
// If the computation fails, the error is propagated unchanged.
//
// Parameters:
// - fa: The ReaderIOEither to transform
// - f: The transformation function
//
// Returns a new ReaderIOEither with the transformed value.
func MonadMap[A, B any](fa ReaderIOEither[A], f func(A) B) ReaderIOEither[B] {
return readerioeither.MonadMap(fa, f)
}
// Map transforms the success value of a [ReaderIOEither] using the provided function.
// This is the curried version of [MonadMap], useful for composition.
//
// Parameters:
// - f: The transformation function
//
// Returns a function that transforms a ReaderIOEither.
func Map[A, B any](f func(A) B) Operator[A, B] {
return readerioeither.Map[context.Context, error](f)
}
// MonadMapTo replaces the success value of a [ReaderIOEither] with a constant value.
// If the computation fails, the error is propagated unchanged.
//
// Parameters:
// - fa: The ReaderIOEither to transform
// - b: The constant value to use
//
// Returns a new ReaderIOEither with the constant value.
func MonadMapTo[A, B any](fa ReaderIOEither[A], b B) ReaderIOEither[B] {
return readerioeither.MonadMapTo(fa, b)
}
// MapTo replaces the success value of a [ReaderIOEither] with a constant value.
// This is the curried version of [MonadMapTo].
//
// Parameters:
// - b: The constant value to use
//
// Returns a function that transforms a ReaderIOEither.
func MapTo[A, B any](b B) Operator[A, B] {
return readerioeither.MapTo[context.Context, error, A](b)
}
// MonadChain sequences two [ReaderIOEither] computations, where the second depends on the result of the first.
// If the first computation fails, the second is not executed.
//
// Parameters:
// - ma: The first ReaderIOEither
// - f: Function that produces the second ReaderIOEither based on the first's result
//
// Returns a new ReaderIOEither representing the sequenced computation.
func MonadChain[A, B any](ma ReaderIOEither[A], f func(A) ReaderIOEither[B]) ReaderIOEither[B] {
return readerioeither.MonadChain(ma, f)
}
// Chain sequences two [ReaderIOEither] computations, where the second depends on the result of the first.
// This is the curried version of [MonadChain], useful for composition.
//
// Parameters:
// - f: Function that produces the second ReaderIOEither based on the first's result
//
// Returns a function that sequences ReaderIOEither computations.
func Chain[A, B any](f func(A) ReaderIOEither[B]) Operator[A, B] {
return readerioeither.Chain(f)
}
// MonadChainFirst sequences two [ReaderIOEither] computations but returns the result of the first.
// The second computation is executed for its side effects only.
//
// Parameters:
// - ma: The first ReaderIOEither
// - f: Function that produces the second ReaderIOEither
//
// Returns a ReaderIOEither with the result of the first computation.
func MonadChainFirst[A, B any](ma ReaderIOEither[A], f func(A) ReaderIOEither[B]) ReaderIOEither[A] {
return readerioeither.MonadChainFirst(ma, f)
}
// ChainFirst sequences two [ReaderIOEither] computations but returns the result of the first.
// This is the curried version of [MonadChainFirst].
//
// Parameters:
// - f: Function that produces the second ReaderIOEither
//
// Returns a function that sequences ReaderIOEither computations.
func ChainFirst[A, B any](f func(A) ReaderIOEither[B]) Operator[A, A] {
return readerioeither.ChainFirst(f)
}
// Of creates a [ReaderIOEither] that always succeeds with the given value.
// This is the same as [Right] and represents the monadic return operation.
//
// Parameters:
// - a: The value to wrap
//
// Returns a ReaderIOEither that always succeeds with the given value.
func Of[A any](a A) ReaderIOEither[A] {
return readerioeither.Of[context.Context, error](a)
}
func withCancelCauseFunc[A any](cancel context.CancelCauseFunc, ma IOEither[A]) IOEither[A] {
return function.Pipe3(
ma,
ioeither.Swap[error, A],
ioeither.ChainFirstIOK[A](func(err error) func() any {
return io.FromImpure(func() { cancel(err) })
}),
ioeither.Swap[A, error],
)
}
// MonadApPar implements parallel applicative application for [ReaderIOEither].
// It executes both computations in parallel and creates a sub-context that will be canceled
// if either operation fails. This provides automatic cancellation propagation.
//
// Parameters:
// - fab: ReaderIOEither containing a function
// - fa: ReaderIOEither containing a value
//
// Returns a ReaderIOEither with the function applied to the value.
func MonadApPar[B, A any](fab ReaderIOEither[func(A) B], fa ReaderIOEither[A]) ReaderIOEither[B] {
// context sensitive input
cfab := WithContext(fab)
cfa := WithContext(fa)
return func(ctx context.Context) IOEither[B] {
// quick check for cancellation
if err := context.Cause(ctx); err != nil {
return ioeither.Left[B](err)
}
return func() Either[B] {
// quick check for cancellation
if err := context.Cause(ctx); err != nil {
return either.Left[B](err)
}
// create sub-contexts for fa and fab, so they can cancel one other
ctxSub, cancelSub := context.WithCancelCause(ctx)
defer cancelSub(nil) // cancel has to be called in all paths
fabIOE := withCancelCauseFunc(cancelSub, cfab(ctxSub))
faIOE := withCancelCauseFunc(cancelSub, cfa(ctxSub))
return ioeither.MonadApPar(fabIOE, faIOE)()
}
}
}
// MonadAp implements applicative application for [ReaderIOEither].
// By default, it uses parallel execution ([MonadApPar]) but can be configured to use
// sequential execution ([MonadApSeq]) via the useParallel constant.
//
// Parameters:
// - fab: ReaderIOEither containing a function
// - fa: ReaderIOEither containing a value
//
// Returns a ReaderIOEither with the function applied to the value.
func MonadAp[B, A any](fab ReaderIOEither[func(A) B], fa ReaderIOEither[A]) ReaderIOEither[B] {
// dispatch to the configured version
if useParallel {
return MonadApPar(fab, fa)
}
return MonadApSeq(fab, fa)
}
// MonadApSeq implements sequential applicative application for [ReaderIOEither].
// It executes the function computation first, then the value computation.
//
// Parameters:
// - fab: ReaderIOEither containing a function
// - fa: ReaderIOEither containing a value
//
// Returns a ReaderIOEither with the function applied to the value.
func MonadApSeq[B, A any](fab ReaderIOEither[func(A) B], fa ReaderIOEither[A]) ReaderIOEither[B] {
return readerioeither.MonadApSeq(fab, fa)
}
// Ap applies a function wrapped in a [ReaderIOEither] to a value wrapped in a ReaderIOEither.
// This is the curried version of [MonadAp], using the default execution mode.
//
// Parameters:
// - fa: ReaderIOEither containing a value
//
// Returns a function that applies a ReaderIOEither function to the value.
func Ap[B, A any](fa ReaderIOEither[A]) Operator[func(A) B, B] {
return function.Bind2nd(MonadAp[B, A], fa)
}
// ApSeq applies a function wrapped in a [ReaderIOEither] to a value sequentially.
// This is the curried version of [MonadApSeq].
//
// Parameters:
// - fa: ReaderIOEither containing a value
//
// Returns a function that applies a ReaderIOEither function to the value sequentially.
func ApSeq[B, A any](fa ReaderIOEither[A]) Operator[func(A) B, B] {
return function.Bind2nd(MonadApSeq[B, A], fa)
}
// ApPar applies a function wrapped in a [ReaderIOEither] to a value in parallel.
// This is the curried version of [MonadApPar].
//
// Parameters:
// - fa: ReaderIOEither containing a value
//
// Returns a function that applies a ReaderIOEither function to the value in parallel.
func ApPar[B, A any](fa ReaderIOEither[A]) Operator[func(A) B, B] {
return function.Bind2nd(MonadApPar[B, A], fa)
}
// FromPredicate creates a [ReaderIOEither] from a predicate function.
// If the predicate returns true, the value is wrapped in Right; otherwise, Left with the error from onFalse.
//
// Parameters:
// - pred: Predicate function to test the value
// - onFalse: Function to generate an error when predicate fails
//
// Returns a function that converts a value to ReaderIOEither based on the predicate.
func FromPredicate[A any](pred func(A) bool, onFalse func(A) error) func(A) ReaderIOEither[A] {
return readerioeither.FromPredicate[context.Context](pred, onFalse)
}
// OrElse provides an alternative [ReaderIOEither] computation if the first one fails.
// The alternative is only executed if the first computation results in a Left (error).
//
// Parameters:
// - onLeft: Function that produces an alternative ReaderIOEither from the error
//
// Returns a function that provides fallback behavior for failed computations.
func OrElse[A any](onLeft func(error) ReaderIOEither[A]) Operator[A, A] {
return readerioeither.OrElse[context.Context](onLeft)
}
// Ask returns a [ReaderIOEither] that provides access to the context.
// This is useful for accessing the [context.Context] within a computation.
//
// Returns a ReaderIOEither that produces the context.
func Ask() ReaderIOEither[context.Context] {
return readerioeither.Ask[context.Context, error]()
}
// MonadChainEitherK chains a function that returns an [Either] into a [ReaderIOEither] computation.
// This is useful for integrating pure Either-returning functions into ReaderIOEither workflows.
//
// Parameters:
// - ma: The ReaderIOEither to chain from
// - f: Function that produces an Either
//
// Returns a new ReaderIOEither with the chained computation.
func MonadChainEitherK[A, B any](ma ReaderIOEither[A], f func(A) Either[B]) ReaderIOEither[B] {
return readerioeither.MonadChainEitherK[context.Context](ma, f)
}
// ChainEitherK chains a function that returns an [Either] into a [ReaderIOEither] computation.
// This is the curried version of [MonadChainEitherK].
//
// Parameters:
// - f: Function that produces an Either
//
// Returns a function that chains the Either-returning function.
func ChainEitherK[A, B any](f func(A) Either[B]) func(ma ReaderIOEither[A]) ReaderIOEither[B] {
return readerioeither.ChainEitherK[context.Context](f)
}
// MonadChainFirstEitherK chains a function that returns an [Either] but keeps the original value.
// The Either-returning function is executed for its validation/side effects only.
//
// Parameters:
// - ma: The ReaderIOEither to chain from
// - f: Function that produces an Either
//
// Returns a ReaderIOEither with the original value if both computations succeed.
func MonadChainFirstEitherK[A, B any](ma ReaderIOEither[A], f func(A) Either[B]) ReaderIOEither[A] {
return readerioeither.MonadChainFirstEitherK[context.Context](ma, f)
}
// ChainFirstEitherK chains a function that returns an [Either] but keeps the original value.
// This is the curried version of [MonadChainFirstEitherK].
//
// Parameters:
// - f: Function that produces an Either
//
// Returns a function that chains the Either-returning function.
func ChainFirstEitherK[A, B any](f func(A) Either[B]) func(ma ReaderIOEither[A]) ReaderIOEither[A] {
return readerioeither.ChainFirstEitherK[context.Context](f)
}
// ChainOptionK chains a function that returns an [Option] into a [ReaderIOEither] computation.
// If the Option is None, the provided error function is called.
//
// Parameters:
// - onNone: Function to generate an error when Option is None
//
// Returns a function that chains Option-returning functions into ReaderIOEither.
func ChainOptionK[A, B any](onNone func() error) func(func(A) Option[B]) Operator[A, B] {
return readerioeither.ChainOptionK[context.Context, A, B](onNone)
}
// FromIOEither converts an [IOEither] into a [ReaderIOEither].
// The resulting computation ignores the context.
//
// Parameters:
// - t: The IOEither to convert
//
// Returns a ReaderIOEither that executes the IOEither.
func FromIOEither[A any](t ioeither.IOEither[error, A]) ReaderIOEither[A] {
return readerioeither.FromIOEither[context.Context](t)
}
// FromIO converts an [IO] into a [ReaderIOEither].
// The IO computation always succeeds, so it's wrapped in Right.
//
// Parameters:
// - t: The IO to convert
//
// Returns a ReaderIOEither that executes the IO and wraps the result in Right.
func FromIO[A any](t IO[A]) ReaderIOEither[A] {
return readerioeither.FromIO[context.Context, error](t)
}
// FromLazy converts a [Lazy] computation into a [ReaderIOEither].
// The Lazy computation always succeeds, so it's wrapped in Right.
// This is an alias for [FromIO] since Lazy and IO have the same structure.
//
// Parameters:
// - t: The Lazy computation to convert
//
// Returns a ReaderIOEither that executes the Lazy computation and wraps the result in Right.
func FromLazy[A any](t Lazy[A]) ReaderIOEither[A] {
return readerioeither.FromIO[context.Context, error](t)
}
// Never returns a [ReaderIOEither] that blocks indefinitely until the context is canceled.
// This is useful for creating computations that wait for external cancellation signals.
//
// Returns a ReaderIOEither that waits for context cancellation and returns the cancellation error.
func Never[A any]() ReaderIOEither[A] {
return func(ctx context.Context) IOEither[A] {
return func() Either[A] {
<-ctx.Done()
return either.Left[A](context.Cause(ctx))
}
}
}
// MonadChainIOK chains a function that returns an [IO] into a [ReaderIOEither] computation.
// The IO computation always succeeds, so it's wrapped in Right.
//
// Parameters:
// - ma: The ReaderIOEither to chain from
// - f: Function that produces an IO
//
// Returns a new ReaderIOEither with the chained IO computation.
func MonadChainIOK[A, B any](ma ReaderIOEither[A], f func(A) IO[B]) ReaderIOEither[B] {
return readerioeither.MonadChainIOK(ma, f)
}
// ChainIOK chains a function that returns an [IO] into a [ReaderIOEither] computation.
// This is the curried version of [MonadChainIOK].
//
// Parameters:
// - f: Function that produces an IO
//
// Returns a function that chains the IO-returning function.
func ChainIOK[A, B any](f func(A) IO[B]) func(ma ReaderIOEither[A]) ReaderIOEither[B] {
return readerioeither.ChainIOK[context.Context, error](f)
}
// MonadChainFirstIOK chains a function that returns an [IO] but keeps the original value.
// The IO computation is executed for its side effects only.
//
// Parameters:
// - ma: The ReaderIOEither to chain from
// - f: Function that produces an IO
//
// Returns a ReaderIOEither with the original value after executing the IO.
func MonadChainFirstIOK[A, B any](ma ReaderIOEither[A], f func(A) IO[B]) ReaderIOEither[A] {
return readerioeither.MonadChainFirstIOK(ma, f)
}
// ChainFirstIOK chains a function that returns an [IO] but keeps the original value.
// This is the curried version of [MonadChainFirstIOK].
//
// Parameters:
// - f: Function that produces an IO
//
// Returns a function that chains the IO-returning function.
func ChainFirstIOK[A, B any](f func(A) IO[B]) func(ma ReaderIOEither[A]) ReaderIOEither[A] {
return readerioeither.ChainFirstIOK[context.Context, error](f)
}
// ChainIOEitherK chains a function that returns an [IOEither] into a [ReaderIOEither] computation.
// This is useful for integrating IOEither-returning functions into ReaderIOEither workflows.
//
// Parameters:
// - f: Function that produces an IOEither
//
// Returns a function that chains the IOEither-returning function.
func ChainIOEitherK[A, B any](f func(A) ioeither.IOEither[error, B]) func(ma ReaderIOEither[A]) ReaderIOEither[B] {
return readerioeither.ChainIOEitherK[context.Context](f)
}
// Delay creates an operation that delays execution by the specified duration.
// The computation waits for either the delay to expire or the context to be canceled.
//
// Parameters:
// - delay: The duration to wait before executing the computation
//
// Returns a function that delays a ReaderIOEither computation.
func Delay[A any](delay time.Duration) func(ma ReaderIOEither[A]) ReaderIOEither[A] {
return func(ma ReaderIOEither[A]) ReaderIOEither[A] {
return func(ctx context.Context) IOEither[A] {
return func() Either[A] {
// manage the timeout
timeoutCtx, cancelTimeout := context.WithTimeout(ctx, delay)
defer cancelTimeout()
// whatever comes first
select {
case <-timeoutCtx.Done():
return ma(ctx)()
case <-ctx.Done():
return either.Left[A](context.Cause(ctx))
}
}
}
}
}
// Timer returns the current time after waiting for the specified delay.
// This is useful for creating time-based computations.
//
// Parameters:
// - delay: The duration to wait before returning the time
//
// Returns a ReaderIOEither that produces the current time after the delay.
func Timer(delay time.Duration) ReaderIOEither[time.Time] {
return function.Pipe2(
io.Now,
FromIO[time.Time],
Delay[time.Time](delay),
)
}
// Defer creates a [ReaderIOEither] by lazily generating a new computation each time it's executed.
// This is useful for creating computations that should be re-evaluated on each execution.
//
// Parameters:
// - gen: Lazy generator function that produces a ReaderIOEither
//
// Returns a ReaderIOEither that generates a fresh computation on each execution.
func Defer[A any](gen Lazy[ReaderIOEither[A]]) ReaderIOEither[A] {
return readerioeither.Defer(gen)
}
// TryCatch wraps a function that returns a tuple (value, error) into a [ReaderIOEither].
// This is the standard way to convert Go error-returning functions into ReaderIOEither.
//
// Parameters:
// - f: Function that takes a context and returns a function producing (value, error)
//
// Returns a ReaderIOEither that wraps the error-returning function.
func TryCatch[A any](f func(context.Context) func() (A, error)) ReaderIOEither[A] {
return readerioeither.TryCatch(f, errors.IdentityError)
}
// MonadAlt provides an alternative [ReaderIOEither] if the first one fails.
// The alternative is lazily evaluated only if needed.
//
// Parameters:
// - first: The primary ReaderIOEither to try
// - second: Lazy alternative ReaderIOEither to use if first fails
//
// Returns a ReaderIOEither that tries the first, then the second if first fails.
func MonadAlt[A any](first ReaderIOEither[A], second Lazy[ReaderIOEither[A]]) ReaderIOEither[A] {
return readerioeither.MonadAlt(first, second)
}
// Alt provides an alternative [ReaderIOEither] if the first one fails.
// This is the curried version of [MonadAlt].
//
// Parameters:
// - second: Lazy alternative ReaderIOEither to use if first fails
//
// Returns a function that provides fallback behavior.
func Alt[A any](second Lazy[ReaderIOEither[A]]) Operator[A, A] {
return readerioeither.Alt(second)
}
// Memoize computes the value of the provided [ReaderIOEither] monad lazily but exactly once.
// The context used to compute the value is the context of the first call, so do not use this
// method if the value has a functional dependency on the content of the context.
//
// Parameters:
// - rdr: The ReaderIOEither to memoize
//
// Returns a ReaderIOEither that caches its result after the first execution.
func Memoize[A any](rdr ReaderIOEither[A]) ReaderIOEither[A] {
return readerioeither.Memoize(rdr)
}
// Flatten converts a nested [ReaderIOEither] into a flat [ReaderIOEither].
// This is equivalent to [MonadChain] with the identity function.
//
// Parameters:
// - rdr: The nested ReaderIOEither to flatten
//
// Returns a flattened ReaderIOEither.
func Flatten[A any](rdr ReaderIOEither[ReaderIOEither[A]]) ReaderIOEither[A] {
return readerioeither.Flatten(rdr)
}
// MonadFlap applies a value to a function wrapped in a [ReaderIOEither].
// This is the reverse of [MonadAp], useful in certain composition scenarios.
//
// Parameters:
// - fab: ReaderIOEither containing a function
// - a: The value to apply to the function
//
// Returns a ReaderIOEither with the function applied to the value.
func MonadFlap[B, A any](fab ReaderIOEither[func(A) B], a A) ReaderIOEither[B] {
return readerioeither.MonadFlap(fab, a)
}
// Flap applies a value to a function wrapped in a [ReaderIOEither].
// This is the curried version of [MonadFlap].
//
// Parameters:
// - a: The value to apply to the function
//
// Returns a function that applies the value to a ReaderIOEither function.
func Flap[B, A any](a A) Operator[func(A) B, B] {
return readerioeither.Flap[context.Context, error, B](a)
}
// Fold handles both success and error cases of a [ReaderIOEither] by providing handlers for each.
// Both handlers return ReaderIOEither, allowing for further composition.
//
// Parameters:
// - onLeft: Handler for error case
// - onRight: Handler for success case
//
// Returns a function that folds a ReaderIOEither into a new ReaderIOEither.
func Fold[A, B any](onLeft func(error) ReaderIOEither[B], onRight func(A) ReaderIOEither[B]) Operator[A, B] {
return readerioeither.Fold(onLeft, onRight)
}
// GetOrElse extracts the value from a [ReaderIOEither], providing a default via a function if it fails.
// The result is a [ReaderIO] that always succeeds.
//
// Parameters:
// - onLeft: Function to provide a default value from the error
//
// Returns a function that converts a ReaderIOEither to a ReaderIO.
func GetOrElse[A any](onLeft func(error) ReaderIO[A]) func(ReaderIOEither[A]) ReaderIO[A] {
return readerioeither.GetOrElse(onLeft)
}
// OrLeft transforms the error of a [ReaderIOEither] using the provided function.
// The success value is left unchanged.
//
// Parameters:
// - onLeft: Function to transform the error
//
// Returns a function that transforms the error of a ReaderIOEither.
func OrLeft[A any](onLeft func(error) ReaderIO[error]) Operator[A, A] {
return readerioeither.OrLeft[A](onLeft)
}

View File

@@ -1,532 +0,0 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package readerioeither
import (
"context"
"errors"
"fmt"
"testing"
"time"
E "github.com/IBM/fp-go/v2/either"
O "github.com/IBM/fp-go/v2/option"
"github.com/stretchr/testify/assert"
)
func TestFromEither(t *testing.T) {
ctx := context.Background()
// Test with Right
rightVal := E.Right[error](42)
result := FromEither(rightVal)(ctx)()
assert.Equal(t, E.Right[error](42), result)
// Test with Left
err := errors.New("test error")
leftVal := E.Left[int](err)
result = FromEither(leftVal)(ctx)()
assert.Equal(t, E.Left[int](err), result)
}
func TestLeftRight(t *testing.T) {
ctx := context.Background()
// Test Left
err := errors.New("test error")
result := Left[int](err)(ctx)()
assert.True(t, E.IsLeft(result))
// Test Right
result = Right(42)(ctx)()
assert.True(t, E.IsRight(result))
val, _ := E.Unwrap(result)
assert.Equal(t, 42, val)
}
func TestOf(t *testing.T) {
ctx := context.Background()
result := Of(42)(ctx)()
assert.Equal(t, E.Right[error](42), result)
}
func TestMonadMap(t *testing.T) {
ctx := context.Background()
// Test with Right
result := MonadMap(Right(42), func(x int) int { return x * 2 })(ctx)()
assert.Equal(t, E.Right[error](84), result)
// Test with Left
err := errors.New("test error")
result = MonadMap(Left[int](err), func(x int) int { return x * 2 })(ctx)()
assert.Equal(t, E.Left[int](err), result)
}
func TestMonadMapTo(t *testing.T) {
ctx := context.Background()
// Test with Right
result := MonadMapTo(Right(42), "hello")(ctx)()
assert.Equal(t, E.Right[error]("hello"), result)
// Test with Left
err := errors.New("test error")
result = MonadMapTo(Left[int](err), "hello")(ctx)()
assert.Equal(t, E.Left[string](err), result)
}
func TestMonadChain(t *testing.T) {
ctx := context.Background()
// Test with Right
result := MonadChain(Right(42), func(x int) ReaderIOEither[int] {
return Right(x * 2)
})(ctx)()
assert.Equal(t, E.Right[error](84), result)
// Test with Left
err := errors.New("test error")
result = MonadChain(Left[int](err), func(x int) ReaderIOEither[int] {
return Right(x * 2)
})(ctx)()
assert.Equal(t, E.Left[int](err), result)
// Test where function returns Left
result = MonadChain(Right(42), func(x int) ReaderIOEither[int] {
return Left[int](errors.New("chain error"))
})(ctx)()
assert.True(t, E.IsLeft(result))
}
func TestMonadChainFirst(t *testing.T) {
ctx := context.Background()
// Test with Right
result := MonadChainFirst(Right(42), func(x int) ReaderIOEither[string] {
return Right("ignored")
})(ctx)()
assert.Equal(t, E.Right[error](42), result)
// Test with Left in first
err := errors.New("test error")
result = MonadChainFirst(Left[int](err), func(x int) ReaderIOEither[string] {
return Right("ignored")
})(ctx)()
assert.Equal(t, E.Left[int](err), result)
// Test with Left in second
result = MonadChainFirst(Right(42), func(x int) ReaderIOEither[string] {
return Left[string](errors.New("chain error"))
})(ctx)()
assert.True(t, E.IsLeft(result))
}
func TestMonadApSeq(t *testing.T) {
ctx := context.Background()
// Test with both Right
fct := Right(func(x int) int { return x * 2 })
val := Right(42)
result := MonadApSeq(fct, val)(ctx)()
assert.Equal(t, E.Right[error](84), result)
// Test with Left function
err := errors.New("function error")
fct = Left[func(int) int](err)
result = MonadApSeq(fct, val)(ctx)()
assert.Equal(t, E.Left[int](err), result)
// Test with Left value
fct = Right(func(x int) int { return x * 2 })
err = errors.New("value error")
val = Left[int](err)
result = MonadApSeq(fct, val)(ctx)()
assert.Equal(t, E.Left[int](err), result)
}
func TestMonadApPar(t *testing.T) {
ctx := context.Background()
// Test with both Right
fct := Right(func(x int) int { return x * 2 })
val := Right(42)
result := MonadApPar(fct, val)(ctx)()
assert.Equal(t, E.Right[error](84), result)
}
func TestFromPredicate(t *testing.T) {
ctx := context.Background()
pred := func(x int) bool { return x > 0 }
onFalse := func(x int) error { return fmt.Errorf("value %d is not positive", x) }
// Test with predicate true
result := FromPredicate(pred, onFalse)(42)(ctx)()
assert.Equal(t, E.Right[error](42), result)
// Test with predicate false
result = FromPredicate(pred, onFalse)(-1)(ctx)()
assert.True(t, E.IsLeft(result))
}
func TestAsk(t *testing.T) {
ctx := context.WithValue(context.Background(), "key", "value")
result := Ask()(ctx)()
assert.True(t, E.IsRight(result))
retrievedCtx, _ := E.Unwrap(result)
assert.Equal(t, "value", retrievedCtx.Value("key"))
}
func TestMonadChainEitherK(t *testing.T) {
ctx := context.Background()
// Test with Right
result := MonadChainEitherK(Right(42), func(x int) E.Either[error, int] {
return E.Right[error](x * 2)
})(ctx)()
assert.Equal(t, E.Right[error](84), result)
// Test with Left in Either
result = MonadChainEitherK(Right(42), func(x int) E.Either[error, int] {
return E.Left[int](errors.New("either error"))
})(ctx)()
assert.True(t, E.IsLeft(result))
}
func TestMonadChainFirstEitherK(t *testing.T) {
ctx := context.Background()
// Test with Right
result := MonadChainFirstEitherK(Right(42), func(x int) E.Either[error, string] {
return E.Right[error]("ignored")
})(ctx)()
assert.Equal(t, E.Right[error](42), result)
// Test with Left in Either
result = MonadChainFirstEitherK(Right(42), func(x int) E.Either[error, string] {
return E.Left[string](errors.New("either error"))
})(ctx)()
assert.True(t, E.IsLeft(result))
}
func TestChainOptionKFunc(t *testing.T) {
ctx := context.Background()
onNone := func() error { return errors.New("none error") }
// Test with Some
chainFunc := ChainOptionK[int, int](onNone)
result := chainFunc(func(x int) O.Option[int] {
return O.Some(x * 2)
})(Right(42))(ctx)()
assert.Equal(t, E.Right[error](84), result)
// Test with None
result = chainFunc(func(x int) O.Option[int] {
return O.None[int]()
})(Right(42))(ctx)()
assert.True(t, E.IsLeft(result))
}
func TestFromIOEither(t *testing.T) {
ctx := context.Background()
// Test with Right
ioe := func() E.Either[error, int] {
return E.Right[error](42)
}
result := FromIOEither(ioe)(ctx)()
assert.Equal(t, E.Right[error](42), result)
// Test with Left
err := errors.New("test error")
ioe = func() E.Either[error, int] {
return E.Left[int](err)
}
result = FromIOEither(ioe)(ctx)()
assert.Equal(t, E.Left[int](err), result)
}
func TestFromIO(t *testing.T) {
ctx := context.Background()
io := func() int { return 42 }
result := FromIO(io)(ctx)()
assert.Equal(t, E.Right[error](42), result)
}
func TestFromLazy(t *testing.T) {
ctx := context.Background()
lazy := func() int { return 42 }
result := FromLazy(lazy)(ctx)()
assert.Equal(t, E.Right[error](42), result)
}
func TestNeverWithCancel(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
// Start Never in a goroutine
done := make(chan E.Either[error, int])
go func() {
done <- Never[int]()(ctx)()
}()
// Cancel the context
cancel()
// Should receive cancellation error
result := <-done
assert.True(t, E.IsLeft(result))
}
func TestMonadChainIOK(t *testing.T) {
ctx := context.Background()
// Test with Right
result := MonadChainIOK(Right(42), func(x int) func() int {
return func() int { return x * 2 }
})(ctx)()
assert.Equal(t, E.Right[error](84), result)
}
func TestMonadChainFirstIOK(t *testing.T) {
ctx := context.Background()
// Test with Right
result := MonadChainFirstIOK(Right(42), func(x int) func() string {
return func() string { return "ignored" }
})(ctx)()
assert.Equal(t, E.Right[error](42), result)
}
func TestDelayFunc(t *testing.T) {
ctx := context.Background()
delay := 100 * time.Millisecond
start := time.Now()
delayFunc := Delay[int](delay)
result := delayFunc(Right(42))(ctx)()
elapsed := time.Since(start)
assert.True(t, E.IsRight(result))
assert.GreaterOrEqual(t, elapsed, delay)
}
func TestDefer(t *testing.T) {
ctx := context.Background()
count := 0
gen := func() ReaderIOEither[int] {
count++
return Right(count)
}
deferred := Defer(gen)
// First call
result1 := deferred(ctx)()
assert.Equal(t, E.Right[error](1), result1)
// Second call should generate new value
result2 := deferred(ctx)()
assert.Equal(t, E.Right[error](2), result2)
}
func TestTryCatch(t *testing.T) {
ctx := context.Background()
// Test success
result := TryCatch(func(ctx context.Context) func() (int, error) {
return func() (int, error) {
return 42, nil
}
})(ctx)()
assert.Equal(t, E.Right[error](42), result)
// Test error
err := errors.New("test error")
result = TryCatch(func(ctx context.Context) func() (int, error) {
return func() (int, error) {
return 0, err
}
})(ctx)()
assert.Equal(t, E.Left[int](err), result)
}
func TestMonadAlt(t *testing.T) {
ctx := context.Background()
// Test with Right (alternative not called)
result := MonadAlt(Right(42), func() ReaderIOEither[int] {
return Right(99)
})(ctx)()
assert.Equal(t, E.Right[error](42), result)
// Test with Left (alternative called)
err := errors.New("test error")
result = MonadAlt(Left[int](err), func() ReaderIOEither[int] {
return Right(99)
})(ctx)()
assert.Equal(t, E.Right[error](99), result)
}
func TestMemoize(t *testing.T) {
ctx := context.Background()
count := 0
rdr := Memoize(FromLazy(func() int {
count++
return count
}))
// First call
result1 := rdr(ctx)()
assert.Equal(t, E.Right[error](1), result1)
// Second call should return memoized value
result2 := rdr(ctx)()
assert.Equal(t, E.Right[error](1), result2)
}
func TestFlatten(t *testing.T) {
ctx := context.Background()
nested := Right(Right(42))
result := Flatten(nested)(ctx)()
assert.Equal(t, E.Right[error](42), result)
}
func TestMonadFlap(t *testing.T) {
ctx := context.Background()
fab := Right(func(x int) int { return x * 2 })
result := MonadFlap(fab, 42)(ctx)()
assert.Equal(t, E.Right[error](84), result)
}
func TestWithContext(t *testing.T) {
// Test with non-canceled context
ctx := context.Background()
result := WithContext(Right(42))(ctx)()
assert.Equal(t, E.Right[error](42), result)
// Test with canceled context
ctx, cancel := context.WithCancel(context.Background())
cancel()
result = WithContext(Right(42))(ctx)()
assert.True(t, E.IsLeft(result))
}
func TestMonadAp(t *testing.T) {
ctx := context.Background()
// Test with both Right
fct := Right(func(x int) int { return x * 2 })
val := Right(42)
result := MonadAp(fct, val)(ctx)()
assert.Equal(t, E.Right[error](84), result)
}
// Test traverse functions
func TestSequenceArray(t *testing.T) {
ctx := context.Background()
// Test with all Right
arr := []ReaderIOEither[int]{Right(1), Right(2), Right(3)}
result := SequenceArray(arr)(ctx)()
assert.True(t, E.IsRight(result))
vals, _ := E.Unwrap(result)
assert.Equal(t, []int{1, 2, 3}, vals)
// Test with one Left
err := errors.New("test error")
arr = []ReaderIOEither[int]{Right(1), Left[int](err), Right(3)}
result = SequenceArray(arr)(ctx)()
assert.True(t, E.IsLeft(result))
}
func TestTraverseArray(t *testing.T) {
ctx := context.Background()
// Test transformation
arr := []int{1, 2, 3}
result := TraverseArray(func(x int) ReaderIOEither[int] {
return Right(x * 2)
})(arr)(ctx)()
assert.True(t, E.IsRight(result))
vals, _ := E.Unwrap(result)
assert.Equal(t, []int{2, 4, 6}, vals)
}
func TestSequenceRecord(t *testing.T) {
ctx := context.Background()
// Test with all Right
rec := map[string]ReaderIOEither[int]{
"a": Right(1),
"b": Right(2),
}
result := SequenceRecord(rec)(ctx)()
assert.True(t, E.IsRight(result))
vals, _ := E.Unwrap(result)
assert.Equal(t, 1, vals["a"])
assert.Equal(t, 2, vals["b"])
}
func TestTraverseRecord(t *testing.T) {
ctx := context.Background()
// Test transformation
rec := map[string]int{"a": 1, "b": 2}
result := TraverseRecord[string](func(x int) ReaderIOEither[int] {
return Right(x * 2)
})(rec)(ctx)()
assert.True(t, E.IsRight(result))
vals, _ := E.Unwrap(result)
assert.Equal(t, 2, vals["a"])
assert.Equal(t, 4, vals["b"])
}
// Test monoid functions
func TestAltSemigroup(t *testing.T) {
ctx := context.Background()
sg := AltSemigroup[int]()
// Test with Right (first succeeds)
result := sg.Concat(Right(42), Right(99))(ctx)()
assert.Equal(t, E.Right[error](42), result)
// Test with Left then Right (fallback)
err := errors.New("test error")
result = sg.Concat(Left[int](err), Right(99))(ctx)()
assert.Equal(t, E.Right[error](99), result)
}
// Test Do notation
func TestDo(t *testing.T) {
ctx := context.Background()
type State struct {
Value int
}
result := Do(State{Value: 42})(ctx)()
assert.True(t, E.IsRight(result))
state, _ := E.Unwrap(result)
assert.Equal(t, 42, state.Value)
}

View File

@@ -1,306 +0,0 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package readerioeither
import (
"github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/internal/array"
"github.com/IBM/fp-go/v2/internal/record"
)
// TraverseArray transforms an array [[]A] into [[]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[[]B]].
// This uses the default applicative behavior (parallel or sequential based on useParallel flag).
//
// Parameters:
// - f: Function that transforms each element into a ReaderIOEither
//
// Returns a function that transforms an array into a ReaderIOEither of an array.
func TraverseArray[A, B any](f func(A) ReaderIOEither[B]) func([]A) ReaderIOEither[[]B] {
return array.Traverse[[]A](
Of[[]B],
Map[[]B, func(B) []B],
Ap[[]B, B],
f,
)
}
// TraverseArrayWithIndex transforms an array [[]A] into [[]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[[]B]].
// The transformation function receives both the index and the element.
//
// Parameters:
// - f: Function that transforms each element with its index into a ReaderIOEither
//
// Returns a function that transforms an array into a ReaderIOEither of an array.
func TraverseArrayWithIndex[A, B any](f func(int, A) ReaderIOEither[B]) func([]A) ReaderIOEither[[]B] {
return array.TraverseWithIndex[[]A](
Of[[]B],
Map[[]B, func(B) []B],
Ap[[]B, B],
f,
)
}
// SequenceArray converts a homogeneous sequence of ReaderIOEither into a ReaderIOEither of sequence.
// This is equivalent to TraverseArray with the identity function.
//
// Parameters:
// - ma: Array of ReaderIOEither values
//
// Returns a ReaderIOEither containing an array of values.
func SequenceArray[A any](ma []ReaderIOEither[A]) ReaderIOEither[[]A] {
return TraverseArray(function.Identity[ReaderIOEither[A]])(ma)
}
// TraverseRecord transforms a record [map[K]A] into [map[K]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[map[K]B]].
//
// Parameters:
// - f: Function that transforms each value into a ReaderIOEither
//
// Returns a function that transforms a map into a ReaderIOEither of a map.
func TraverseRecord[K comparable, A, B any](f func(A) ReaderIOEither[B]) func(map[K]A) ReaderIOEither[map[K]B] {
return record.Traverse[map[K]A](
Of[map[K]B],
Map[map[K]B, func(B) map[K]B],
Ap[map[K]B, B],
f,
)
}
// TraverseRecordWithIndex transforms a record [map[K]A] into [map[K]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[map[K]B]].
// The transformation function receives both the key and the value.
//
// Parameters:
// - f: Function that transforms each key-value pair into a ReaderIOEither
//
// Returns a function that transforms a map into a ReaderIOEither of a map.
func TraverseRecordWithIndex[K comparable, A, B any](f func(K, A) ReaderIOEither[B]) func(map[K]A) ReaderIOEither[map[K]B] {
return record.TraverseWithIndex[map[K]A](
Of[map[K]B],
Map[map[K]B, func(B) map[K]B],
Ap[map[K]B, B],
f,
)
}
// SequenceRecord converts a homogeneous map of ReaderIOEither into a ReaderIOEither of map.
//
// Parameters:
// - ma: Map of ReaderIOEither values
//
// Returns a ReaderIOEither containing a map of values.
func SequenceRecord[K comparable, A any](ma map[K]ReaderIOEither[A]) ReaderIOEither[map[K]A] {
return TraverseRecord[K](function.Identity[ReaderIOEither[A]])(ma)
}
// MonadTraverseArraySeq transforms an array [[]A] into [[]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[[]B]].
// This explicitly uses sequential execution.
//
// Parameters:
// - as: The array to traverse
// - f: Function that transforms each element into a ReaderIOEither
//
// Returns a ReaderIOEither containing an array of transformed values.
func MonadTraverseArraySeq[A, B any](as []A, f func(A) ReaderIOEither[B]) ReaderIOEither[[]B] {
return array.MonadTraverse[[]A](
Of[[]B],
Map[[]B, func(B) []B],
ApSeq[[]B, B],
as,
f,
)
}
// TraverseArraySeq transforms an array [[]A] into [[]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[[]B]].
// This is the curried version of [MonadTraverseArraySeq] with sequential execution.
//
// Parameters:
// - f: Function that transforms each element into a ReaderIOEither
//
// Returns a function that transforms an array into a ReaderIOEither of an array.
func TraverseArraySeq[A, B any](f func(A) ReaderIOEither[B]) func([]A) ReaderIOEither[[]B] {
return array.Traverse[[]A](
Of[[]B],
Map[[]B, func(B) []B],
ApSeq[[]B, B],
f,
)
}
// TraverseArrayWithIndexSeq uses transforms an array [[]A] into [[]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[[]B]]
func TraverseArrayWithIndexSeq[A, B any](f func(int, A) ReaderIOEither[B]) func([]A) ReaderIOEither[[]B] {
return array.TraverseWithIndex[[]A](
Of[[]B],
Map[[]B, func(B) []B],
ApSeq[[]B, B],
f,
)
}
// SequenceArraySeq converts a homogeneous sequence of ReaderIOEither into a ReaderIOEither of sequence.
// This explicitly uses sequential execution.
//
// Parameters:
// - ma: Array of ReaderIOEither values
//
// Returns a ReaderIOEither containing an array of values.
func SequenceArraySeq[A any](ma []ReaderIOEither[A]) ReaderIOEither[[]A] {
return MonadTraverseArraySeq(ma, function.Identity[ReaderIOEither[A]])
}
// MonadTraverseRecordSeq uses transforms a record [map[K]A] into [map[K]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[map[K]B]]
func MonadTraverseRecordSeq[K comparable, A, B any](as map[K]A, f func(A) ReaderIOEither[B]) ReaderIOEither[map[K]B] {
return record.MonadTraverse[map[K]A](
Of[map[K]B],
Map[map[K]B, func(B) map[K]B],
ApSeq[map[K]B, B],
as,
f,
)
}
// TraverseRecordSeq uses transforms a record [map[K]A] into [map[K]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[map[K]B]]
func TraverseRecordSeq[K comparable, A, B any](f func(A) ReaderIOEither[B]) func(map[K]A) ReaderIOEither[map[K]B] {
return record.Traverse[map[K]A](
Of[map[K]B],
Map[map[K]B, func(B) map[K]B],
ApSeq[map[K]B, B],
f,
)
}
// TraverseRecordWithIndexSeq uses transforms a record [map[K]A] into [map[K]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[map[K]B]]
func TraverseRecordWithIndexSeq[K comparable, A, B any](f func(K, A) ReaderIOEither[B]) func(map[K]A) ReaderIOEither[map[K]B] {
return record.TraverseWithIndex[map[K]A](
Of[map[K]B],
Map[map[K]B, func(B) map[K]B],
ApSeq[map[K]B, B],
f,
)
}
// SequenceRecordSeq converts a homogeneous sequence of either into an either of sequence
func SequenceRecordSeq[K comparable, A any](ma map[K]ReaderIOEither[A]) ReaderIOEither[map[K]A] {
return MonadTraverseRecordSeq(ma, function.Identity[ReaderIOEither[A]])
}
// MonadTraverseArrayPar transforms an array [[]A] into [[]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[[]B]].
// This explicitly uses parallel execution.
//
// Parameters:
// - as: The array to traverse
// - f: Function that transforms each element into a ReaderIOEither
//
// Returns a ReaderIOEither containing an array of transformed values.
func MonadTraverseArrayPar[A, B any](as []A, f func(A) ReaderIOEither[B]) ReaderIOEither[[]B] {
return array.MonadTraverse[[]A](
Of[[]B],
Map[[]B, func(B) []B],
ApPar[[]B, B],
as,
f,
)
}
// TraverseArrayPar transforms an array [[]A] into [[]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[[]B]].
// This is the curried version of [MonadTraverseArrayPar] with parallel execution.
//
// Parameters:
// - f: Function that transforms each element into a ReaderIOEither
//
// Returns a function that transforms an array into a ReaderIOEither of an array.
func TraverseArrayPar[A, B any](f func(A) ReaderIOEither[B]) func([]A) ReaderIOEither[[]B] {
return array.Traverse[[]A](
Of[[]B],
Map[[]B, func(B) []B],
ApPar[[]B, B],
f,
)
}
// TraverseArrayWithIndexPar uses transforms an array [[]A] into [[]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[[]B]]
func TraverseArrayWithIndexPar[A, B any](f func(int, A) ReaderIOEither[B]) func([]A) ReaderIOEither[[]B] {
return array.TraverseWithIndex[[]A](
Of[[]B],
Map[[]B, func(B) []B],
ApPar[[]B, B],
f,
)
}
// SequenceArrayPar converts a homogeneous sequence of ReaderIOEither into a ReaderIOEither of sequence.
// This explicitly uses parallel execution.
//
// Parameters:
// - ma: Array of ReaderIOEither values
//
// Returns a ReaderIOEither containing an array of values.
func SequenceArrayPar[A any](ma []ReaderIOEither[A]) ReaderIOEither[[]A] {
return MonadTraverseArrayPar(ma, function.Identity[ReaderIOEither[A]])
}
// TraverseRecordPar uses transforms a record [map[K]A] into [map[K]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[map[K]B]]
func TraverseRecordPar[K comparable, A, B any](f func(A) ReaderIOEither[B]) func(map[K]A) ReaderIOEither[map[K]B] {
return record.Traverse[map[K]A](
Of[map[K]B],
Map[map[K]B, func(B) map[K]B],
ApPar[map[K]B, B],
f,
)
}
// TraverseRecordWithIndexPar uses transforms a record [map[K]A] into [map[K]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[map[K]B]]
func TraverseRecordWithIndexPar[K comparable, A, B any](f func(K, A) ReaderIOEither[B]) func(map[K]A) ReaderIOEither[map[K]B] {
return record.TraverseWithIndex[map[K]A](
Of[map[K]B],
Map[map[K]B, func(B) map[K]B],
ApPar[map[K]B, B],
f,
)
}
// MonadTraverseRecordPar uses transforms a record [map[K]A] into [map[K]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[map[K]B]]
func MonadTraverseRecordPar[K comparable, A, B any](as map[K]A, f func(A) ReaderIOEither[B]) ReaderIOEither[map[K]B] {
return record.MonadTraverse[map[K]A](
Of[map[K]B],
Map[map[K]B, func(B) map[K]B],
ApPar[map[K]B, B],
as,
f,
)
}
// SequenceRecordPar converts a homogeneous map of ReaderIOEither into a ReaderIOEither of map.
// This explicitly uses parallel execution.
//
// Parameters:
// - ma: Map of ReaderIOEither values
//
// Returns a ReaderIOEither containing a map of values.
func SequenceRecordPar[K comparable, A any](ma map[K]ReaderIOEither[A]) ReaderIOEither[map[K]A] {
return MonadTraverseRecordPar(ma, function.Identity[ReaderIOEither[A]])
}

View File

@@ -0,0 +1,374 @@
# ReaderIOResult Benchmarks
This document describes the benchmark suite for the `context/readerioeither` package and how to interpret the results to identify performance bottlenecks.
## Running Benchmarks
To run all benchmarks:
```bash
cd context/readerioeither
go test -bench=. -benchmem
```
To run specific benchmarks:
```bash
go test -bench=BenchmarkMap -benchmem
go test -bench=BenchmarkChain -benchmem
go test -bench=BenchmarkApPar -benchmem
```
To run with more iterations for stable results:
```bash
go test -bench=. -benchmem -benchtime=100000x
```
## Benchmark Categories
### 1. Core Constructors
- `BenchmarkLeft` - Creating Left (error) values (~64ns, 2 allocs)
- `BenchmarkRight` - Creating Right (success) values (~64ns, 2 allocs)
- `BenchmarkOf` - Creating Right values via Of (~47ns, 2 allocs)
**Key Insights:**
- All constructors allocate 2 times (64B total)
- `Of` is slightly faster than `Right` due to inlining
- Construction is very fast, suitable for hot paths
### 2. Conversion Operations
- `BenchmarkFromEither_Right/Left` - Converting Either to ReaderIOResult (~70ns, 2 allocs)
- `BenchmarkFromIO` - Converting IO to ReaderIOResult (~78ns, 3 allocs)
- `BenchmarkFromIOEither_Right/Left` - Converting IOEither (~23ns, 1 alloc)
**Key Insights:**
- FromIOEither is the fastest conversion (~23ns)
- FromIO has an extra allocation due to wrapping
- All conversions are lightweight
### 3. Execution Operations
- `BenchmarkExecute_Right` - Executing Right computation (~37ns, 1 alloc)
- `BenchmarkExecute_Left` - Executing Left computation (~48ns, 1 alloc)
- `BenchmarkExecute_WithContext` - Executing with context (~42ns, 1 alloc)
**Key Insights:**
- Execution is very fast with minimal allocations
- Left path is slightly slower due to error handling
- Context overhead is minimal (~5ns)
### 4. Functor Operations (Map)
- `BenchmarkMonadMap_Right/Left` - Direct map (~135ns, 5 allocs)
- `BenchmarkMap_Right/Left` - Curried map (~24ns, 1 alloc)
- `BenchmarkMapTo_Right` - Replacing with constant (~69ns, 1 alloc)
**Key Insights:**
- **Bottleneck:** MonadMap has 5 allocations (128B)
- Curried Map is ~5x faster with fewer allocations
- **Recommendation:** Use curried `Map` instead of `MonadMap`
### 5. Monad Operations (Chain)
- `BenchmarkMonadChain_Right/Left` - Direct chain (~190ns, 6 allocs)
- `BenchmarkChain_Right/Left` - Curried chain (~28ns, 1 alloc)
- `BenchmarkChainFirst_Right/Left` - Chain preserving original (~27ns, 1 alloc)
- `BenchmarkFlatten_Right/Left` - Removing nesting (~147ns, 7 allocs)
**Key Insights:**
- **Bottleneck:** MonadChain has 6 allocations (160B)
- Curried Chain is ~7x faster
- ChainFirst is as fast as Chain
- **Bottleneck:** Flatten has 7 allocations
- **Recommendation:** Use curried `Chain` instead of `MonadChain`
### 6. Applicative Operations (Ap)
- `BenchmarkMonadApSeq_RightRight` - Sequential apply (~281ns, 8 allocs)
- `BenchmarkMonadApPar_RightRight` - Parallel apply (~49ns, 3 allocs)
- `BenchmarkExecuteApSeq_RightRight` - Executing sequential (~1403ns, 8 allocs)
- `BenchmarkExecuteApPar_RightRight` - Executing parallel (~5606ns, 61 allocs)
**Key Insights:**
- **Major Bottleneck:** Parallel execution has 61 allocations (1896B)
- Construction of ApPar is fast (~49ns), but execution is expensive
- Sequential execution is faster for simple operations (~1.4μs vs ~5.6μs)
- **Recommendation:** Use ApSeq for simple operations, ApPar only for truly independent, expensive computations
- Parallel overhead includes context management and goroutine coordination
### 7. Alternative Operations
- `BenchmarkAlt_RightRight/LeftRight` - Providing alternatives (~210-344ns, 6 allocs)
- `BenchmarkOrElse_Right/Left` - Recovery from Left (~40-52ns, 2 allocs)
**Key Insights:**
- Alt has significant overhead (6 allocations)
- OrElse is much more efficient for error recovery
- **Recommendation:** Prefer OrElse over Alt when possible
### 8. Chain Operations with Different Types
- `BenchmarkChainEitherK_Right/Left` - Chaining Either (~25ns, 1 alloc)
- `BenchmarkChainIOK_Right/Left` - Chaining IO (~55ns, 1 alloc)
- `BenchmarkChainIOEitherK_Right/Left` - Chaining IOEither (~53ns, 1 alloc)
**Key Insights:**
- All chain-K operations are efficient
- ChainEitherK is fastest (pure transformation)
- ChainIOK and ChainIOEitherK have similar performance
### 9. Context Operations
- `BenchmarkAsk` - Accessing context (~52ns, 3 allocs)
- `BenchmarkDefer` - Lazy generation (~34ns, 1 alloc)
- `BenchmarkMemoize` - Caching results (~82ns, 4 allocs)
**Key Insights:**
- Ask has 3 allocations for context wrapping
- Defer is lightweight
- Memoize has overhead but pays off for expensive computations
### 10. Delay Operations
- `BenchmarkDelay_Construction` - Creating delayed computation (~19ns, 1 alloc)
- `BenchmarkTimer_Construction` - Creating timer (~92ns, 3 allocs)
**Key Insights:**
- Delay construction is very cheap
- Timer has additional overhead for time operations
### 11. TryCatch Operations
- `BenchmarkTryCatch_Success/Error` - Creating TryCatch (~33ns, 1 alloc)
- `BenchmarkExecuteTryCatch_Success/Error` - Executing TryCatch (~3ns, 0 allocs)
**Key Insights:**
- TryCatch construction is cheap
- Execution is extremely fast with zero allocations
- Excellent for wrapping Go error-returning functions
### 12. Pipeline Operations
- `BenchmarkPipeline_Map_Right/Left` - Single Map in pipeline (~200-306ns, 9 allocs)
- `BenchmarkPipeline_Chain_Right/Left` - Single Chain in pipeline (~155-217ns, 7 allocs)
- `BenchmarkPipeline_Complex_Right/Left` - Multiple operations (~777-1039ns, 25 allocs)
- `BenchmarkExecutePipeline_Complex_Right` - Executing complex pipeline (~533ns, 10 allocs)
**Key Insights:**
- **Major Bottleneck:** Pipeline operations allocate heavily
- Single Map: ~200ns with 9 allocations (224B)
- Complex pipeline: ~900ns with 25 allocations (640B)
- **Recommendation:** Avoid F.Pipe in hot paths, use direct function calls
### 13. Do-Notation Operations
- `BenchmarkDo` - Creating empty context (~45ns, 2 allocs)
- `BenchmarkBind_Right` - Binding values (~25ns, 1 alloc)
- `BenchmarkLet_Right` - Pure computations (~23ns, 1 alloc)
- `BenchmarkApS_Right` - Applicative binding (~99ns, 4 allocs)
**Key Insights:**
- Do-notation operations are efficient
- Bind and Let are very fast
- ApS has more overhead (4 allocations)
- Much better than either package's Bind (~130ns vs ~25ns)
### 14. Traverse Operations
- `BenchmarkTraverseArray_Empty` - Empty array (~689ns, 13 allocs)
- `BenchmarkTraverseArray_Small` - 3 elements (~1971ns, 37 allocs)
- `BenchmarkTraverseArray_Medium` - 10 elements (~4386ns, 93 allocs)
- `BenchmarkTraverseArraySeq_Small` - Sequential (~1885ns, 52 allocs)
- `BenchmarkTraverseArrayPar_Small` - Parallel (~1362ns, 37 allocs)
- `BenchmarkExecuteTraverseArraySeq_Small` - Executing sequential (~1080ns, 34 allocs)
- `BenchmarkExecuteTraverseArrayPar_Small` - Executing parallel (~18560ns, 202 allocs)
**Key Insights:**
- **Bottleneck:** Traverse operations allocate per element
- Empty array still has 13 allocations (overhead)
- Parallel construction is faster but execution is much slower
- **Major Bottleneck:** Parallel execution: ~18.5μs with 202 allocations
- Sequential execution is ~17x faster for small arrays
- **Recommendation:** Use sequential traverse for small collections, parallel only for large, expensive operations
### 15. Record Operations
- `BenchmarkTraverseRecord_Small` - 3 entries (~1444ns, 55 allocs)
- `BenchmarkSequenceRecord_Small` - 3 entries (~1073ns, 54 allocs)
**Key Insights:**
- Record operations have high allocation overhead
- Similar performance to array traversal
- Allocations scale with map size
### 16. Resource Management
- `BenchmarkWithResource_Success` - Creating resource wrapper (~193ns, 8 allocs)
- `BenchmarkExecuteWithResource_Success` - Executing with resource (varies)
- `BenchmarkExecuteWithResource_ErrorInBody` - Error handling (varies)
**Key Insights:**
- Resource management has 8 allocations for safety
- Ensures proper cleanup even on errors
- Overhead is acceptable for resource safety guarantees
### 17. Context Cancellation
- `BenchmarkExecute_CanceledContext` - Executing with canceled context
- `BenchmarkExecuteApPar_CanceledContext` - Parallel with canceled context
**Key Insights:**
- Cancellation is handled efficiently
- Minimal overhead for checking cancellation
- ApPar respects cancellation properly
## Performance Bottlenecks Summary
### Critical Bottlenecks (>100ns or >5 allocations)
1. **Pipeline operations with F.Pipe** (~200-1000ns, 9-25 allocations)
- **Impact:** High - commonly used pattern
- **Mitigation:** Use direct function calls in hot paths
- **Example:**
```go
// Slow (200ns, 9 allocs)
result := F.Pipe1(rioe, Map[int](transform))
// Fast (24ns, 1 alloc)
result := Map[int](transform)(rioe)
```
2. **MonadMap and MonadChain** (~135-207ns, 5-6 allocations)
- **Impact:** High - fundamental operations
- **Mitigation:** Use curried versions (Map, Chain)
- **Speedup:** 5-7x faster
3. **Parallel applicative execution** (~5.6μs, 61 allocations)
- **Impact:** High when used
- **Mitigation:** Use ApSeq for simple operations
- **Note:** Only use ApPar for truly independent, expensive computations
4. **Parallel traverse execution** (~18.5μs, 202 allocations)
- **Impact:** High for collections
- **Mitigation:** Use sequential traverse for small collections
- **Threshold:** Consider parallel only for >100 elements with expensive operations
5. **Alt operations** (~210-344ns, 6 allocations)
- **Impact:** Medium
- **Mitigation:** Use OrElse for error recovery (40-52ns, 2 allocs)
### Minor Bottlenecks (50-100ns or 3-4 allocations)
6. **Flatten operations** (~147ns, 7 allocations)
- **Impact:** Low - less commonly used
- **Mitigation:** Avoid unnecessary nesting
7. **Memoize** (~82ns, 4 allocations)
- **Impact:** Low - overhead pays off for expensive computations
- **Mitigation:** Only use for computations >1μs
8. **ApS in do-notation** (~99ns, 4 allocations)
- **Impact:** Low
- **Mitigation:** Use Let or Bind when possible
## Optimization Recommendations
### For Hot Paths
1. **Use curried functions over Monad* versions:**
```go
// Instead of:
result := MonadMap(rioe, transform) // 135ns, 5 allocs
// Use:
result := Map[int](transform)(rioe) // 24ns, 1 alloc
```
2. **Avoid F.Pipe in performance-critical code:**
```go
// Instead of:
result := F.Pipe3(rioe, Map(f1), Chain(f2), Map(f3)) // 1000ns, 25 allocs
// Use:
result := Map(f3)(Chain(f2)(Map(f1)(rioe))) // Much faster
```
3. **Use sequential operations for small collections:**
```go
// For arrays with <10 elements:
result := TraverseArraySeq(f)(arr) // 1.9μs, 52 allocs
// Instead of:
result := TraverseArrayPar(f)(arr) // 18.5μs, 202 allocs (when executed)
```
4. **Prefer OrElse over Alt for error recovery:**
```go
// Instead of:
result := Alt(alternative)(rioe) // 210-344ns, 6 allocs
// Use:
result := OrElse(recover)(rioe) // 40-52ns, 2 allocs
```
### For Context Operations
- Context operations are generally efficient
- Ask has 3 allocations but is necessary for context access
- Cancellation checking is fast and should be used liberally
### For Resource Management
- WithResource overhead (8 allocations) is acceptable for safety
- Always use for resources that need cleanup
- The RAII pattern prevents resource leaks
### Memory Considerations
- Most operations have 1-2 allocations
- Monad* versions have 5-8 allocations
- Pipeline operations allocate heavily
- Parallel operations have significant allocation overhead
- Traverse operations allocate per element
## Comparative Analysis
### Fastest Operations (<50ns, <2 allocations)
- Constructors (Left, Right, Of)
- Execution (Execute_Right/Left)
- Curried operations (Map, Chain, ChainFirst)
- TryCatch execution
- Chain-K operations
- Let and Bind in do-notation
### Medium Speed (50-200ns, 2-8 allocations)
- Conversion operations
- MonadMap, MonadChain
- Flatten
- Memoize
- Resource management construction
- Traverse construction
### Slower Operations (>200ns or >8 allocations)
- Pipeline operations
- Alt operations
- Traverse operations (especially parallel)
- Applicative operations (especially parallel execution)
## Parallel vs Sequential Trade-offs
### When to Use Sequential (ApSeq, TraverseArraySeq)
- Small collections (<10 elements)
- Fast operations (<1μs per element)
- When allocations matter
- Default choice for most use cases
### When to Use Parallel (ApPar, TraverseArrayPar)
- Large collections (>100 elements)
- Expensive operations (>10μs per element)
- Independent computations
- When latency matters more than throughput
- I/O-bound operations
### Parallel Overhead
- Construction: ~50ns, 3 allocs
- Execution: +4-17μs, +40-160 allocs
- Context management and goroutine coordination
- Only worthwhile for truly expensive operations
## Conclusion
The `context/readerioeither` package is well-optimized with most operations completing in nanoseconds with minimal allocations. Key recommendations:
1. Use curried functions (Map, Chain) instead of Monad* versions (5-7x faster)
2. Avoid F.Pipe in hot paths (5-10x slower)
3. Use sequential operations by default; parallel only for expensive, independent computations
4. Prefer OrElse over Alt for error recovery (5x faster)
5. TryCatch is excellent for wrapping Go functions (near-zero execution cost)
6. Context operations are efficient; use liberally
7. Resource management overhead is acceptable for safety guarantees
For typical use cases, the performance is excellent. Only in extremely hot paths (millions of operations per second) should you consider the micro-optimizations suggested above.

View File

@@ -0,0 +1,724 @@
// 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 readerioresult
import (
"context"
F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/internal/apply"
"github.com/IBM/fp-go/v2/io"
"github.com/IBM/fp-go/v2/ioeither"
"github.com/IBM/fp-go/v2/ioresult"
L "github.com/IBM/fp-go/v2/optics/lens"
"github.com/IBM/fp-go/v2/reader"
"github.com/IBM/fp-go/v2/readerio"
RIOR "github.com/IBM/fp-go/v2/readerioresult"
"github.com/IBM/fp-go/v2/result"
)
// Do creates an empty context of type [S] to be used with the [Bind] operation.
// This is the starting point for do-notation style composition.
//
// Example:
//
// type State struct {
// User User
// Config Config
// }
// result := readerioeither.Do(State{})
//
//go:inline
func Do[S any](
empty S,
) ReaderIOResult[S] {
return RIOR.Of[context.Context](empty)
}
// Bind attaches the result of a computation to a context [S1] to produce a context [S2].
// This enables sequential composition where each step can depend on the results of previous steps
// and access the context.Context from the environment.
//
// The setter function takes the result of the computation and returns a function that
// updates the context from S1 to S2.
//
// Example:
//
// type State struct {
// User User
// Config Config
// }
//
// result := F.Pipe2(
// readerioeither.Do(State{}),
// readerioeither.Bind(
// func(user User) func(State) State {
// return func(s State) State { s.User = user; return s }
// },
// func(s State) readerioeither.ReaderIOResult[User] {
// return func(ctx context.Context) ioeither.IOEither[error, User] {
// return ioeither.TryCatch(func() (User, error) {
// return fetchUser(ctx)
// })
// }
// },
// ),
// readerioeither.Bind(
// func(cfg Config) func(State) State {
// return func(s State) State { s.Config = cfg; return s }
// },
// func(s State) readerioeither.ReaderIOResult[Config] {
// // This can access s.User from the previous step
// return func(ctx context.Context) ioeither.IOEither[error, Config] {
// return ioeither.TryCatch(func() (Config, error) {
// return fetchConfigForUser(ctx, s.User.ID)
// })
// }
// },
// ),
// )
//
//go:inline
func Bind[S1, S2, T any](
setter func(T) func(S1) S2,
f Kleisli[S1, T],
) Operator[S1, S2] {
return RIOR.Bind(setter, f)
}
// Let attaches the result of a computation to a context [S1] to produce a context [S2]
//
//go:inline
func Let[S1, S2, T any](
setter func(T) func(S1) S2,
f func(S1) T,
) Operator[S1, S2] {
return RIOR.Let[context.Context](setter, f)
}
// LetTo attaches the a value to a context [S1] to produce a context [S2]
//
//go:inline
func LetTo[S1, S2, T any](
setter func(T) func(S1) S2,
b T,
) Operator[S1, S2] {
return RIOR.LetTo[context.Context](setter, b)
}
// BindTo initializes a new state [S1] from a value [T]
//
//go:inline
func BindTo[S1, T any](
setter func(T) S1,
) Operator[T, S1] {
return RIOR.BindTo[context.Context](setter)
}
// ApS attaches a value to a context [S1] to produce a context [S2] by considering
// the context and the value concurrently (using Applicative rather than Monad).
// This allows independent computations to be combined without one depending on the result of the other.
//
// Unlike Bind, which sequences operations, ApS can be used when operations are independent
// and can conceptually run in parallel.
//
// Example:
//
// type State struct {
// User User
// Config Config
// }
//
// // These operations are independent and can be combined with ApS
// getUser := func(ctx context.Context) ioeither.IOEither[error, User] {
// return ioeither.TryCatch(func() (User, error) {
// return fetchUser(ctx)
// })
// }
// getConfig := func(ctx context.Context) ioeither.IOEither[error, Config] {
// return ioeither.TryCatch(func() (Config, error) {
// return fetchConfig(ctx)
// })
// }
//
// result := F.Pipe2(
// readerioeither.Do(State{}),
// readerioeither.ApS(
// func(user User) func(State) State {
// return func(s State) State { s.User = user; return s }
// },
// getUser,
// ),
// readerioeither.ApS(
// func(cfg Config) func(State) State {
// return func(s State) State { s.Config = cfg; return s }
// },
// getConfig,
// ),
// )
//
//go:inline
func ApS[S1, S2, T any](
setter func(T) func(S1) S2,
fa ReaderIOResult[T],
) Operator[S1, S2] {
return apply.ApS(
Ap,
Map,
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.
//
// Example:
//
// type State struct {
// User User
// Config Config
// }
//
// userLens := lens.MakeLens(
// func(s State) User { return s.User },
// func(s State, u User) State { s.User = u; return s },
// )
//
// getUser := func(ctx context.Context) ioeither.IOEither[error, User] {
// return ioeither.TryCatch(func() (User, error) {
// return fetchUser(ctx)
// })
// }
// result := F.Pipe2(
// readerioeither.Of(State{}),
// readerioeither.ApSL(userLens, getUser),
// )
//
//go:inline
func ApSL[S, T any](
lens L.Lens[S, T],
fa ReaderIOResult[T],
) Operator[S, S] {
return ApS(lens.Set, fa)
}
// BindL is a variant of Bind that uses a lens to focus on a specific part of the context.
// This provides a more ergonomic API when working with nested structures, eliminating
// the need to manually write setter functions.
//
// The lens parameter provides both a getter and setter for a field of type T within
// the context S. The function f receives the current value of the focused field and
// returns a ReaderIOResult computation that produces an updated value.
//
// Example:
//
// type State struct {
// User User
// Config Config
// }
//
// userLens := lens.MakeLens(
// func(s State) User { return s.User },
// func(s State, u User) State { s.User = u; return s },
// )
//
// result := F.Pipe2(
// readerioeither.Do(State{}),
// readerioeither.BindL(userLens, func(user User) readerioeither.ReaderIOResult[User] {
// return func(ctx context.Context) ioeither.IOEither[error, User] {
// return ioeither.TryCatch(func() (User, error) {
// return fetchUser(ctx)
// })
// }
// }),
// )
//
//go:inline
func BindL[S, T any](
lens L.Lens[S, T],
f Kleisli[T, T],
) Operator[S, S] {
return RIOR.BindL(lens, f)
}
// LetL is a variant of Let that uses a lens to focus on a specific part of the context.
// This provides a more ergonomic API when working with nested structures, eliminating
// the need to manually write setter functions.
//
// The lens parameter provides both a getter and setter for a field of type T within
// the context S. The function f receives the current value of the focused field and
// returns a new value (without wrapping in a ReaderIOResult).
//
// Example:
//
// type State struct {
// User User
// Config Config
// }
//
// userLens := lens.MakeLens(
// func(s State) User { return s.User },
// func(s State, u User) State { s.User = u; return s },
// )
//
// result := F.Pipe2(
// readerioeither.Do(State{User: User{Name: "Alice"}}),
// readerioeither.LetL(userLens, func(user User) User {
// user.Name = "Bob"
// return user
// }),
// )
//
//go:inline
func LetL[S, T any](
lens L.Lens[S, T],
f func(T) T,
) Operator[S, S] {
return RIOR.LetL[context.Context](lens, f)
}
// LetToL is a variant of LetTo that uses a lens to focus on a specific part of the context.
// This provides a more ergonomic API when working with nested structures, eliminating
// the need to manually write setter functions.
//
// The lens parameter provides both a getter and setter for a field of type T within
// the context S. The value b is set directly to the focused field.
//
// Example:
//
// type State struct {
// User User
// Config Config
// }
//
// userLens := lens.MakeLens(
// func(s State) User { return s.User },
// func(s State, u User) State { s.User = u; return s },
// )
//
// newUser := User{Name: "Bob", ID: 123}
// result := F.Pipe2(
// readerioeither.Do(State{}),
// readerioeither.LetToL(userLens, newUser),
// )
//
//go:inline
func LetToL[S, T any](
lens L.Lens[S, T],
b T,
) Operator[S, S] {
return RIOR.LetToL[context.Context](lens, b)
}
// BindIOEitherK is a variant of Bind that works with IOEither computations.
// It lifts an IOEither Kleisli arrow into the ReaderIOResult context (with context.Context as environment).
//
// Parameters:
// - setter: Updates state from S1 to S2 using result T
// - f: An IOEither Kleisli arrow (S1 -> IOEither[error, T])
//
//go:inline
func BindIOEitherK[S1, S2, T any](
setter func(T) func(S1) S2,
f ioresult.Kleisli[S1, T],
) Operator[S1, S2] {
return Bind(setter, F.Flow2(f, FromIOEither[T]))
}
// BindIOResultK is a variant of Bind that works with IOResult computations.
// This is an alias for BindIOEitherK for consistency with the Result naming convention.
//
// Parameters:
// - setter: Updates state from S1 to S2 using result T
// - f: An IOResult Kleisli arrow (S1 -> IOResult[T])
//
//go:inline
func BindIOResultK[S1, S2, T any](
setter func(T) func(S1) S2,
f ioresult.Kleisli[S1, T],
) Operator[S1, S2] {
return Bind(setter, F.Flow2(f, FromIOResult[T]))
}
// BindIOK is a variant of Bind that works with IO computations.
// It lifts an IO Kleisli arrow into the ReaderIOResult context.
//
// Parameters:
// - setter: Updates state from S1 to S2 using result T
// - f: An IO Kleisli arrow (S1 -> IO[T])
//
//go:inline
func BindIOK[S1, S2, T any](
setter func(T) func(S1) S2,
f io.Kleisli[S1, T],
) Operator[S1, S2] {
return Bind(setter, F.Flow2(f, FromIO[T]))
}
// BindReaderK is a variant of Bind that works with Reader computations.
// It lifts a Reader Kleisli arrow (with context.Context) into the ReaderIOResult context.
//
// Parameters:
// - setter: Updates state from S1 to S2 using result T
// - f: A Reader Kleisli arrow (S1 -> Reader[context.Context, T])
//
//go:inline
func BindReaderK[S1, S2, T any](
setter func(T) func(S1) S2,
f reader.Kleisli[context.Context, S1, T],
) Operator[S1, S2] {
return Bind(setter, F.Flow2(f, FromReader[T]))
}
// BindReaderIOK is a variant of Bind that works with ReaderIO computations.
// It lifts a ReaderIO Kleisli arrow (with context.Context) into the ReaderIOResult context.
//
// Parameters:
// - setter: Updates state from S1 to S2 using result T
// - f: A ReaderIO Kleisli arrow (S1 -> ReaderIO[context.Context, T])
//
//go:inline
func BindReaderIOK[S1, S2, T any](
setter func(T) func(S1) S2,
f readerio.Kleisli[context.Context, S1, T],
) Operator[S1, S2] {
return Bind(setter, F.Flow2(f, FromReaderIO[T]))
}
// BindEitherK is a variant of Bind that works with Either (Result) computations.
// It lifts an Either Kleisli arrow into the ReaderIOResult context.
//
// Parameters:
// - setter: Updates state from S1 to S2 using result T
// - f: An Either Kleisli arrow (S1 -> Either[error, T])
//
//go:inline
func BindEitherK[S1, S2, T any](
setter func(T) func(S1) S2,
f result.Kleisli[S1, T],
) Operator[S1, S2] {
return Bind(setter, F.Flow2(f, FromEither[T]))
}
// BindResultK is a variant of Bind that works with Result computations.
// This is an alias for BindEitherK for consistency with the Result naming convention.
//
// Parameters:
// - setter: Updates state from S1 to S2 using result T
// - f: A Result Kleisli arrow (S1 -> Result[T])
//
//go:inline
func BindResultK[S1, S2, T any](
setter func(T) func(S1) S2,
f result.Kleisli[S1, T],
) Operator[S1, S2] {
return Bind(setter, F.Flow2(f, FromResult[T]))
}
// BindIOEitherKL is a lens-based variant of BindIOEitherK.
// It combines a lens with an IOEither Kleisli arrow, focusing on a specific field
// within the state structure.
//
// Parameters:
// - lens: A lens focusing on field T within state S
// - f: An IOEither Kleisli arrow (T -> IOEither[error, T])
//
//go:inline
func BindIOEitherKL[S, T any](
lens L.Lens[S, T],
f ioresult.Kleisli[T, T],
) Operator[S, S] {
return BindL(lens, F.Flow2(f, FromIOEither[T]))
}
// BindIOResultKL is a lens-based variant of BindIOResultK.
// This is an alias for BindIOEitherKL for consistency with the Result naming convention.
//
// Parameters:
// - lens: A lens focusing on field T within state S
// - f: An IOResult Kleisli arrow (T -> IOResult[T])
//
//go:inline
func BindIOResultKL[S, T any](
lens L.Lens[S, T],
f ioresult.Kleisli[T, T],
) Operator[S, S] {
return BindL(lens, F.Flow2(f, FromIOEither[T]))
}
// BindIOKL is a lens-based variant of BindIOK.
// It combines a lens with an IO Kleisli arrow, focusing on a specific field
// within the state structure.
//
// Parameters:
// - lens: A lens focusing on field T within state S
// - f: An IO Kleisli arrow (T -> IO[T])
//
//go:inline
func BindIOKL[S, T any](
lens L.Lens[S, T],
f io.Kleisli[T, T],
) Operator[S, S] {
return BindL(lens, F.Flow2(f, FromIO[T]))
}
// BindReaderKL is a lens-based variant of BindReaderK.
// It combines a lens with a Reader Kleisli arrow (with context.Context), focusing on a specific field
// within the state structure.
//
// Parameters:
// - lens: A lens focusing on field T within state S
// - f: A Reader Kleisli arrow (T -> Reader[context.Context, T])
//
//go:inline
func BindReaderKL[S, T any](
lens L.Lens[S, T],
f reader.Kleisli[context.Context, T, T],
) Operator[S, S] {
return BindL(lens, F.Flow2(f, FromReader[T]))
}
// BindReaderIOKL is a lens-based variant of BindReaderIOK.
// It combines a lens with a ReaderIO Kleisli arrow (with context.Context), focusing on a specific field
// within the state structure.
//
// Parameters:
// - lens: A lens focusing on field T within state S
// - f: A ReaderIO Kleisli arrow (T -> ReaderIO[context.Context, T])
//
//go:inline
func BindReaderIOKL[S, T any](
lens L.Lens[S, T],
f readerio.Kleisli[context.Context, T, T],
) Operator[S, S] {
return BindL(lens, F.Flow2(f, FromReaderIO[T]))
}
// ApIOEitherS is an applicative variant that works with IOEither values.
// Unlike BindIOEitherK, this uses applicative composition (ApS) instead of monadic
// composition (Bind), allowing independent computations to be combined.
//
// Parameters:
// - setter: Updates state from S1 to S2 using result T
// - fa: An IOEither value
//
//go:inline
func ApIOEitherS[S1, S2, T any](
setter func(T) func(S1) S2,
fa IOResult[T],
) Operator[S1, S2] {
return F.Bind2nd(F.Flow2[ReaderIOResult[S1], ioresult.Operator[S1, S2]], ioeither.ApS(setter, fa))
}
// ApIOResultS is an applicative variant that works with IOResult values.
// This is an alias for ApIOEitherS for consistency with the Result naming convention.
//
// Parameters:
// - setter: Updates state from S1 to S2 using result T
// - fa: An IOResult value
//
//go:inline
func ApIOResultS[S1, S2, T any](
setter func(T) func(S1) S2,
fa IOResult[T],
) Operator[S1, S2] {
return F.Bind2nd(F.Flow2[ReaderIOResult[S1], ioresult.Operator[S1, S2]], ioeither.ApS(setter, fa))
}
// ApIOS is an applicative variant that works with IO values.
// It lifts an IO value into the ReaderIOResult context using applicative composition.
//
// Parameters:
// - setter: Updates state from S1 to S2 using result T
// - fa: An IO value
//
//go:inline
func ApIOS[S1, S2, T any](
setter func(T) func(S1) S2,
fa IO[T],
) Operator[S1, S2] {
return ApS(setter, FromIO(fa))
}
// ApReaderS is an applicative variant that works with Reader values.
// It lifts a Reader value (with context.Context) into the ReaderIOResult context using applicative composition.
//
// Parameters:
// - setter: Updates state from S1 to S2 using result T
// - fa: A Reader value
//
//go:inline
func ApReaderS[S1, S2, T any](
setter func(T) func(S1) S2,
fa Reader[context.Context, T],
) Operator[S1, S2] {
return ApS(setter, FromReader(fa))
}
// ApReaderIOS is an applicative variant that works with ReaderIO values.
// It lifts a ReaderIO value (with context.Context) into the ReaderIOResult context using applicative composition.
//
// Parameters:
// - setter: Updates state from S1 to S2 using result T
// - fa: A ReaderIO value
//
//go:inline
func ApReaderIOS[S1, S2, T any](
setter func(T) func(S1) S2,
fa ReaderIO[T],
) Operator[S1, S2] {
return ApS(setter, FromReaderIO(fa))
}
// ApEitherS is an applicative variant that works with Either (Result) values.
// It lifts an Either value into the ReaderIOResult context using applicative composition.
//
// Parameters:
// - setter: Updates state from S1 to S2 using result T
// - fa: An Either value
//
//go:inline
func ApEitherS[S1, S2, T any](
setter func(T) func(S1) S2,
fa Result[T],
) Operator[S1, S2] {
return ApS(setter, FromEither(fa))
}
// ApResultS is an applicative variant that works with Result values.
// This is an alias for ApEitherS for consistency with the Result naming convention.
//
// Parameters:
// - setter: Updates state from S1 to S2 using result T
// - fa: A Result value
//
//go:inline
func ApResultS[S1, S2, T any](
setter func(T) func(S1) S2,
fa Result[T],
) Operator[S1, S2] {
return ApS(setter, FromResult(fa))
}
// ApIOEitherSL is a lens-based variant of ApIOEitherS.
// It combines a lens with an IOEither value using applicative composition.
//
// Parameters:
// - lens: A lens focusing on field T within state S
// - fa: An IOEither value
//
//go:inline
func ApIOEitherSL[S, T any](
lens L.Lens[S, T],
fa IOResult[T],
) Operator[S, S] {
return F.Bind2nd(F.Flow2[ReaderIOResult[S], ioresult.Operator[S, S]], ioresult.ApSL(lens, fa))
}
// ApIOResultSL is a lens-based variant of ApIOResultS.
// This is an alias for ApIOEitherSL for consistency with the Result naming convention.
//
// Parameters:
// - lens: A lens focusing on field T within state S
// - fa: An IOResult value
//
//go:inline
func ApIOResultSL[S, T any](
lens L.Lens[S, T],
fa IOResult[T],
) Operator[S, S] {
return F.Bind2nd(F.Flow2[ReaderIOResult[S], ioresult.Operator[S, S]], ioresult.ApSL(lens, fa))
}
// ApIOSL is a lens-based variant of ApIOS.
// It combines a lens with an IO value using applicative composition.
//
// Parameters:
// - lens: A lens focusing on field T within state S
// - fa: An IO value
//
//go:inline
func ApIOSL[S, T any](
lens L.Lens[S, T],
fa IO[T],
) Operator[S, S] {
return ApSL(lens, FromIO(fa))
}
// ApReaderSL is a lens-based variant of ApReaderS.
// It combines a lens with a Reader value (with context.Context) using applicative composition.
//
// Parameters:
// - lens: A lens focusing on field T within state S
// - fa: A Reader value
//
//go:inline
func ApReaderSL[S, T any](
lens L.Lens[S, T],
fa Reader[context.Context, T],
) Operator[S, S] {
return ApSL(lens, FromReader(fa))
}
// ApReaderIOSL is a lens-based variant of ApReaderIOS.
// It combines a lens with a ReaderIO value (with context.Context) using applicative composition.
//
// Parameters:
// - lens: A lens focusing on field T within state S
// - fa: A ReaderIO value
//
//go:inline
func ApReaderIOSL[S, T any](
lens L.Lens[S, T],
fa ReaderIO[T],
) Operator[S, S] {
return ApSL(lens, FromReaderIO(fa))
}
// ApEitherSL is a lens-based variant of ApEitherS.
// It combines a lens with an Either value using applicative composition.
//
// Parameters:
// - lens: A lens focusing on field T within state S
// - fa: An Either value
//
//go:inline
func ApEitherSL[S, T any](
lens L.Lens[S, T],
fa Result[T],
) Operator[S, S] {
return ApSL(lens, FromEither(fa))
}
// ApResultSL is a lens-based variant of ApResultS.
// This is an alias for ApEitherSL for consistency with the Result naming convention.
//
// Parameters:
// - lens: A lens focusing on field T within state S
// - fa: A Result value
//
//go:inline
func ApResultSL[S, T any](
lens L.Lens[S, T],
fa Result[T],
) Operator[S, S] {
return ApSL(lens, FromResult(fa))
}

View File

@@ -0,0 +1,275 @@
// 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 readerioresult
import (
"context"
"testing"
E "github.com/IBM/fp-go/v2/either"
F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/internal/utils"
O "github.com/IBM/fp-go/v2/option"
"github.com/stretchr/testify/assert"
)
func getLastName(s utils.Initial) ReaderIOResult[string] {
return Of("Doe")
}
func getGivenName(s utils.WithLastName) ReaderIOResult[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(t.Context())(), E.Of[error]("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(t.Context())(), E.Of[error]("John Doe"))
}
func TestApS_WithError(t *testing.T) {
// Test that ApS propagates errors correctly
testErr := assert.AnError
res := F.Pipe3(
Do(utils.Empty),
ApS(utils.SetLastName, Left[string](testErr)),
ApS(utils.SetGivenName, Of("John")),
Map(utils.GetFullName),
)
result := res(t.Context())()
assert.True(t, E.IsLeft(result))
assert.Equal(t, testErr, E.ToError(result))
}
func TestApS_WithSecondError(t *testing.T) {
// Test that ApS propagates errors from the second operation
testErr := assert.AnError
res := F.Pipe3(
Do(utils.Empty),
ApS(utils.SetLastName, Of("Doe")),
ApS(utils.SetGivenName, Left[string](testErr)),
Map(utils.GetFullName),
)
result := res(t.Context())()
assert.True(t, E.IsLeft(result))
assert.Equal(t, testErr, E.ToError(result))
}
func TestApS_MultipleFields(t *testing.T) {
// Test ApS with more than two fields
type Person struct {
FirstName string
MiddleName string
LastName string
Age int
}
setFirstName := func(s string) func(Person) Person {
return func(p Person) Person {
p.FirstName = s
return p
}
}
setMiddleName := func(s string) func(Person) Person {
return func(p Person) Person {
p.MiddleName = s
return p
}
}
setLastName := func(s string) func(Person) Person {
return func(p Person) Person {
p.LastName = s
return p
}
}
setAge := func(a int) func(Person) Person {
return func(p Person) Person {
p.Age = a
return p
}
}
res := F.Pipe5(
Do(Person{}),
ApS(setFirstName, Of("John")),
ApS(setMiddleName, Of("Q")),
ApS(setLastName, Of("Doe")),
ApS(setAge, Of(42)),
Map(func(p Person) Person { return p }),
)
result := res(t.Context())()
assert.True(t, E.IsRight(result))
person := E.ToOption(result)
assert.True(t, O.IsSome(person))
p, _ := O.Unwrap(person)
assert.Equal(t, "John", p.FirstName)
assert.Equal(t, "Q", p.MiddleName)
assert.Equal(t, "Doe", p.LastName)
assert.Equal(t, 42, p.Age)
}
func TestApS_WithDifferentTypes(t *testing.T) {
// Test ApS with different value types
type State struct {
Name string
Count int
Flag bool
}
setName := func(s string) func(State) State {
return func(st State) State {
st.Name = s
return st
}
}
setCount := func(c int) func(State) State {
return func(st State) State {
st.Count = c
return st
}
}
setFlag := func(f bool) func(State) State {
return func(st State) State {
st.Flag = f
return st
}
}
res := F.Pipe4(
Do(State{}),
ApS(setName, Of("test")),
ApS(setCount, Of(100)),
ApS(setFlag, Of(true)),
Map(func(s State) State { return s }),
)
result := res(t.Context())()
assert.True(t, E.IsRight(result))
stateOpt := E.ToOption(result)
assert.True(t, O.IsSome(stateOpt))
state, _ := O.Unwrap(stateOpt)
assert.Equal(t, "test", state.Name)
assert.Equal(t, 100, state.Count)
assert.True(t, state.Flag)
}
func TestApS_EmptyState(t *testing.T) {
// Test ApS starting with an empty state
type Empty struct{}
res := Do(Empty{})
result := res(t.Context())()
assert.True(t, E.IsRight(result))
emptyOpt := E.ToOption(result)
assert.True(t, O.IsSome(emptyOpt))
empty, _ := O.Unwrap(emptyOpt)
assert.Equal(t, Empty{}, empty)
}
func TestApS_ChainedWithBind(t *testing.T) {
// Test mixing ApS with Bind operations
type State struct {
Independent string
Dependent string
}
setIndependent := func(s string) func(State) State {
return func(st State) State {
st.Independent = s
return st
}
}
setDependent := func(s string) func(State) State {
return func(st State) State {
st.Dependent = s
return st
}
}
getDependentValue := func(s State) ReaderIOResult[string] {
// This depends on the Independent field
return Of(s.Independent + "-dependent")
}
res := F.Pipe3(
Do(State{}),
ApS(setIndependent, Of("value")),
Bind(setDependent, getDependentValue),
Map(func(s State) State { return s }),
)
result := res(t.Context())()
assert.True(t, E.IsRight(result))
stateOpt := E.ToOption(result)
assert.True(t, O.IsSome(stateOpt))
state, _ := O.Unwrap(stateOpt)
assert.Equal(t, "value", state.Independent)
assert.Equal(t, "value-dependent", state.Dependent)
}
func TestApS_WithContextCancellation(t *testing.T) {
// Test that ApS respects context cancellation
type State struct {
Value string
}
setValue := func(s string) func(State) State {
return func(st State) State {
st.Value = s
return st
}
}
// Create a computation that would succeed
computation := ApS(setValue, Of("test"))(Do(State{}))
// Create a cancelled context
ctx, cancel := context.WithCancel(t.Context())
cancel()
result := computation(ctx)()
assert.True(t, E.IsLeft(result))
}

View File

@@ -13,13 +13,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package readerioeither package readerioresult
import ( import (
"context" RIOR "github.com/IBM/fp-go/v2/readerioresult"
"github.com/IBM/fp-go/v2/internal/bracket"
"github.com/IBM/fp-go/v2/readerio"
) )
// Bracket makes sure that a resource is cleaned up in the event of an error. The release action is called regardless of // Bracket makes sure that a resource is cleaned up in the event of an error. The release action is called regardless of
@@ -27,18 +24,9 @@ import (
func Bracket[ func Bracket[
A, B, ANY any]( A, B, ANY any](
acquire ReaderIOEither[A], acquire ReaderIOResult[A],
use func(A) ReaderIOEither[B], use Kleisli[A, B],
release func(A, Either[B]) ReaderIOEither[ANY], release func(A, Either[B]) ReaderIOResult[ANY],
) ReaderIOEither[B] { ) ReaderIOResult[B] {
return bracket.Bracket[ReaderIOEither[A], ReaderIOEither[B], ReaderIOEither[ANY], Either[B], A, B]( return RIOR.Bracket(acquire, use, release)
readerio.Of[context.Context, Either[B]],
MonadChain[A, B],
readerio.MonadChain[context.Context, Either[B], Either[B]],
MonadChain[ANY, B],
acquire,
use,
release,
)
} }

View File

@@ -13,26 +13,26 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package readerioeither package readerioresult
import ( import (
"context" "context"
CIOE "github.com/IBM/fp-go/v2/context/ioeither" CIOE "github.com/IBM/fp-go/v2/context/ioresult"
"github.com/IBM/fp-go/v2/ioeither" "github.com/IBM/fp-go/v2/ioeither"
) )
// WithContext wraps an existing [ReaderIOEither] and performs a context check for cancellation before delegating. // WithContext wraps an existing [ReaderIOResult] and performs a context check for cancellation before delegating.
// This ensures that if the context is already canceled, the computation short-circuits immediately // This ensures that if the context is already canceled, the computation short-circuits immediately
// without executing the wrapped computation. // without executing the wrapped computation.
// //
// This is useful for adding cancellation awareness to computations that might not check the context themselves. // This is useful for adding cancellation awareness to computations that might not check the context themselves.
// //
// Parameters: // Parameters:
// - ma: The ReaderIOEither to wrap with context checking // - ma: The ReaderIOResult to wrap with context checking
// //
// Returns a ReaderIOEither that checks for cancellation before executing. // Returns a ReaderIOResult that checks for cancellation before executing.
func WithContext[A any](ma ReaderIOEither[A]) ReaderIOEither[A] { func WithContext[A any](ma ReaderIOResult[A]) ReaderIOResult[A] {
return func(ctx context.Context) IOEither[A] { return func(ctx context.Context) IOEither[A] {
if err := context.Cause(ctx); err != nil { if err := context.Cause(ctx); err != nil {
return ioeither.Left[A](err) return ioeither.Left[A](err)

View File

@@ -0,0 +1,251 @@
mode: set
github.com/IBM/fp-go/v2/context/readerioresult/bind.go:27.21,29.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/bind.go:35.47,42.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/bind.go:48.47,54.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/bind.go:60.47,66.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/bind.go:71.46,76.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/bind.go:82.47,89.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/bracket.go:33.21,44.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/cancel.go:35.65,36.47 1 1
github.com/IBM/fp-go/v2/context/readerioresult/cancel.go:36.47,37.44 1 1
github.com/IBM/fp-go/v2/context/readerioresult/cancel.go:37.44,39.4 1 1
github.com/IBM/fp-go/v2/context/readerioresult/cancel.go:40.3,40.40 1 1
github.com/IBM/fp-go/v2/context/readerioresult/eq.go:42.84,44.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:18.91,20.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:24.93,26.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:30.101,32.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:36.103,38.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:43.36,48.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:53.36,58.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:63.36,68.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:71.98,76.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:79.101,84.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:87.101,92.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:95.129,96.68 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:96.68,102.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:106.132,107.68 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:107.68,113.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:117.132,118.68 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:118.68,124.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:129.113,131.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:135.115,137.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:143.40,150.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:156.40,163.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:169.40,176.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:179.126,185.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:188.129,194.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:197.129,203.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:206.185,207.76 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:207.76,215.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:219.188,220.76 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:220.76,228.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:232.188,233.76 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:233.76,241.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:246.125,248.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:252.127,254.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:261.44,270.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:277.44,286.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:293.44,302.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:305.154,312.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:315.157,322.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:325.157,332.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:335.241,336.84 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:336.84,346.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:350.244,351.84 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:351.84,361.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:365.244,366.84 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:366.84,376.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:381.137,383.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:387.139,389.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:397.48,408.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:416.48,427.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:435.48,446.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:449.182,457.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:460.185,468.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:471.185,479.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:482.297,483.92 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:483.92,495.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:499.300,500.92 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:500.92,512.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:516.300,517.92 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:517.92,529.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:534.149,536.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:540.151,542.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:551.52,564.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:573.52,586.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:595.52,608.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:611.210,620.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:623.213,632.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:635.213,644.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:647.353,648.100 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:648.100,662.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:666.356,667.100 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:667.100,681.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:685.356,686.100 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:686.100,700.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:705.161,707.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:711.163,713.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:723.56,738.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:748.56,763.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:773.56,788.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:791.238,801.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:804.241,814.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:817.241,827.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:830.409,831.108 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:831.108,847.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:851.412,852.108 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:852.108,868.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:872.412,873.108 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:873.108,889.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:894.173,896.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:900.175,902.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:913.60,930.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:941.60,958.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:969.60,986.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:989.266,1000.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1003.269,1014.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1017.269,1028.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1031.465,1032.116 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1032.116,1050.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1054.468,1055.116 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1055.116,1073.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1077.468,1078.116 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1078.116,1096.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1101.185,1103.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1107.187,1109.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1121.64,1140.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1152.64,1171.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1183.64,1202.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1205.294,1217.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1220.297,1232.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1235.297,1247.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1250.521,1251.124 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1251.124,1271.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1275.524,1276.124 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1276.124,1296.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1300.524,1301.124 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1301.124,1321.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1326.197,1328.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1332.199,1334.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1347.68,1368.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1381.68,1402.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1415.68,1436.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1439.322,1452.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1455.325,1468.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1471.325,1484.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1487.577,1488.132 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1488.132,1510.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1514.580,1515.132 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1515.132,1537.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1541.580,1542.132 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1542.132,1564.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1569.210,1571.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1575.212,1577.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1591.74,1614.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1628.74,1651.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1665.74,1688.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1691.356,1705.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1708.359,1722.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1725.359,1739.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1742.645,1743.144 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1743.144,1767.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1771.648,1772.144 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1772.144,1796.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1800.648,1801.144 1 0
github.com/IBM/fp-go/v2/context/readerioresult/gen.go:1801.144,1825.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/monoid.go:36.61,43.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/monoid.go:52.64,59.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/monoid.go:68.64,75.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/monoid.go:85.61,93.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/monoid.go:103.63,108.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:42.55,44.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:52.45,54.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:62.42,64.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:74.78,76.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:85.75,87.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:97.72,99.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:108.69,110.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:120.96,122.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:131.93,133.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:143.101,145.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:154.71,156.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:165.39,167.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:169.93,173.56 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:173.56,174.32 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:174.32,174.47 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:189.98,194.47 3 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:194.47,196.44 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:196.44,198.4 1 0
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:200.3,200.27 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:200.27,202.45 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:202.45,204.5 1 0
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:207.4,213.47 5 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:227.95,229.17 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:229.17,231.3 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:232.2,232.28 1 0
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:243.98,245.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:254.91,256.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:265.94,267.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:276.94,278.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:288.95,290.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:299.73,301.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:307.44,309.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:319.95,321.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:330.95,332.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:342.100,344.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:353.100,355.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:364.116,366.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:375.75,377.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:386.47,388.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:398.51,400.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:406.39,407.47 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:407.47,408.27 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:408.27,411.4 2 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:423.87,425.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:434.87,436.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:446.92,448.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:457.92,459.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:468.115,470.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:479.85,480.54 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:480.54,481.48 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:481.48,482.28 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:482.28,487.12 3 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:488.30,489.22 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:490.23,491.47 1 0
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:505.59,511.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:520.66,522.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:531.83,533.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:543.97,545.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:554.64,556.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:566.62,568.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:577.78,579.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:589.80,591.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:600.76,602.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:612.136,614.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:623.91,625.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/reader.go:634.71,636.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/resource.go:58.151,63.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/semigroup.go:39.41,43.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/sync.go:46.78,54.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/traverse.go:31.89,39.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/traverse.go:48.103,56.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/traverse.go:65.71,67.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/traverse.go:75.112,83.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/traverse.go:92.124,100.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/traverse.go:108.94,110.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/traverse.go:120.95,128.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/traverse.go:137.92,145.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/traverse.go:148.106,156.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/traverse.go:165.74,167.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/traverse.go:170.118,178.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/traverse.go:181.115,189.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/traverse.go:192.127,200.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/traverse.go:203.97,205.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/traverse.go:215.95,223.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/traverse.go:232.92,240.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/traverse.go:243.106,251.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/traverse.go:260.74,262.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/traverse.go:265.115,273.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/traverse.go:276.127,284.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/traverse.go:287.118,295.2 1 0
github.com/IBM/fp-go/v2/context/readerioresult/traverse.go:304.97,306.2 1 0

View File

@@ -13,13 +13,13 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// Package readerioeither provides a specialized version of [readerioeither.ReaderIOEither] that uses // package readerioresult provides a specialized version of [readerioeither.ReaderIOResult] that uses
// [context.Context] as the context type and [error] as the left (error) type. This package is designed // [context.Context] as the context type and [error] as the left (error) type. This package is designed
// for typical Go applications where context-aware, effectful computations with error handling are needed. // for typical Go applications where context-aware, effectful computations with error handling are needed.
// //
// # Core Concept // # Core Concept
// //
// ReaderIOEither[A] represents a computation that: // ReaderIOResult[A] represents a computation that:
// - Depends on a [context.Context] (Reader aspect) // - Depends on a [context.Context] (Reader aspect)
// - Performs side effects (IO aspect) // - Performs side effects (IO aspect)
// - Can fail with an [error] (Either aspect) // - Can fail with an [error] (Either aspect)
@@ -27,7 +27,7 @@
// //
// The type is defined as: // The type is defined as:
// //
// ReaderIOEither[A] = func(context.Context) func() Either[error, A] // ReaderIOResult[A] = func(context.Context) func() Either[error, A]
// //
// This combines three powerful functional programming concepts: // This combines three powerful functional programming concepts:
// - Reader: Dependency injection via context // - Reader: Dependency injection via context
@@ -50,7 +50,7 @@
// - [Left]: Create failed computations // - [Left]: Create failed computations
// - [FromEither], [FromIO], [FromIOEither]: Convert from other types // - [FromEither], [FromIO], [FromIOEither]: Convert from other types
// - [TryCatch]: Wrap error-returning functions // - [TryCatch]: Wrap error-returning functions
// - [Eitherize0-10]: Convert standard Go functions to ReaderIOEither // - [Eitherize0-10]: Convert standard Go functions to ReaderIOResult
// //
// Transformation: // Transformation:
// - [Map]: Transform success values // - [Map]: Transform success values
@@ -90,15 +90,15 @@
// import ( // import (
// "context" // "context"
// "fmt" // "fmt"
// RIOE "github.com/IBM/fp-go/v2/context/readerioeither" // RIOE "github.com/IBM/fp-go/v2/context/readerioresult"
// F "github.com/IBM/fp-go/v2/function" // F "github.com/IBM/fp-go/v2/function"
// ) // )
// //
// // Define a computation that reads from context and may fail // // Define a computation that reads from context and may fail
// func fetchUser(id string) RIOE.ReaderIOEither[User] { // func fetchUser(id string) RIOE.ReaderIOResult[User] {
// return F.Pipe2( // return F.Pipe2(
// RIOE.Ask(), // RIOE.Ask(),
// RIOE.Chain(func(ctx context.Context) RIOE.ReaderIOEither[User] { // RIOE.Chain(func(ctx context.Context) RIOE.ReaderIOResult[User] {
// return RIOE.TryCatch(func(ctx context.Context) func() (User, error) { // return RIOE.TryCatch(func(ctx context.Context) func() (User, error) {
// return func() (User, error) { // return func() (User, error) {
// return userService.Get(ctx, id) // return userService.Get(ctx, id)
@@ -138,8 +138,8 @@
// openFile("data.txt"), // openFile("data.txt"),
// closeFile, // closeFile,
// ), // ),
// func(use func(func(*os.File) RIOE.ReaderIOEither[string]) RIOE.ReaderIOEither[string]) RIOE.ReaderIOEither[string] { // func(use func(func(*os.File) RIOE.ReaderIOResult[string]) RIOE.ReaderIOResult[string]) RIOE.ReaderIOResult[string] {
// return use(func(file *os.File) RIOE.ReaderIOEither[string] { // return use(func(file *os.File) RIOE.ReaderIOResult[string] {
// return readContent(file) // return readContent(file)
// }) // })
// }, // },
@@ -166,4 +166,4 @@
// result := computation(ctx)() // Returns Left with cancellation error // result := computation(ctx)() // Returns Left with cancellation error
// //
//go:generate go run ../.. contextreaderioeither --count 10 --filename gen.go //go:generate go run ../.. contextreaderioeither --count 10 --filename gen.go
package readerioeither package readerioresult

View File

@@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package readerioeither package readerioresult
import ( import (
"context" "context"
@@ -22,14 +22,14 @@ import (
RIOE "github.com/IBM/fp-go/v2/readerioeither" RIOE "github.com/IBM/fp-go/v2/readerioeither"
) )
// Eq implements the equals predicate for values contained in the [ReaderIOEither] monad. // Eq implements the equals predicate for values contained in the [ReaderIOResult] monad.
// It creates an equality checker that can compare two ReaderIOEither values by executing them // It creates an equality checker that can compare two ReaderIOResult values by executing them
// with a given context and comparing their results using the provided Either equality checker. // with a given context and comparing their results using the provided Either equality checker.
// //
// Parameters: // Parameters:
// - eq: Equality checker for Either[A] values // - eq: Equality checker for Either[A] values
// //
// Returns a function that takes a context and returns an equality checker for ReaderIOEither[A]. // Returns a function that takes a context and returns an equality checker for ReaderIOResult[A].
// //
// Example: // Example:
// //
@@ -39,6 +39,8 @@ import (
// eqRIE := Eq(eqInt) // eqRIE := Eq(eqInt)
// ctx := context.Background() // ctx := context.Background()
// equal := eqRIE(ctx).Equals(Right[int](42), Right[int](42)) // true // equal := eqRIE(ctx).Equals(Right[int](42), Right[int](42)) // true
func Eq[A any](eq eq.Eq[Either[A]]) func(context.Context) eq.Eq[ReaderIOEither[A]] { //
//go:inline
func Eq[A any](eq eq.Eq[Either[A]]) func(context.Context) eq.Eq[ReaderIOResult[A]] {
return RIOE.Eq[context.Context](eq) return RIOE.Eq[context.Context](eq)
} }

View File

@@ -18,7 +18,7 @@ package exec
import ( import (
"context" "context"
RIOE "github.com/IBM/fp-go/v2/context/readerioeither" RIOE "github.com/IBM/fp-go/v2/context/readerioresult"
"github.com/IBM/fp-go/v2/exec" "github.com/IBM/fp-go/v2/exec"
F "github.com/IBM/fp-go/v2/function" F "github.com/IBM/fp-go/v2/function"
GE "github.com/IBM/fp-go/v2/internal/exec" GE "github.com/IBM/fp-go/v2/internal/exec"
@@ -30,7 +30,7 @@ var (
Command = F.Curry3(command) Command = F.Curry3(command)
) )
func command(name string, args []string, in []byte) RIOE.ReaderIOEither[exec.CommandOutput] { func command(name string, args []string, in []byte) RIOE.ReaderIOResult[exec.CommandOutput] {
return func(ctx context.Context) IOE.IOEither[error, exec.CommandOutput] { return func(ctx context.Context) IOE.IOEither[error, exec.CommandOutput] {
return IOE.TryCatchError(func() (exec.CommandOutput, error) { return IOE.TryCatchError(func() (exec.CommandOutput, error) {
return GE.Exec(ctx, name, args, in) return GE.Exec(ctx, name, args, in)

View File

@@ -20,7 +20,7 @@ import (
"io" "io"
"os" "os"
RIOE "github.com/IBM/fp-go/v2/context/readerioeither" RIOE "github.com/IBM/fp-go/v2/context/readerioresult"
ET "github.com/IBM/fp-go/v2/either" ET "github.com/IBM/fp-go/v2/either"
F "github.com/IBM/fp-go/v2/function" F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/internal/file" "github.com/IBM/fp-go/v2/internal/file"
@@ -44,7 +44,7 @@ var (
) )
// Close closes an object // Close closes an object
func Close[C io.Closer](c C) RIOE.ReaderIOEither[any] { func Close[C io.Closer](c C) RIOE.ReaderIOResult[any] {
return F.Pipe2( return F.Pipe2(
c, c,
IOEF.Close[C], IOEF.Close[C],
@@ -53,8 +53,8 @@ func Close[C io.Closer](c C) RIOE.ReaderIOEither[any] {
} }
// ReadFile reads a file in the scope of a context // ReadFile reads a file in the scope of a context
func ReadFile(path string) RIOE.ReaderIOEither[[]byte] { func ReadFile(path string) RIOE.ReaderIOResult[[]byte] {
return RIOE.WithResource[[]byte](Open(path), Close[*os.File])(func(r *os.File) RIOE.ReaderIOEither[[]byte] { return RIOE.WithResource[[]byte](Open(path), Close[*os.File])(func(r *os.File) RIOE.ReaderIOResult[[]byte] {
return func(ctx context.Context) IOE.IOEither[error, []byte] { return func(ctx context.Context) IOE.IOEither[error, []byte] {
return func() ET.Either[error, []byte] { return func() ET.Either[error, []byte] {
return file.ReadAll(ctx, r) return file.ReadAll(ctx, r)

View File

@@ -19,7 +19,7 @@ import (
"context" "context"
"fmt" "fmt"
R "github.com/IBM/fp-go/v2/context/readerioeither" R "github.com/IBM/fp-go/v2/context/readerioresult"
F "github.com/IBM/fp-go/v2/function" F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/io" "github.com/IBM/fp-go/v2/io"
J "github.com/IBM/fp-go/v2/json" J "github.com/IBM/fp-go/v2/json"

View File

@@ -18,7 +18,7 @@ package file
import ( import (
"os" "os"
RIOE "github.com/IBM/fp-go/v2/context/readerioeither" RIOE "github.com/IBM/fp-go/v2/context/readerioresult"
F "github.com/IBM/fp-go/v2/function" F "github.com/IBM/fp-go/v2/function"
IO "github.com/IBM/fp-go/v2/io" IO "github.com/IBM/fp-go/v2/io"
IOF "github.com/IBM/fp-go/v2/io/file" IOF "github.com/IBM/fp-go/v2/io/file"
@@ -38,7 +38,7 @@ var (
) )
// CreateTemp created a temp file with proper parametrization // CreateTemp created a temp file with proper parametrization
func CreateTemp(dir, pattern string) RIOE.ReaderIOEither[*os.File] { func CreateTemp(dir, pattern string) RIOE.ReaderIOResult[*os.File] {
return F.Pipe2( return F.Pipe2(
IOEF.CreateTemp(dir, pattern), IOEF.CreateTemp(dir, pattern),
RIOE.FromIOEither[*os.File], RIOE.FromIOEither[*os.File],
@@ -47,6 +47,6 @@ func CreateTemp(dir, pattern string) RIOE.ReaderIOEither[*os.File] {
} }
// WithTempFile creates a temporary file, then invokes a callback to create a resource based on the file, then close and remove the temp file // WithTempFile creates a temporary file, then invokes a callback to create a resource based on the file, then close and remove the temp file
func WithTempFile[A any](f func(*os.File) RIOE.ReaderIOEither[A]) RIOE.ReaderIOEither[A] { func WithTempFile[A any](f func(*os.File) RIOE.ReaderIOResult[A]) RIOE.ReaderIOResult[A] {
return RIOE.WithResource[A](onCreateTempFile, onReleaseTempFile)(f) return RIOE.WithResource[A](onCreateTempFile, onReleaseTempFile)(f)
} }

View File

@@ -20,7 +20,7 @@ import (
"os" "os"
"testing" "testing"
RIOE "github.com/IBM/fp-go/v2/context/readerioeither" RIOE "github.com/IBM/fp-go/v2/context/readerioresult"
E "github.com/IBM/fp-go/v2/either" E "github.com/IBM/fp-go/v2/either"
F "github.com/IBM/fp-go/v2/function" F "github.com/IBM/fp-go/v2/function"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -35,7 +35,7 @@ func TestWithTempFile(t *testing.T) {
func TestWithTempFileOnClosedFile(t *testing.T) { func TestWithTempFileOnClosedFile(t *testing.T) {
res := WithTempFile(func(f *os.File) RIOE.ReaderIOEither[[]byte] { res := WithTempFile(func(f *os.File) RIOE.ReaderIOResult[[]byte] {
return F.Pipe2( return F.Pipe2(
f, f,
onWriteAll[*os.File]([]byte("Carsten")), onWriteAll[*os.File]([]byte("Carsten")),

View File

@@ -19,12 +19,12 @@ import (
"context" "context"
"io" "io"
RIOE "github.com/IBM/fp-go/v2/context/readerioeither" RIOE "github.com/IBM/fp-go/v2/context/readerioresult"
F "github.com/IBM/fp-go/v2/function" F "github.com/IBM/fp-go/v2/function"
) )
func onWriteAll[W io.Writer](data []byte) func(w W) RIOE.ReaderIOEither[[]byte] { func onWriteAll[W io.Writer](data []byte) func(w W) RIOE.ReaderIOResult[[]byte] {
return func(w W) RIOE.ReaderIOEither[[]byte] { return func(w W) RIOE.ReaderIOResult[[]byte] {
return F.Pipe1( return F.Pipe1(
RIOE.TryCatch(func(_ context.Context) func() ([]byte, error) { RIOE.TryCatch(func(_ context.Context) func() ([]byte, error) {
return func() ([]byte, error) { return func() ([]byte, error) {
@@ -38,9 +38,9 @@ func onWriteAll[W io.Writer](data []byte) func(w W) RIOE.ReaderIOEither[[]byte]
} }
// WriteAll uses a generator function to create a stream, writes data to it and closes it // WriteAll uses a generator function to create a stream, writes data to it and closes it
func WriteAll[W io.WriteCloser](data []byte) func(acquire RIOE.ReaderIOEither[W]) RIOE.ReaderIOEither[[]byte] { func WriteAll[W io.WriteCloser](data []byte) func(acquire RIOE.ReaderIOResult[W]) RIOE.ReaderIOResult[[]byte] {
onWrite := onWriteAll[W](data) onWrite := onWriteAll[W](data)
return func(onCreate RIOE.ReaderIOEither[W]) RIOE.ReaderIOEither[[]byte] { return func(onCreate RIOE.ReaderIOResult[W]) RIOE.ReaderIOResult[[]byte] {
return RIOE.WithResource[[]byte]( return RIOE.WithResource[[]byte](
onCreate, onCreate,
Close[W])( Close[W])(
@@ -50,7 +50,7 @@ func WriteAll[W io.WriteCloser](data []byte) func(acquire RIOE.ReaderIOEither[W]
} }
// Write uses a generator function to create a stream, writes data to it and closes it // Write uses a generator function to create a stream, writes data to it and closes it
func Write[R any, W io.WriteCloser](acquire RIOE.ReaderIOEither[W]) func(use func(W) RIOE.ReaderIOEither[R]) RIOE.ReaderIOEither[R] { func Write[R any, W io.WriteCloser](acquire RIOE.ReaderIOResult[W]) func(use func(W) RIOE.ReaderIOResult[R]) RIOE.ReaderIOResult[R] {
return RIOE.WithResource[R]( return RIOE.WithResource[R](
acquire, acquire,
Close[W]) Close[W])

File diff suppressed because it is too large Load Diff

View File

@@ -14,12 +14,12 @@
// limitations under the License. // limitations under the License.
// Package builder provides utilities for building HTTP requests in a functional way // Package builder provides utilities for building HTTP requests in a functional way
// using the ReaderIOEither monad. It integrates with the http/builder package to // using the ReaderIOResult monad. It integrates with the http/builder package to
// create composable, type-safe HTTP request builders with proper error handling // create composable, type-safe HTTP request builders with proper error handling
// and context support. // and context support.
// //
// The main function, Requester, converts a Builder from the http/builder package // The main function, Requester, converts a Builder from the http/builder package
// into a ReaderIOEither that produces HTTP requests. This allows for: // into a ReaderIOResult that produces HTTP requests. This allows for:
// - Immutable request building with method chaining // - Immutable request building with method chaining
// - Automatic header management including Content-Length // - Automatic header management including Content-Length
// - Support for requests with and without bodies // - Support for requests with and without bodies
@@ -31,7 +31,7 @@
// import ( // import (
// "context" // "context"
// B "github.com/IBM/fp-go/v2/http/builder" // B "github.com/IBM/fp-go/v2/http/builder"
// RB "github.com/IBM/fp-go/v2/context/readerioeither/http/builder" // RB "github.com/IBM/fp-go/v2/context/readerioresult/http/builder"
// ) // )
// //
// builder := F.Pipe3( // builder := F.Pipe3(
@@ -51,8 +51,8 @@ import (
"net/http" "net/http"
"strconv" "strconv"
RIOE "github.com/IBM/fp-go/v2/context/readerioeither" RIOE "github.com/IBM/fp-go/v2/context/readerioresult"
RIOEH "github.com/IBM/fp-go/v2/context/readerioeither/http" RIOEH "github.com/IBM/fp-go/v2/context/readerioresult/http"
E "github.com/IBM/fp-go/v2/either" E "github.com/IBM/fp-go/v2/either"
F "github.com/IBM/fp-go/v2/function" F "github.com/IBM/fp-go/v2/function"
R "github.com/IBM/fp-go/v2/http/builder" R "github.com/IBM/fp-go/v2/http/builder"
@@ -61,7 +61,7 @@ import (
O "github.com/IBM/fp-go/v2/option" O "github.com/IBM/fp-go/v2/option"
) )
// Requester converts an http/builder.Builder into a ReaderIOEither that produces HTTP requests. // Requester converts an http/builder.Builder into a ReaderIOResult that produces HTTP requests.
// It handles both requests with and without bodies, automatically managing headers including // It handles both requests with and without bodies, automatically managing headers including
// Content-Length for requests with bodies. // Content-Length for requests with bodies.
// //
@@ -86,14 +86,14 @@ import (
// - builder: A pointer to an http/builder.Builder containing request configuration // - builder: A pointer to an http/builder.Builder containing request configuration
// //
// Returns: // Returns:
// - A Requester (ReaderIOEither[*http.Request]) that, when executed with a context, // - A Requester (ReaderIOResult[*http.Request]) that, when executed with a context,
// produces either an error or a configured *http.Request // produces either an error or a configured *http.Request
// //
// Example with body: // Example with body:
// //
// import ( // import (
// B "github.com/IBM/fp-go/v2/http/builder" // B "github.com/IBM/fp-go/v2/http/builder"
// RB "github.com/IBM/fp-go/v2/context/readerioeither/http/builder" // RB "github.com/IBM/fp-go/v2/context/readerioresult/http/builder"
// ) // )
// //
// builder := F.Pipe3( // builder := F.Pipe3(
@@ -116,7 +116,7 @@ import (
// result := requester(context.Background())() // result := requester(context.Background())()
func Requester(builder *R.Builder) RIOEH.Requester { func Requester(builder *R.Builder) RIOEH.Requester {
withBody := F.Curry3(func(data []byte, url string, method string) RIOE.ReaderIOEither[*http.Request] { withBody := F.Curry3(func(data []byte, url string, method string) RIOE.ReaderIOResult[*http.Request] {
return RIOE.TryCatch(func(ctx context.Context) func() (*http.Request, error) { return RIOE.TryCatch(func(ctx context.Context) func() (*http.Request, error) {
return func() (*http.Request, error) { return func() (*http.Request, error) {
req, err := http.NewRequestWithContext(ctx, method, url, bytes.NewReader(data)) req, err := http.NewRequestWithContext(ctx, method, url, bytes.NewReader(data))
@@ -129,7 +129,7 @@ func Requester(builder *R.Builder) RIOEH.Requester {
}) })
}) })
withoutBody := F.Curry2(func(url string, method string) RIOE.ReaderIOEither[*http.Request] { withoutBody := F.Curry2(func(url string, method string) RIOE.ReaderIOResult[*http.Request] {
return RIOE.TryCatch(func(ctx context.Context) func() (*http.Request, error) { return RIOE.TryCatch(func(ctx context.Context) func() (*http.Request, error) {
return func() (*http.Request, error) { return func() (*http.Request, error) {
req, err := http.NewRequestWithContext(ctx, method, url, nil) req, err := http.NewRequestWithContext(ctx, method, url, nil)
@@ -144,8 +144,8 @@ func Requester(builder *R.Builder) RIOEH.Requester {
return F.Pipe5( return F.Pipe5(
builder.GetBody(), builder.GetBody(),
O.Fold(LZ.Of(E.Of[error](withoutBody)), E.Map[error](withBody)), O.Fold(LZ.Of(E.Of[error](withoutBody)), E.Map[error](withBody)),
E.Ap[func(string) RIOE.ReaderIOEither[*http.Request]](builder.GetTargetURL()), E.Ap[func(string) RIOE.ReaderIOResult[*http.Request]](builder.GetTargetURL()),
E.Flap[error, RIOE.ReaderIOEither[*http.Request]](builder.GetMethod()), E.Flap[error, RIOE.ReaderIOResult[*http.Request]](builder.GetMethod()),
E.GetOrElse(RIOE.Left[*http.Request]), E.GetOrElse(RIOE.Left[*http.Request]),
RIOE.Map(func(req *http.Request) *http.Request { RIOE.Map(func(req *http.Request) *http.Request {
req.Header = H.Monoid.Concat(req.Header, builder.GetHeaders()) req.Header = H.Monoid.Concat(req.Header, builder.GetHeaders())

View File

@@ -21,7 +21,7 @@ import (
"net/url" "net/url"
"testing" "testing"
RIOE "github.com/IBM/fp-go/v2/context/readerioeither" RIOE "github.com/IBM/fp-go/v2/context/readerioresult"
E "github.com/IBM/fp-go/v2/either" E "github.com/IBM/fp-go/v2/either"
F "github.com/IBM/fp-go/v2/function" F "github.com/IBM/fp-go/v2/function"
R "github.com/IBM/fp-go/v2/http/builder" R "github.com/IBM/fp-go/v2/http/builder"

View File

@@ -0,0 +1,15 @@
mode: set
github.com/IBM/fp-go/v2/context/readerioresult/http/builder/builder.go:117.52,119.103 1 1
github.com/IBM/fp-go/v2/context/readerioresult/http/builder/builder.go:119.103,120.80 1 1
github.com/IBM/fp-go/v2/context/readerioresult/http/builder/builder.go:120.80,121.41 1 1
github.com/IBM/fp-go/v2/context/readerioresult/http/builder/builder.go:121.41,123.19 2 1
github.com/IBM/fp-go/v2/context/readerioresult/http/builder/builder.go:123.19,126.6 2 1
github.com/IBM/fp-go/v2/context/readerioresult/http/builder/builder.go:127.5,127.20 1 1
github.com/IBM/fp-go/v2/context/readerioresult/http/builder/builder.go:132.2,132.93 1 1
github.com/IBM/fp-go/v2/context/readerioresult/http/builder/builder.go:132.93,133.80 1 1
github.com/IBM/fp-go/v2/context/readerioresult/http/builder/builder.go:133.80,134.41 1 1
github.com/IBM/fp-go/v2/context/readerioresult/http/builder/builder.go:134.41,136.19 2 1
github.com/IBM/fp-go/v2/context/readerioresult/http/builder/builder.go:136.19,138.6 1 1
github.com/IBM/fp-go/v2/context/readerioresult/http/builder/builder.go:139.5,139.20 1 1
github.com/IBM/fp-go/v2/context/readerioresult/http/builder/builder.go:144.2,150.50 1 1
github.com/IBM/fp-go/v2/context/readerioresult/http/builder/builder.go:150.50,153.4 2 1

View File

@@ -0,0 +1,11 @@
mode: set
github.com/IBM/fp-go/v2/context/readerioresult/http/request.go:111.76,116.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/http/request.go:134.49,136.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/http/request.go:161.90,162.65 1 1
github.com/IBM/fp-go/v2/context/readerioresult/http/request.go:162.65,166.76 1 1
github.com/IBM/fp-go/v2/context/readerioresult/http/request.go:166.76,176.5 1 1
github.com/IBM/fp-go/v2/context/readerioresult/http/request.go:198.73,203.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/http/request.go:222.74,227.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/http/request.go:234.76,236.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/http/request.go:245.74,254.2 1 1
github.com/IBM/fp-go/v2/context/readerioresult/http/request.go:281.76,286.2 1 1

View File

@@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// Package http provides functional HTTP client utilities built on top of ReaderIOEither monad. // Package http provides functional HTTP client utilities built on top of ReaderIOResult monad.
// It offers a composable way to make HTTP requests with context support, error handling, // It offers a composable way to make HTTP requests with context support, error handling,
// and response parsing capabilities. The package follows functional programming principles // and response parsing capabilities. The package follows functional programming principles
// to ensure type-safe, testable, and maintainable HTTP operations. // to ensure type-safe, testable, and maintainable HTTP operations.
@@ -36,7 +36,7 @@ import (
"net/http" "net/http"
B "github.com/IBM/fp-go/v2/bytes" B "github.com/IBM/fp-go/v2/bytes"
RIOE "github.com/IBM/fp-go/v2/context/readerioeither" RIOE "github.com/IBM/fp-go/v2/context/readerioresult"
F "github.com/IBM/fp-go/v2/function" F "github.com/IBM/fp-go/v2/function"
H "github.com/IBM/fp-go/v2/http" H "github.com/IBM/fp-go/v2/http"
IOE "github.com/IBM/fp-go/v2/ioeither" IOE "github.com/IBM/fp-go/v2/ioeither"
@@ -50,13 +50,13 @@ type (
// It represents a computation that, given a context, produces either an error // It represents a computation that, given a context, produces either an error
// or an HTTP request. This allows for composable request building with proper // or an HTTP request. This allows for composable request building with proper
// error handling and context propagation. // error handling and context propagation.
Requester = RIOE.ReaderIOEither[*http.Request] Requester = RIOE.ReaderIOResult[*http.Request]
// Client is an interface for executing HTTP requests in a functional way. // Client is an interface for executing HTTP requests in a functional way.
// It wraps the standard http.Client and provides a Do method that works // It wraps the standard http.Client and provides a Do method that works
// with the ReaderIOEither monad for composable, type-safe HTTP operations. // with the ReaderIOResult monad for composable, type-safe HTTP operations.
Client interface { Client interface {
// Do executes an HTTP request and returns the response wrapped in a ReaderIOEither. // Do executes an HTTP request and returns the response wrapped in a ReaderIOResult.
// It takes a Requester (which builds the request) and returns a computation that, // It takes a Requester (which builds the request) and returns a computation that,
// when executed with a context, performs the HTTP request and returns either // when executed with a context, performs the HTTP request and returns either
// an error or the HTTP response. // an error or the HTTP response.
@@ -65,8 +65,8 @@ type (
// - req: A Requester that builds the HTTP request // - req: A Requester that builds the HTTP request
// //
// Returns: // Returns:
// - A ReaderIOEither that produces either an error or an *http.Response // - A ReaderIOResult that produces either an error or an *http.Response
Do(Requester) RIOE.ReaderIOEither[*http.Response] Do(Requester) RIOE.ReaderIOResult[*http.Response]
} }
// client is the internal implementation of the Client interface. // client is the internal implementation of the Client interface.
@@ -108,7 +108,7 @@ var (
MakeGetRequest = makeRequest("GET", nil) MakeGetRequest = makeRequest("GET", nil)
) )
func (client client) Do(req Requester) RIOE.ReaderIOEither[*http.Response] { func (client client) Do(req Requester) RIOE.ReaderIOResult[*http.Response] {
return F.Pipe1( return F.Pipe1(
req, req,
RIOE.ChainIOEitherK(client.doIOE), RIOE.ChainIOEitherK(client.doIOE),
@@ -117,7 +117,7 @@ func (client client) Do(req Requester) RIOE.ReaderIOEither[*http.Response] {
// MakeClient creates a functional HTTP client wrapper around a standard http.Client. // MakeClient creates a functional HTTP client wrapper around a standard http.Client.
// The returned Client provides methods for executing HTTP requests in a functional, // The returned Client provides methods for executing HTTP requests in a functional,
// composable way using the ReaderIOEither monad. // composable way using the ReaderIOResult monad.
// //
// Parameters: // Parameters:
// - httpClient: A standard *http.Client to wrap (e.g., http.DefaultClient) // - httpClient: A standard *http.Client to wrap (e.g., http.DefaultClient)
@@ -149,7 +149,7 @@ func MakeClient(httpClient *http.Client) Client {
// - client: The HTTP client to use for executing the request // - client: The HTTP client to use for executing the request
// //
// Returns: // Returns:
// - A function that takes a Requester and returns a ReaderIOEither[FullResponse] // - A function that takes a Requester and returns a ReaderIOResult[FullResponse]
// where FullResponse is a tuple of (*http.Response, []byte) // where FullResponse is a tuple of (*http.Response, []byte)
// //
// Example: // Example:
@@ -158,8 +158,8 @@ func MakeClient(httpClient *http.Client) Client {
// request := MakeGetRequest("https://api.example.com/data") // request := MakeGetRequest("https://api.example.com/data")
// fullResp := ReadFullResponse(client)(request) // fullResp := ReadFullResponse(client)(request)
// result := fullResp(context.Background())() // result := fullResp(context.Background())()
func ReadFullResponse(client Client) func(Requester) RIOE.ReaderIOEither[H.FullResponse] { func ReadFullResponse(client Client) func(Requester) RIOE.ReaderIOResult[H.FullResponse] {
return func(req Requester) RIOE.ReaderIOEither[H.FullResponse] { return func(req Requester) RIOE.ReaderIOResult[H.FullResponse] {
return F.Flow3( return F.Flow3(
client.Do(req), client.Do(req),
IOE.ChainEitherK(H.ValidateResponse), IOE.ChainEitherK(H.ValidateResponse),
@@ -186,7 +186,7 @@ func ReadFullResponse(client Client) func(Requester) RIOE.ReaderIOEither[H.FullR
// - client: The HTTP client to use for executing the request // - client: The HTTP client to use for executing the request
// //
// Returns: // Returns:
// - A function that takes a Requester and returns a ReaderIOEither[[]byte] // - A function that takes a Requester and returns a ReaderIOResult[[]byte]
// containing the response body as bytes // containing the response body as bytes
// //
// Example: // Example:
@@ -195,7 +195,7 @@ func ReadFullResponse(client Client) func(Requester) RIOE.ReaderIOEither[H.FullR
// request := MakeGetRequest("https://api.example.com/data") // request := MakeGetRequest("https://api.example.com/data")
// readBytes := ReadAll(client) // readBytes := ReadAll(client)
// result := readBytes(request)(context.Background())() // result := readBytes(request)(context.Background())()
func ReadAll(client Client) func(Requester) RIOE.ReaderIOEither[[]byte] { func ReadAll(client Client) func(Requester) RIOE.ReaderIOResult[[]byte] {
return F.Flow2( return F.Flow2(
ReadFullResponse(client), ReadFullResponse(client),
RIOE.Map(H.Body), RIOE.Map(H.Body),
@@ -210,7 +210,7 @@ func ReadAll(client Client) func(Requester) RIOE.ReaderIOEither[[]byte] {
// - client: The HTTP client to use for executing the request // - client: The HTTP client to use for executing the request
// //
// Returns: // Returns:
// - A function that takes a Requester and returns a ReaderIOEither[string] // - A function that takes a Requester and returns a ReaderIOResult[string]
// containing the response body as a string // containing the response body as a string
// //
// Example: // Example:
@@ -219,7 +219,7 @@ func ReadAll(client Client) func(Requester) RIOE.ReaderIOEither[[]byte] {
// request := MakeGetRequest("https://api.example.com/text") // request := MakeGetRequest("https://api.example.com/text")
// readText := ReadText(client) // readText := ReadText(client)
// result := readText(request)(context.Background())() // result := readText(request)(context.Background())()
func ReadText(client Client) func(Requester) RIOE.ReaderIOEither[string] { func ReadText(client Client) func(Requester) RIOE.ReaderIOResult[string] {
return F.Flow2( return F.Flow2(
ReadAll(client), ReadAll(client),
RIOE.Map(B.ToString), RIOE.Map(B.ToString),
@@ -231,7 +231,7 @@ func ReadText(client Client) func(Requester) RIOE.ReaderIOEither[string] {
// Deprecated: Use [ReadJSON] instead. This function is kept for backward compatibility // Deprecated: Use [ReadJSON] instead. This function is kept for backward compatibility
// but will be removed in a future version. The capitalized version follows Go naming // but will be removed in a future version. The capitalized version follows Go naming
// conventions for acronyms. // conventions for acronyms.
func ReadJson[A any](client Client) func(Requester) RIOE.ReaderIOEither[A] { func ReadJson[A any](client Client) func(Requester) RIOE.ReaderIOResult[A] {
return ReadJSON[A](client) return ReadJSON[A](client)
} }
@@ -242,7 +242,7 @@ func ReadJson[A any](client Client) func(Requester) RIOE.ReaderIOEither[A] {
// 3. Reads the response body as bytes // 3. Reads the response body as bytes
// //
// This function is used internally by ReadJSON to ensure proper JSON response handling. // This function is used internally by ReadJSON to ensure proper JSON response handling.
func readJSON(client Client) func(Requester) RIOE.ReaderIOEither[[]byte] { func readJSON(client Client) func(Requester) RIOE.ReaderIOResult[[]byte] {
return F.Flow3( return F.Flow3(
ReadFullResponse(client), ReadFullResponse(client),
RIOE.ChainFirstEitherK(F.Flow2( RIOE.ChainFirstEitherK(F.Flow2(
@@ -264,7 +264,7 @@ func readJSON(client Client) func(Requester) RIOE.ReaderIOEither[[]byte] {
// - client: The HTTP client to use for executing the request // - client: The HTTP client to use for executing the request
// //
// Returns: // Returns:
// - A function that takes a Requester and returns a ReaderIOEither[A] // - A function that takes a Requester and returns a ReaderIOResult[A]
// containing the parsed JSON data // containing the parsed JSON data
// //
// Example: // Example:
@@ -278,7 +278,7 @@ func readJSON(client Client) func(Requester) RIOE.ReaderIOEither[[]byte] {
// request := MakeGetRequest("https://api.example.com/user/1") // request := MakeGetRequest("https://api.example.com/user/1")
// readUser := ReadJSON[User](client) // readUser := ReadJSON[User](client)
// result := readUser(request)(context.Background())() // result := readUser(request)(context.Background())()
func ReadJSON[A any](client Client) func(Requester) RIOE.ReaderIOEither[A] { func ReadJSON[A any](client Client) func(Requester) RIOE.ReaderIOResult[A] {
return F.Flow2( return F.Flow2(
readJSON(client), readJSON(client),
RIOE.ChainEitherK(J.Unmarshal[A]), RIOE.ChainEitherK(J.Unmarshal[A]),

View File

@@ -22,7 +22,7 @@ import (
H "net/http" H "net/http"
R "github.com/IBM/fp-go/v2/context/readerioeither" R "github.com/IBM/fp-go/v2/context/readerioresult"
E "github.com/IBM/fp-go/v2/either" E "github.com/IBM/fp-go/v2/either"
"github.com/IBM/fp-go/v2/errors" "github.com/IBM/fp-go/v2/errors"
F "github.com/IBM/fp-go/v2/function" F "github.com/IBM/fp-go/v2/function"
@@ -66,7 +66,7 @@ func (b simpleRequestBuilder) WithHeader(key, value string) simpleRequestBuilder
return b return b
} }
func (b simpleRequestBuilder) Build() R.ReaderIOEither[*H.Request] { func (b simpleRequestBuilder) Build() R.ReaderIOResult[*H.Request] {
return func(ctx context.Context) IOE.IOEither[error, *H.Request] { return func(ctx context.Context) IOE.IOEither[error, *H.Request] {
return IOE.TryCatchError(func() (*H.Request, error) { return IOE.TryCatchError(func() (*H.Request, error) {
req, err := H.NewRequestWithContext(ctx, b.method, b.url, nil) req, err := H.NewRequestWithContext(ctx, b.method, b.url, nil)

View File

@@ -13,96 +13,75 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package readerioeither package readerioresult
import ( import (
"context"
"github.com/IBM/fp-go/v2/monoid" "github.com/IBM/fp-go/v2/monoid"
RIOR "github.com/IBM/fp-go/v2/readerioresult"
) )
type ( type (
Monoid[A any] = monoid.Monoid[ReaderIOEither[A]] Monoid[A any] = monoid.Monoid[ReaderIOResult[A]]
) )
// ApplicativeMonoid returns a [Monoid] that concatenates [ReaderIOEither] instances via their applicative. // ApplicativeMonoid returns a [Monoid] that concatenates [ReaderIOResult] instances via their applicative.
// This uses the default applicative behavior (parallel or sequential based on useParallel flag). // This uses the default applicative behavior (parallel or sequential based on useParallel flag).
// //
// The monoid combines two ReaderIOEither values by applying the underlying monoid's combine operation // The monoid combines two ReaderIOResult values by applying the underlying monoid's combine operation
// to their success values using applicative application. // to their success values using applicative application.
// //
// Parameters: // Parameters:
// - m: The underlying monoid for type A // - m: The underlying monoid for type A
// //
// Returns a Monoid for ReaderIOEither[A]. // Returns a Monoid for ReaderIOResult[A].
func ApplicativeMonoid[A any](m monoid.Monoid[A]) Monoid[A] { func ApplicativeMonoid[A any](m monoid.Monoid[A]) Monoid[A] {
return monoid.ApplicativeMonoid( return RIOR.ApplicativeMonoid[context.Context](m)
Of[A],
MonadMap[A, func(A) A],
MonadAp[A, A],
m,
)
} }
// ApplicativeMonoidSeq returns a [Monoid] that concatenates [ReaderIOEither] instances via their applicative. // ApplicativeMonoidSeq returns a [Monoid] that concatenates [ReaderIOResult] instances via their applicative.
// This explicitly uses sequential execution for combining values. // This explicitly uses sequential execution for combining values.
// //
// Parameters: // Parameters:
// - m: The underlying monoid for type A // - m: The underlying monoid for type A
// //
// Returns a Monoid for ReaderIOEither[A] with sequential execution. // Returns a Monoid for ReaderIOResult[A] with sequential execution.
func ApplicativeMonoidSeq[A any](m monoid.Monoid[A]) Monoid[A] { func ApplicativeMonoidSeq[A any](m monoid.Monoid[A]) Monoid[A] {
return monoid.ApplicativeMonoid( return RIOR.ApplicativeMonoidSeq[context.Context](m)
Of[A],
MonadMap[A, func(A) A],
MonadApSeq[A, A],
m,
)
} }
// ApplicativeMonoidPar returns a [Monoid] that concatenates [ReaderIOEither] instances via their applicative. // ApplicativeMonoidPar returns a [Monoid] that concatenates [ReaderIOResult] instances via their applicative.
// This explicitly uses parallel execution for combining values. // This explicitly uses parallel execution for combining values.
// //
// Parameters: // Parameters:
// - m: The underlying monoid for type A // - m: The underlying monoid for type A
// //
// Returns a Monoid for ReaderIOEither[A] with parallel execution. // Returns a Monoid for ReaderIOResult[A] with parallel execution.
func ApplicativeMonoidPar[A any](m monoid.Monoid[A]) Monoid[A] { func ApplicativeMonoidPar[A any](m monoid.Monoid[A]) Monoid[A] {
return monoid.ApplicativeMonoid( return RIOR.ApplicativeMonoidPar[context.Context](m)
Of[A],
MonadMap[A, func(A) A],
MonadApPar[A, A],
m,
)
} }
// AlternativeMonoid is the alternative [Monoid] for [ReaderIOEither]. // AlternativeMonoid is the alternative [Monoid] for [ReaderIOResult].
// This combines ReaderIOEither values using the alternative semantics, // This combines ReaderIOResult values using the alternative semantics,
// where the second value is only evaluated if the first fails. // where the second value is only evaluated if the first fails.
// //
// Parameters: // Parameters:
// - m: The underlying monoid for type A // - m: The underlying monoid for type A
// //
// Returns a Monoid for ReaderIOEither[A] with alternative semantics. // Returns a Monoid for ReaderIOResult[A] with alternative semantics.
func AlternativeMonoid[A any](m monoid.Monoid[A]) Monoid[A] { func AlternativeMonoid[A any](m monoid.Monoid[A]) Monoid[A] {
return monoid.AlternativeMonoid( return RIOR.AlternativeMonoid[context.Context](m)
Of[A],
MonadMap[A, func(A) A],
MonadAp[A, A],
MonadAlt[A],
m,
)
} }
// AltMonoid is the alternative [Monoid] for a [ReaderIOEither]. // AltMonoid is the alternative [Monoid] for a [ReaderIOResult].
// This creates a monoid where the empty value is provided lazily, // This creates a monoid where the empty value is provided lazily,
// and combination uses the Alt operation (try first, fallback to second on failure). // and combination uses the Alt operation (try first, fallback to second on failure).
// //
// Parameters: // Parameters:
// - zero: Lazy computation that provides the empty/identity value // - zero: Lazy computation that provides the empty/identity value
// //
// Returns a Monoid for ReaderIOEither[A] with Alt-based combination. // Returns a Monoid for ReaderIOResult[A] with Alt-based combination.
func AltMonoid[A any](zero Lazy[ReaderIOEither[A]]) Monoid[A] { func AltMonoid[A any](zero Lazy[ReaderIOResult[A]]) Monoid[A] {
return monoid.AltMonoid( return RIOR.AltMonoid(zero)
zero,
MonadAlt[A],
)
} }

View File

@@ -0,0 +1,843 @@
// 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 readerioresult
import (
"context"
"time"
"github.com/IBM/fp-go/v2/context/readerresult"
"github.com/IBM/fp-go/v2/either"
"github.com/IBM/fp-go/v2/errors"
"github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/io"
"github.com/IBM/fp-go/v2/ioeither"
"github.com/IBM/fp-go/v2/ioresult"
"github.com/IBM/fp-go/v2/reader"
"github.com/IBM/fp-go/v2/readerio"
RIOR "github.com/IBM/fp-go/v2/readerioresult"
"github.com/IBM/fp-go/v2/readeroption"
)
const (
// useParallel is the feature flag to control if we use the parallel or the sequential implementation of ap
useParallel = true
)
// FromEither converts an [Either] into a [ReaderIOResult].
// The resulting computation ignores the context and immediately returns the Either value.
//
// Parameters:
// - e: The Either value to lift into ReaderIOResult
//
// Returns a ReaderIOResult that produces the given Either value.
//
//go:inline
func FromEither[A any](e Either[A]) ReaderIOResult[A] {
return RIOR.FromEither[context.Context](e)
}
// FromEither converts an [Either] into a [ReaderIOResult].
// The resulting computation ignores the context and immediately returns the Either value.
//
// Parameters:
// - e: The Either value to lift into ReaderIOResult
//
// Returns a ReaderIOResult that produces the given Either value.
//
//go:inline
func FromResult[A any](e Result[A]) ReaderIOResult[A] {
return RIOR.FromEither[context.Context](e)
}
// Left creates a [ReaderIOResult] that represents a failed computation with the given error.
//
// Parameters:
// - l: The error value
//
// Returns a ReaderIOResult that always fails with the given error.
func Left[A any](l error) ReaderIOResult[A] {
return RIOR.Left[context.Context, A](l)
}
// Right creates a [ReaderIOResult] that represents a successful computation with the given value.
//
// Parameters:
// - r: The success value
//
// Returns a ReaderIOResult that always succeeds with the given value.
//
//go:inline
func Right[A any](r A) ReaderIOResult[A] {
return RIOR.Right[context.Context](r)
}
// MonadMap transforms the success value of a [ReaderIOResult] using the provided function.
// If the computation fails, the error is propagated unchanged.
//
// Parameters:
// - fa: The ReaderIOResult to transform
// - f: The transformation function
//
// Returns a new ReaderIOResult with the transformed value.
//
//go:inline
func MonadMap[A, B any](fa ReaderIOResult[A], f func(A) B) ReaderIOResult[B] {
return RIOR.MonadMap(fa, f)
}
// Map transforms the success value of a [ReaderIOResult] using the provided function.
// This is the curried version of [MonadMap], useful for composition.
//
// Parameters:
// - f: The transformation function
//
// Returns a function that transforms a ReaderIOResult.
//
//go:inline
func Map[A, B any](f func(A) B) Operator[A, B] {
return RIOR.Map[context.Context](f)
}
// MonadMapTo replaces the success value of a [ReaderIOResult] with a constant value.
// If the computation fails, the error is propagated unchanged.
//
// Parameters:
// - fa: The ReaderIOResult to transform
// - b: The constant value to use
//
// Returns a new ReaderIOResult with the constant value.
//
//go:inline
func MonadMapTo[A, B any](fa ReaderIOResult[A], b B) ReaderIOResult[B] {
return RIOR.MonadMapTo(fa, b)
}
// MapTo replaces the success value of a [ReaderIOResult] with a constant value.
// This is the curried version of [MonadMapTo].
//
// Parameters:
// - b: The constant value to use
//
// Returns a function that transforms a ReaderIOResult.
//
//go:inline
func MapTo[A, B any](b B) Operator[A, B] {
return RIOR.MapTo[context.Context, A](b)
}
// MonadChain sequences two [ReaderIOResult] computations, where the second depends on the result of the first.
// If the first computation fails, the second is not executed.
//
// Parameters:
// - ma: The first ReaderIOResult
// - f: Function that produces the second ReaderIOResult based on the first's result
//
// Returns a new ReaderIOResult representing the sequenced computation.
//
//go:inline
func MonadChain[A, B any](ma ReaderIOResult[A], f Kleisli[A, B]) ReaderIOResult[B] {
return RIOR.MonadChain(ma, f)
}
// Chain sequences two [ReaderIOResult] computations, where the second depends on the result of the first.
// This is the curried version of [MonadChain], useful for composition.
//
// Parameters:
// - f: Function that produces the second ReaderIOResult based on the first's result
//
// Returns a function that sequences ReaderIOResult computations.
//
//go:inline
func Chain[A, B any](f Kleisli[A, B]) Operator[A, B] {
return RIOR.Chain(f)
}
// MonadChainFirst sequences two [ReaderIOResult] computations but returns the result of the first.
// The second computation is executed for its side effects only.
//
// Parameters:
// - ma: The first ReaderIOResult
// - f: Function that produces the second ReaderIOResult
//
// Returns a ReaderIOResult with the result of the first computation.
//
//go:inline
func MonadChainFirst[A, B any](ma ReaderIOResult[A], f Kleisli[A, B]) ReaderIOResult[A] {
return RIOR.MonadChainFirst(ma, f)
}
// ChainFirst sequences two [ReaderIOResult] computations but returns the result of the first.
// This is the curried version of [MonadChainFirst].
//
// Parameters:
// - f: Function that produces the second ReaderIOResult
//
// Returns a function that sequences ReaderIOResult computations.
//
//go:inline
func ChainFirst[A, B any](f Kleisli[A, B]) Operator[A, A] {
return RIOR.ChainFirst(f)
}
// Of creates a [ReaderIOResult] that always succeeds with the given value.
// This is the same as [Right] and represents the monadic return operation.
//
// Parameters:
// - a: The value to wrap
//
// Returns a ReaderIOResult that always succeeds with the given value.
//
//go:inline
func Of[A any](a A) ReaderIOResult[A] {
return RIOR.Of[context.Context](a)
}
func withCancelCauseFunc[A any](cancel context.CancelCauseFunc, ma IOResult[A]) IOResult[A] {
return function.Pipe3(
ma,
ioresult.Swap[A],
ioeither.ChainFirstIOK[A](func(err error) func() any {
return io.FromImpure(func() { cancel(err) })
}),
ioeither.Swap[A],
)
}
// MonadApPar implements parallel applicative application for [ReaderIOResult].
// It executes both computations in parallel and creates a sub-context that will be canceled
// if either operation fails. This provides automatic cancellation propagation.
//
// Parameters:
// - fab: ReaderIOResult containing a function
// - fa: ReaderIOResult containing a value
//
// Returns a ReaderIOResult with the function applied to the value.
func MonadApPar[B, A any](fab ReaderIOResult[func(A) B], fa ReaderIOResult[A]) ReaderIOResult[B] {
// context sensitive input
cfab := WithContext(fab)
cfa := WithContext(fa)
return func(ctx context.Context) IOResult[B] {
// quick check for cancellation
if err := context.Cause(ctx); err != nil {
return ioeither.Left[B](err)
}
return func() Result[B] {
// quick check for cancellation
if err := context.Cause(ctx); err != nil {
return either.Left[B](err)
}
// create sub-contexts for fa and fab, so they can cancel one other
ctxSub, cancelSub := context.WithCancelCause(ctx)
defer cancelSub(nil) // cancel has to be called in all paths
fabIOE := withCancelCauseFunc(cancelSub, cfab(ctxSub))
faIOE := withCancelCauseFunc(cancelSub, cfa(ctxSub))
return ioresult.MonadApPar(fabIOE, faIOE)()
}
}
}
// MonadAp implements applicative application for [ReaderIOResult].
// By default, it uses parallel execution ([MonadApPar]) but can be configured to use
// sequential execution ([MonadApSeq]) via the useParallel constant.
//
// Parameters:
// - fab: ReaderIOResult containing a function
// - fa: ReaderIOResult containing a value
//
// Returns a ReaderIOResult with the function applied to the value.
func MonadAp[B, A any](fab ReaderIOResult[func(A) B], fa ReaderIOResult[A]) ReaderIOResult[B] {
// dispatch to the configured version
if useParallel {
return MonadApPar(fab, fa)
}
return MonadApSeq(fab, fa)
}
// MonadApSeq implements sequential applicative application for [ReaderIOResult].
// It executes the function computation first, then the value computation.
//
// Parameters:
// - fab: ReaderIOResult containing a function
// - fa: ReaderIOResult containing a value
//
// Returns a ReaderIOResult with the function applied to the value.
//
//go:inline
func MonadApSeq[B, A any](fab ReaderIOResult[func(A) B], fa ReaderIOResult[A]) ReaderIOResult[B] {
return RIOR.MonadApSeq(fab, fa)
}
// Ap applies a function wrapped in a [ReaderIOResult] to a value wrapped in a ReaderIOResult.
// This is the curried version of [MonadAp], using the default execution mode.
//
// Parameters:
// - fa: ReaderIOResult containing a value
//
// Returns a function that applies a ReaderIOResult function to the value.
//
//go:inline
func Ap[B, A any](fa ReaderIOResult[A]) Operator[func(A) B, B] {
return function.Bind2nd(MonadAp[B, A], fa)
}
// ApSeq applies a function wrapped in a [ReaderIOResult] to a value sequentially.
// This is the curried version of [MonadApSeq].
//
// Parameters:
// - fa: ReaderIOResult containing a value
//
// Returns a function that applies a ReaderIOResult function to the value sequentially.
//
//go:inline
func ApSeq[B, A any](fa ReaderIOResult[A]) Operator[func(A) B, B] {
return function.Bind2nd(MonadApSeq[B, A], fa)
}
// ApPar applies a function wrapped in a [ReaderIOResult] to a value in parallel.
// This is the curried version of [MonadApPar].
//
// Parameters:
// - fa: ReaderIOResult containing a value
//
// Returns a function that applies a ReaderIOResult function to the value in parallel.
//
//go:inline
func ApPar[B, A any](fa ReaderIOResult[A]) Operator[func(A) B, B] {
return function.Bind2nd(MonadApPar[B, A], fa)
}
// FromPredicate creates a [ReaderIOResult] from a predicate function.
// If the predicate returns true, the value is wrapped in Right; otherwise, Left with the error from onFalse.
//
// Parameters:
// - pred: Predicate function to test the value
// - onFalse: Function to generate an error when predicate fails
//
// Returns a function that converts a value to ReaderIOResult based on the predicate.
//
//go:inline
func FromPredicate[A any](pred func(A) bool, onFalse func(A) error) Kleisli[A, A] {
return RIOR.FromPredicate[context.Context](pred, onFalse)
}
// OrElse provides an alternative [ReaderIOResult] computation if the first one fails.
// The alternative is only executed if the first computation results in a Left (error).
//
// Parameters:
// - onLeft: Function that produces an alternative ReaderIOResult from the error
//
// Returns a function that provides fallback behavior for failed computations.
//
//go:inline
func OrElse[A any](onLeft Kleisli[error, A]) Operator[A, A] {
return RIOR.OrElse(onLeft)
}
// Ask returns a [ReaderIOResult] that provides access to the context.
// This is useful for accessing the [context.Context] within a computation.
//
// Returns a ReaderIOResult that produces the context.
//
//go:inline
func Ask() ReaderIOResult[context.Context] {
return RIOR.Ask[context.Context]()
}
// MonadChainEitherK chains a function that returns an [Either] into a [ReaderIOResult] computation.
// This is useful for integrating pure Either-returning functions into ReaderIOResult workflows.
//
// Parameters:
// - ma: The ReaderIOResult to chain from
// - f: Function that produces an Either
//
// Returns a new ReaderIOResult with the chained computation.
//
//go:inline
func MonadChainEitherK[A, B any](ma ReaderIOResult[A], f func(A) Either[B]) ReaderIOResult[B] {
return RIOR.MonadChainEitherK(ma, f)
}
// ChainEitherK chains a function that returns an [Either] into a [ReaderIOResult] computation.
// This is the curried version of [MonadChainEitherK].
//
// Parameters:
// - f: Function that produces an Either
//
// Returns a function that chains the Either-returning function.
//
//go:inline
func ChainEitherK[A, B any](f func(A) Either[B]) Operator[A, B] {
return RIOR.ChainEitherK[context.Context](f)
}
// MonadChainFirstEitherK chains a function that returns an [Either] but keeps the original value.
// The Either-returning function is executed for its validation/side effects only.
//
// Parameters:
// - ma: The ReaderIOResult to chain from
// - f: Function that produces an Either
//
// Returns a ReaderIOResult with the original value if both computations succeed.
//
//go:inline
func MonadChainFirstEitherK[A, B any](ma ReaderIOResult[A], f func(A) Either[B]) ReaderIOResult[A] {
return RIOR.MonadChainFirstEitherK(ma, f)
}
// ChainFirstEitherK chains a function that returns an [Either] but keeps the original value.
// This is the curried version of [MonadChainFirstEitherK].
//
// Parameters:
// - f: Function that produces an Either
//
// Returns a function that chains the Either-returning function.
//
//go:inline
func ChainFirstEitherK[A, B any](f func(A) Either[B]) Operator[A, A] {
return RIOR.ChainFirstEitherK[context.Context](f)
}
// ChainOptionK chains a function that returns an [Option] into a [ReaderIOResult] computation.
// If the Option is None, the provided error function is called.
//
// Parameters:
// - onNone: Function to generate an error when Option is None
//
// Returns a function that chains Option-returning functions into ReaderIOResult.
//
//go:inline
func ChainOptionK[A, B any](onNone func() error) func(func(A) Option[B]) Operator[A, B] {
return RIOR.ChainOptionK[context.Context, A, B](onNone)
}
// FromIOEither converts an [IOResult] into a [ReaderIOResult].
// The resulting computation ignores the context.
//
// Parameters:
// - t: The IOResult to convert
//
// Returns a ReaderIOResult that executes the IOResult.
//
//go:inline
func FromIOEither[A any](t IOResult[A]) ReaderIOResult[A] {
return RIOR.FromIOEither[context.Context](t)
}
//go:inline
func FromIOResult[A any](t IOResult[A]) ReaderIOResult[A] {
return RIOR.FromIOResult[context.Context](t)
}
// FromIO converts an [IO] into a [ReaderIOResult].
// The IO computation always succeeds, so it's wrapped in Right.
//
// Parameters:
// - t: The IO to convert
//
// Returns a ReaderIOResult that executes the IO and wraps the result in Right.
//
//go:inline
func FromIO[A any](t IO[A]) ReaderIOResult[A] {
return RIOR.FromIO[context.Context](t)
}
//go:inline
func FromReader[A any](t Reader[context.Context, A]) ReaderIOResult[A] {
return RIOR.FromReader(t)
}
//go:inline
func FromReaderIO[A any](t ReaderIO[A]) ReaderIOResult[A] {
return RIOR.FromReaderIO(t)
}
// FromLazy converts a [Lazy] computation into a [ReaderIOResult].
// The Lazy computation always succeeds, so it's wrapped in Right.
// This is an alias for [FromIO] since Lazy and IO have the same structure.
//
// Parameters:
// - t: The Lazy computation to convert
//
// Returns a ReaderIOResult that executes the Lazy computation and wraps the result in Right.
//
//go:inline
func FromLazy[A any](t Lazy[A]) ReaderIOResult[A] {
return RIOR.FromIO[context.Context](t)
}
// Never returns a [ReaderIOResult] that blocks indefinitely until the context is canceled.
// This is useful for creating computations that wait for external cancellation signals.
//
// Returns a ReaderIOResult that waits for context cancellation and returns the cancellation error.
func Never[A any]() ReaderIOResult[A] {
return func(ctx context.Context) IOResult[A] {
return func() Either[A] {
<-ctx.Done()
return either.Left[A](context.Cause(ctx))
}
}
}
// MonadChainIOK chains a function that returns an [IO] into a [ReaderIOResult] computation.
// The IO computation always succeeds, so it's wrapped in Right.
//
// Parameters:
// - ma: The ReaderIOResult to chain from
// - f: Function that produces an IO
//
// Returns a new ReaderIOResult with the chained IO computation.
//
//go:inline
func MonadChainIOK[A, B any](ma ReaderIOResult[A], f func(A) IO[B]) ReaderIOResult[B] {
return RIOR.MonadChainIOK(ma, f)
}
// ChainIOK chains a function that returns an [IO] into a [ReaderIOResult] computation.
// This is the curried version of [MonadChainIOK].
//
// Parameters:
// - f: Function that produces an IO
//
// Returns a function that chains the IO-returning function.
//
//go:inline
func ChainIOK[A, B any](f func(A) IO[B]) Operator[A, B] {
return RIOR.ChainIOK[context.Context](f)
}
// MonadChainFirstIOK chains a function that returns an [IO] but keeps the original value.
// The IO computation is executed for its side effects only.
//
// Parameters:
// - ma: The ReaderIOResult to chain from
// - f: Function that produces an IO
//
// Returns a ReaderIOResult with the original value after executing the IO.
//
//go:inline
func MonadChainFirstIOK[A, B any](ma ReaderIOResult[A], f func(A) IO[B]) ReaderIOResult[A] {
return RIOR.MonadChainFirstIOK(ma, f)
}
// ChainFirstIOK chains a function that returns an [IO] but keeps the original value.
// This is the curried version of [MonadChainFirstIOK].
//
// Parameters:
// - f: Function that produces an IO
//
// Returns a function that chains the IO-returning function.
//
//go:inline
func ChainFirstIOK[A, B any](f func(A) IO[B]) Operator[A, A] {
return RIOR.ChainFirstIOK[context.Context](f)
}
// ChainIOEitherK chains a function that returns an [IOResult] into a [ReaderIOResult] computation.
// This is useful for integrating IOResult-returning functions into ReaderIOResult workflows.
//
// Parameters:
// - f: Function that produces an IOResult
//
// Returns a function that chains the IOResult-returning function.
//
//go:inline
func ChainIOEitherK[A, B any](f func(A) IOResult[B]) Operator[A, B] {
return RIOR.ChainIOEitherK[context.Context](f)
}
// Delay creates an operation that delays execution by the specified duration.
// The computation waits for either the delay to expire or the context to be canceled.
//
// Parameters:
// - delay: The duration to wait before executing the computation
//
// Returns a function that delays a ReaderIOResult computation.
func Delay[A any](delay time.Duration) Operator[A, A] {
return func(ma ReaderIOResult[A]) ReaderIOResult[A] {
return func(ctx context.Context) IOResult[A] {
return func() Either[A] {
// manage the timeout
timeoutCtx, cancelTimeout := context.WithTimeout(ctx, delay)
defer cancelTimeout()
// whatever comes first
select {
case <-timeoutCtx.Done():
return ma(ctx)()
case <-ctx.Done():
return either.Left[A](context.Cause(ctx))
}
}
}
}
}
// Timer returns the current time after waiting for the specified delay.
// This is useful for creating time-based computations.
//
// Parameters:
// - delay: The duration to wait before returning the time
//
// Returns a ReaderIOResult that produces the current time after the delay.
func Timer(delay time.Duration) ReaderIOResult[time.Time] {
return function.Pipe2(
io.Now,
FromIO[time.Time],
Delay[time.Time](delay),
)
}
// Defer creates a [ReaderIOResult] by lazily generating a new computation each time it's executed.
// This is useful for creating computations that should be re-evaluated on each execution.
//
// Parameters:
// - gen: Lazy generator function that produces a ReaderIOResult
//
// Returns a ReaderIOResult that generates a fresh computation on each execution.
//
//go:inline
func Defer[A any](gen Lazy[ReaderIOResult[A]]) ReaderIOResult[A] {
return RIOR.Defer(gen)
}
// TryCatch wraps a function that returns a tuple (value) into a [ReaderIOResult].
// This is the standard way to convert Go error-returning functions into ReaderIOResult.
//
// Parameters:
// - f: Function that takes a context and returns a function producing (value)
//
// Returns a ReaderIOResult that wraps the error-returning function.
//
//go:inline
func TryCatch[A any](f func(context.Context) func() (A, error)) ReaderIOResult[A] {
return RIOR.TryCatch(f, errors.IdentityError)
}
// MonadAlt provides an alternative [ReaderIOResult] if the first one fails.
// The alternative is lazily evaluated only if needed.
//
// Parameters:
// - first: The primary ReaderIOResult to try
// - second: Lazy alternative ReaderIOResult to use if first fails
//
// Returns a ReaderIOResult that tries the first, then the second if first fails.
//
//go:inline
func MonadAlt[A any](first ReaderIOResult[A], second Lazy[ReaderIOResult[A]]) ReaderIOResult[A] {
return RIOR.MonadAlt(first, second)
}
// Alt provides an alternative [ReaderIOResult] if the first one fails.
// This is the curried version of [MonadAlt].
//
// Parameters:
// - second: Lazy alternative ReaderIOResult to use if first fails
//
// Returns a function that provides fallback behavior.
//
//go:inline
func Alt[A any](second Lazy[ReaderIOResult[A]]) Operator[A, A] {
return RIOR.Alt(second)
}
// Memoize computes the value of the provided [ReaderIOResult] monad lazily but exactly once.
// The context used to compute the value is the context of the first call, so do not use this
// method if the value has a functional dependency on the content of the context.
//
// Parameters:
// - rdr: The ReaderIOResult to memoize
//
// Returns a ReaderIOResult that caches its result after the first execution.
//
//go:inline
func Memoize[A any](rdr ReaderIOResult[A]) ReaderIOResult[A] {
return RIOR.Memoize(rdr)
}
// Flatten converts a nested [ReaderIOResult] into a flat [ReaderIOResult].
// This is equivalent to [MonadChain] with the identity function.
//
// Parameters:
// - rdr: The nested ReaderIOResult to flatten
//
// Returns a flattened ReaderIOResult.
//
//go:inline
func Flatten[A any](rdr ReaderIOResult[ReaderIOResult[A]]) ReaderIOResult[A] {
return RIOR.Flatten(rdr)
}
// MonadFlap applies a value to a function wrapped in a [ReaderIOResult].
// This is the reverse of [MonadAp], useful in certain composition scenarios.
//
// Parameters:
// - fab: ReaderIOResult containing a function
// - a: The value to apply to the function
//
// Returns a ReaderIOResult with the function applied to the value.
//
//go:inline
func MonadFlap[B, A any](fab ReaderIOResult[func(A) B], a A) ReaderIOResult[B] {
return RIOR.MonadFlap(fab, a)
}
// Flap applies a value to a function wrapped in a [ReaderIOResult].
// This is the curried version of [MonadFlap].
//
// Parameters:
// - a: The value to apply to the function
//
// Returns a function that applies the value to a ReaderIOResult function.
//
//go:inline
func Flap[B, A any](a A) Operator[func(A) B, B] {
return RIOR.Flap[context.Context, B](a)
}
// Fold handles both success and error cases of a [ReaderIOResult] by providing handlers for each.
// Both handlers return ReaderIOResult, allowing for further composition.
//
// Parameters:
// - onLeft: Handler for error case
// - onRight: Handler for success case
//
// Returns a function that folds a ReaderIOResult into a new ReaderIOResult.
//
//go:inline
func Fold[A, B any](onLeft Kleisli[error, B], onRight Kleisli[A, B]) Operator[A, B] {
return RIOR.Fold(onLeft, onRight)
}
// GetOrElse extracts the value from a [ReaderIOResult], providing a default via a function if it fails.
// The result is a [ReaderIO] that always succeeds.
//
// Parameters:
// - onLeft: Function to provide a default value from the error
//
// Returns a function that converts a ReaderIOResult to a ReaderIO.
//
//go:inline
func GetOrElse[A any](onLeft func(error) ReaderIO[A]) func(ReaderIOResult[A]) ReaderIO[A] {
return RIOR.GetOrElse(onLeft)
}
// OrLeft transforms the error of a [ReaderIOResult] using the provided function.
// The success value is left unchanged.
//
// Parameters:
// - onLeft: Function to transform the error
//
// Returns a function that transforms the error of a ReaderIOResult.
//
//go:inline
func OrLeft[A any](onLeft func(error) ReaderIO[error]) Operator[A, A] {
return RIOR.OrLeft[A](onLeft)
}
//go:inline
func FromReaderEither[A any](ma ReaderEither[context.Context, error, A]) ReaderIOResult[A] {
return RIOR.FromReaderEither(ma)
}
//go:inline
func FromReaderResult[A any](ma ReaderResult[A]) ReaderIOResult[A] {
return RIOR.FromReaderEither(ma)
}
//go:inline
func FromReaderOption[A any](onNone func() error) Kleisli[ReaderOption[context.Context, A], A] {
return RIOR.FromReaderOption[context.Context, A](onNone)
}
//go:inline
func MonadChainReaderK[A, B any](ma ReaderIOResult[A], f reader.Kleisli[context.Context, A, B]) ReaderIOResult[B] {
return RIOR.MonadChainReaderK(ma, f)
}
//go:inline
func ChainReaderK[A, B any](f reader.Kleisli[context.Context, A, B]) Operator[A, B] {
return RIOR.ChainReaderK(f)
}
//go:inline
func MonadChainFirstReaderK[A, B any](ma ReaderIOResult[A], f reader.Kleisli[context.Context, A, B]) ReaderIOResult[A] {
return RIOR.MonadChainFirstReaderK(ma, f)
}
//go:inline
func ChainFirstReaderK[A, B any](f reader.Kleisli[context.Context, A, B]) Operator[A, A] {
return RIOR.ChainFirstReaderK(f)
}
//go:inline
func MonadChainReaderResultK[A, B any](ma ReaderIOResult[A], f readerresult.Kleisli[A, B]) ReaderIOResult[B] {
return RIOR.MonadChainReaderResultK(ma, f)
}
//go:inline
func ChainReaderResultK[A, B any](f readerresult.Kleisli[A, B]) Operator[A, B] {
return RIOR.ChainReaderResultK(f)
}
//go:inline
func MonadChainFirstReaderResultK[A, B any](ma ReaderIOResult[A], f readerresult.Kleisli[A, B]) ReaderIOResult[A] {
return RIOR.MonadChainFirstReaderResultK(ma, f)
}
//go:inline
func ChainFirstReaderResultK[A, B any](f readerresult.Kleisli[A, B]) Operator[A, A] {
return RIOR.ChainFirstReaderResultK(f)
}
//go:inline
func MonadChainReaderIOK[A, B any](ma ReaderIOResult[A], f readerio.Kleisli[context.Context, A, B]) ReaderIOResult[B] {
return RIOR.MonadChainReaderIOK(ma, f)
}
//go:inline
func ChainReaderIOK[A, B any](f readerio.Kleisli[context.Context, A, B]) Operator[A, B] {
return RIOR.ChainReaderIOK(f)
}
//go:inline
func MonadChainFirstReaderIOK[A, B any](ma ReaderIOResult[A], f readerio.Kleisli[context.Context, A, B]) ReaderIOResult[A] {
return RIOR.MonadChainFirstReaderIOK(ma, f)
}
//go:inline
func ChainFirstReaderIOK[A, B any](f readerio.Kleisli[context.Context, A, B]) Operator[A, A] {
return RIOR.ChainFirstReaderIOK(f)
}
//go:inline
func ChainReaderOptionK[A, B any](onNone func() error) func(readeroption.Kleisli[context.Context, A, B]) Operator[A, B] {
return RIOR.ChainReaderOptionK[context.Context, A, B](onNone)
}
//go:inline
func ChainFirstReaderOptionK[A, B any](onNone func() error) func(readeroption.Kleisli[context.Context, A, B]) Operator[A, A] {
return RIOR.ChainFirstReaderOptionK[context.Context, A, B](onNone)
}
//go:inline
func Read[A any](r context.Context) func(ReaderIOResult[A]) IOResult[A] {
return RIOR.Read[A](r)
}

View File

@@ -0,0 +1,885 @@
// 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 readerioresult
import (
"context"
"errors"
"testing"
"time"
E "github.com/IBM/fp-go/v2/either"
F "github.com/IBM/fp-go/v2/function"
IOE "github.com/IBM/fp-go/v2/ioeither"
)
var (
benchErr = errors.New("benchmark error")
benchCtx = context.Background()
benchResult Either[int]
benchRIOE ReaderIOResult[int]
benchInt int
)
// Benchmark core constructors
func BenchmarkLeft(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = Left[int](benchErr)
}
}
func BenchmarkRight(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = Right(42)
}
}
func BenchmarkOf(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = Of(42)
}
}
func BenchmarkFromEither_Right(b *testing.B) {
either := E.Right[error](42)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = FromEither(either)
}
}
func BenchmarkFromEither_Left(b *testing.B) {
either := E.Left[int](benchErr)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = FromEither(either)
}
}
func BenchmarkFromIO(b *testing.B) {
io := func() int { return 42 }
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = FromIO(io)
}
}
func BenchmarkFromIOEither_Right(b *testing.B) {
ioe := IOE.Of[error](42)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = FromIOEither(ioe)
}
}
func BenchmarkFromIOEither_Left(b *testing.B) {
ioe := IOE.Left[int](benchErr)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = FromIOEither(ioe)
}
}
// Benchmark execution
func BenchmarkExecute_Right(b *testing.B) {
rioe := Right(42)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchResult = rioe(benchCtx)()
}
}
func BenchmarkExecute_Left(b *testing.B) {
rioe := Left[int](benchErr)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchResult = rioe(benchCtx)()
}
}
func BenchmarkExecute_WithContext(b *testing.B) {
rioe := Right(42)
ctx, cancel := context.WithCancel(benchCtx)
defer cancel()
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchResult = rioe(ctx)()
}
}
// Benchmark functor operations
func BenchmarkMonadMap_Right(b *testing.B) {
rioe := Right(42)
mapper := func(a int) int { return a * 2 }
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = MonadMap(rioe, mapper)
}
}
func BenchmarkMonadMap_Left(b *testing.B) {
rioe := Left[int](benchErr)
mapper := func(a int) int { return a * 2 }
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = MonadMap(rioe, mapper)
}
}
func BenchmarkMap_Right(b *testing.B) {
rioe := Right(42)
mapper := Map(func(a int) int { return a * 2 })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = mapper(rioe)
}
}
func BenchmarkMap_Left(b *testing.B) {
rioe := Left[int](benchErr)
mapper := Map(func(a int) int { return a * 2 })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = mapper(rioe)
}
}
func BenchmarkMapTo_Right(b *testing.B) {
rioe := Right(42)
mapper := MapTo[int](99)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = mapper(rioe)
}
}
// Benchmark monad operations
func BenchmarkMonadChain_Right(b *testing.B) {
rioe := Right(42)
chainer := func(a int) ReaderIOResult[int] { return Right(a * 2) }
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = MonadChain(rioe, chainer)
}
}
func BenchmarkMonadChain_Left(b *testing.B) {
rioe := Left[int](benchErr)
chainer := func(a int) ReaderIOResult[int] { return Right(a * 2) }
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = MonadChain(rioe, chainer)
}
}
func BenchmarkChain_Right(b *testing.B) {
rioe := Right(42)
chainer := Chain(func(a int) ReaderIOResult[int] { return Right(a * 2) })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = chainer(rioe)
}
}
func BenchmarkChain_Left(b *testing.B) {
rioe := Left[int](benchErr)
chainer := Chain(func(a int) ReaderIOResult[int] { return Right(a * 2) })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = chainer(rioe)
}
}
func BenchmarkChainFirst_Right(b *testing.B) {
rioe := Right(42)
chainer := ChainFirst(func(a int) ReaderIOResult[string] { return Right("logged") })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = chainer(rioe)
}
}
func BenchmarkChainFirst_Left(b *testing.B) {
rioe := Left[int](benchErr)
chainer := ChainFirst(func(a int) ReaderIOResult[string] { return Right("logged") })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = chainer(rioe)
}
}
func BenchmarkFlatten_Right(b *testing.B) {
nested := Right(Right(42))
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = Flatten(nested)
}
}
func BenchmarkFlatten_Left(b *testing.B) {
nested := Left[ReaderIOResult[int]](benchErr)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = Flatten(nested)
}
}
// Benchmark applicative operations
func BenchmarkMonadApSeq_RightRight(b *testing.B) {
fab := Right(func(a int) int { return a * 2 })
fa := Right(42)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = MonadApSeq(fab, fa)
}
}
func BenchmarkMonadApSeq_RightLeft(b *testing.B) {
fab := Right(func(a int) int { return a * 2 })
fa := Left[int](benchErr)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = MonadApSeq(fab, fa)
}
}
func BenchmarkMonadApSeq_LeftRight(b *testing.B) {
fab := Left[func(int) int](benchErr)
fa := Right(42)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = MonadApSeq(fab, fa)
}
}
func BenchmarkMonadApPar_RightRight(b *testing.B) {
fab := Right(func(a int) int { return a * 2 })
fa := Right(42)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = MonadApPar(fab, fa)
}
}
func BenchmarkMonadApPar_RightLeft(b *testing.B) {
fab := Right(func(a int) int { return a * 2 })
fa := Left[int](benchErr)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = MonadApPar(fab, fa)
}
}
func BenchmarkMonadApPar_LeftRight(b *testing.B) {
fab := Left[func(int) int](benchErr)
fa := Right(42)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = MonadApPar(fab, fa)
}
}
// Benchmark execution of applicative operations
func BenchmarkExecuteApSeq_RightRight(b *testing.B) {
fab := Right(func(a int) int { return a * 2 })
fa := Right(42)
rioe := MonadApSeq(fab, fa)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchResult = rioe(benchCtx)()
}
}
func BenchmarkExecuteApPar_RightRight(b *testing.B) {
fab := Right(func(a int) int { return a * 2 })
fa := Right(42)
rioe := MonadApPar(fab, fa)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchResult = rioe(benchCtx)()
}
}
// Benchmark alternative operations
func BenchmarkAlt_RightRight(b *testing.B) {
rioe := Right(42)
alternative := Alt(func() ReaderIOResult[int] { return Right(99) })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = alternative(rioe)
}
}
func BenchmarkAlt_LeftRight(b *testing.B) {
rioe := Left[int](benchErr)
alternative := Alt(func() ReaderIOResult[int] { return Right(99) })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = alternative(rioe)
}
}
func BenchmarkOrElse_Right(b *testing.B) {
rioe := Right(42)
recover := OrElse(func(e error) ReaderIOResult[int] { return Right(0) })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = recover(rioe)
}
}
func BenchmarkOrElse_Left(b *testing.B) {
rioe := Left[int](benchErr)
recover := OrElse(func(e error) ReaderIOResult[int] { return Right(0) })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = recover(rioe)
}
}
// Benchmark chain operations with different types
func BenchmarkChainEitherK_Right(b *testing.B) {
rioe := Right(42)
chainer := ChainEitherK(func(a int) Either[int] { return E.Right[error](a * 2) })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = chainer(rioe)
}
}
func BenchmarkChainEitherK_Left(b *testing.B) {
rioe := Left[int](benchErr)
chainer := ChainEitherK(func(a int) Either[int] { return E.Right[error](a * 2) })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = chainer(rioe)
}
}
func BenchmarkChainIOK_Right(b *testing.B) {
rioe := Right(42)
chainer := ChainIOK(func(a int) func() int { return func() int { return a * 2 } })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = chainer(rioe)
}
}
func BenchmarkChainIOK_Left(b *testing.B) {
rioe := Left[int](benchErr)
chainer := ChainIOK(func(a int) func() int { return func() int { return a * 2 } })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = chainer(rioe)
}
}
func BenchmarkChainIOEitherK_Right(b *testing.B) {
rioe := Right(42)
chainer := ChainIOEitherK(func(a int) IOEither[int] { return IOE.Of[error](a * 2) })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = chainer(rioe)
}
}
func BenchmarkChainIOEitherK_Left(b *testing.B) {
rioe := Left[int](benchErr)
chainer := ChainIOEitherK(func(a int) IOEither[int] { return IOE.Of[error](a * 2) })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = chainer(rioe)
}
}
// Benchmark context operations
func BenchmarkAsk(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = Ask()
}
}
func BenchmarkDefer(b *testing.B) {
gen := func() ReaderIOResult[int] { return Right(42) }
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = Defer(gen)
}
}
func BenchmarkMemoize(b *testing.B) {
rioe := Right(42)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = Memoize(rioe)
}
}
// Benchmark delay operations
func BenchmarkDelay_Construction(b *testing.B) {
rioe := Right(42)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = Delay[int](time.Millisecond)(rioe)
}
}
func BenchmarkTimer_Construction(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = Timer(time.Millisecond)
}
}
// Benchmark TryCatch
func BenchmarkTryCatch_Success(b *testing.B) {
f := func(ctx context.Context) func() (int, error) {
return func() (int, error) { return 42, nil }
}
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = TryCatch(f)
}
}
func BenchmarkTryCatch_Error(b *testing.B) {
f := func(ctx context.Context) func() (int, error) {
return func() (int, error) { return 0, benchErr }
}
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = TryCatch(f)
}
}
func BenchmarkExecuteTryCatch_Success(b *testing.B) {
f := func(ctx context.Context) func() (int, error) {
return func() (int, error) { return 42, nil }
}
rioe := TryCatch(f)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchResult = rioe(benchCtx)()
}
}
func BenchmarkExecuteTryCatch_Error(b *testing.B) {
f := func(ctx context.Context) func() (int, error) {
return func() (int, error) { return 0, benchErr }
}
rioe := TryCatch(f)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchResult = rioe(benchCtx)()
}
}
// Benchmark pipeline operations
func BenchmarkPipeline_Map_Right(b *testing.B) {
rioe := Right(21)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = F.Pipe1(
rioe,
Map(func(x int) int { return x * 2 }),
)
}
}
func BenchmarkPipeline_Map_Left(b *testing.B) {
rioe := Left[int](benchErr)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = F.Pipe1(
rioe,
Map(func(x int) int { return x * 2 }),
)
}
}
func BenchmarkPipeline_Chain_Right(b *testing.B) {
rioe := Right(21)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = F.Pipe1(
rioe,
Chain(func(x int) ReaderIOResult[int] { return Right(x * 2) }),
)
}
}
func BenchmarkPipeline_Chain_Left(b *testing.B) {
rioe := Left[int](benchErr)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = F.Pipe1(
rioe,
Chain(func(x int) ReaderIOResult[int] { return Right(x * 2) }),
)
}
}
func BenchmarkPipeline_Complex_Right(b *testing.B) {
rioe := Right(10)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = F.Pipe3(
rioe,
Map(func(x int) int { return x * 2 }),
Chain(func(x int) ReaderIOResult[int] { return Right(x + 1) }),
Map(func(x int) int { return x * 2 }),
)
}
}
func BenchmarkPipeline_Complex_Left(b *testing.B) {
rioe := Left[int](benchErr)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchRIOE = F.Pipe3(
rioe,
Map(func(x int) int { return x * 2 }),
Chain(func(x int) ReaderIOResult[int] { return Right(x + 1) }),
Map(func(x int) int { return x * 2 }),
)
}
}
func BenchmarkExecutePipeline_Complex_Right(b *testing.B) {
rioe := F.Pipe3(
Right(10),
Map(func(x int) int { return x * 2 }),
Chain(func(x int) ReaderIOResult[int] { return Right(x + 1) }),
Map(func(x int) int { return x * 2 }),
)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchResult = rioe(benchCtx)()
}
}
// Benchmark do-notation operations
func BenchmarkDo(b *testing.B) {
type State struct{ value int }
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = Do(State{})
}
}
func BenchmarkBind_Right(b *testing.B) {
type State struct{ value int }
initial := Do(State{})
binder := Bind(
func(v int) func(State) State {
return func(s State) State { return State{value: v} }
},
func(s State) ReaderIOResult[int] {
return Right(42)
},
)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = binder(initial)
}
}
func BenchmarkLet_Right(b *testing.B) {
type State struct{ value int }
initial := Right(State{value: 10})
letter := Let(
func(v int) func(State) State {
return func(s State) State { return State{value: s.value + v} }
},
func(s State) int { return 32 },
)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = letter(initial)
}
}
func BenchmarkApS_Right(b *testing.B) {
type State struct{ value int }
initial := Right(State{value: 10})
aps := ApS(
func(v int) func(State) State {
return func(s State) State { return State{value: v} }
},
Right(42),
)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = aps(initial)
}
}
// Benchmark traverse operations
func BenchmarkTraverseArray_Empty(b *testing.B) {
arr := []int{}
traverser := TraverseArray(func(x int) ReaderIOResult[int] {
return Right(x * 2)
})
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = traverser(arr)
}
}
func BenchmarkTraverseArray_Small(b *testing.B) {
arr := []int{1, 2, 3}
traverser := TraverseArray(func(x int) ReaderIOResult[int] {
return Right(x * 2)
})
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = traverser(arr)
}
}
func BenchmarkTraverseArray_Medium(b *testing.B) {
arr := make([]int, 10)
for i := range arr {
arr[i] = i
}
traverser := TraverseArray(func(x int) ReaderIOResult[int] {
return Right(x * 2)
})
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = traverser(arr)
}
}
func BenchmarkTraverseArraySeq_Small(b *testing.B) {
arr := []int{1, 2, 3}
traverser := TraverseArraySeq(func(x int) ReaderIOResult[int] {
return Right(x * 2)
})
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = traverser(arr)
}
}
func BenchmarkTraverseArrayPar_Small(b *testing.B) {
arr := []int{1, 2, 3}
traverser := TraverseArrayPar(func(x int) ReaderIOResult[int] {
return Right(x * 2)
})
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = traverser(arr)
}
}
func BenchmarkSequenceArray_Small(b *testing.B) {
arr := []ReaderIOResult[int]{
Right(1),
Right(2),
Right(3),
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = SequenceArray(arr)
}
}
func BenchmarkExecuteTraverseArray_Small(b *testing.B) {
arr := []int{1, 2, 3}
rioe := TraverseArray(func(x int) ReaderIOResult[int] {
return Right(x * 2)
})(arr)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = rioe(benchCtx)()
}
}
func BenchmarkExecuteTraverseArraySeq_Small(b *testing.B) {
arr := []int{1, 2, 3}
rioe := TraverseArraySeq(func(x int) ReaderIOResult[int] {
return Right(x * 2)
})(arr)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = rioe(benchCtx)()
}
}
func BenchmarkExecuteTraverseArrayPar_Small(b *testing.B) {
arr := []int{1, 2, 3}
rioe := TraverseArrayPar(func(x int) ReaderIOResult[int] {
return Right(x * 2)
})(arr)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = rioe(benchCtx)()
}
}
// Benchmark record operations
func BenchmarkTraverseRecord_Small(b *testing.B) {
rec := map[string]int{"a": 1, "b": 2, "c": 3}
traverser := TraverseRecord[string](func(x int) ReaderIOResult[int] {
return Right(x * 2)
})
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = traverser(rec)
}
}
func BenchmarkSequenceRecord_Small(b *testing.B) {
rec := map[string]ReaderIOResult[int]{
"a": Right(1),
"b": Right(2),
"c": Right(3),
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = SequenceRecord(rec)
}
}
// Benchmark resource management
func BenchmarkWithResource_Success(b *testing.B) {
acquire := Right(42)
release := func(int) ReaderIOResult[int] { return Right(0) }
body := func(x int) ReaderIOResult[int] { return Right(x * 2) }
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = WithResource[int](acquire, release)(body)
}
}
func BenchmarkExecuteWithResource_Success(b *testing.B) {
acquire := Right(42)
release := func(int) ReaderIOResult[int] { return Right(0) }
body := func(x int) ReaderIOResult[int] { return Right(x * 2) }
rioe := WithResource[int](acquire, release)(body)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchResult = rioe(benchCtx)()
}
}
func BenchmarkExecuteWithResource_ErrorInBody(b *testing.B) {
acquire := Right(42)
release := func(int) ReaderIOResult[int] { return Right(0) }
body := func(x int) ReaderIOResult[int] { return Left[int](benchErr) }
rioe := WithResource[int](acquire, release)(body)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchResult = rioe(benchCtx)()
}
}
// Benchmark context cancellation
func BenchmarkExecute_CanceledContext(b *testing.B) {
rioe := Right(42)
ctx, cancel := context.WithCancel(benchCtx)
cancel() // Cancel immediately
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchResult = rioe(ctx)()
}
}
func BenchmarkExecuteApPar_CanceledContext(b *testing.B) {
fab := Right(func(a int) int { return a * 2 })
fa := Right(42)
rioe := MonadApPar(fab, fa)
ctx, cancel := context.WithCancel(benchCtx)
cancel() // Cancel immediately
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
benchResult = rioe(ctx)()
}
}

View File

@@ -0,0 +1,877 @@
// 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 readerioresult
import (
"context"
"errors"
"fmt"
"testing"
"time"
E "github.com/IBM/fp-go/v2/either"
IOG "github.com/IBM/fp-go/v2/io"
IOE "github.com/IBM/fp-go/v2/ioeither"
M "github.com/IBM/fp-go/v2/monoid"
O "github.com/IBM/fp-go/v2/option"
R "github.com/IBM/fp-go/v2/reader"
"github.com/stretchr/testify/assert"
)
func TestFromEither(t *testing.T) {
t.Run("Right value", func(t *testing.T) {
either := E.Right[error]("success")
result := FromEither(either)
assert.Equal(t, E.Right[error]("success"), result(context.Background())())
})
t.Run("Left value", func(t *testing.T) {
err := errors.New("test error")
either := E.Left[string](err)
result := FromEither(either)
assert.Equal(t, E.Left[string](err), result(context.Background())())
})
}
func TestFromResult(t *testing.T) {
t.Run("Success", func(t *testing.T) {
result := FromResult(E.Right[error](42))
assert.Equal(t, E.Right[error](42), result(context.Background())())
})
t.Run("Error", func(t *testing.T) {
err := errors.New("test error")
result := FromResult(E.Left[int](err))
assert.Equal(t, E.Left[int](err), result(context.Background())())
})
}
func TestLeft(t *testing.T) {
err := errors.New("test error")
result := Left[string](err)
assert.Equal(t, E.Left[string](err), result(context.Background())())
}
func TestRight(t *testing.T) {
result := Right("success")
assert.Equal(t, E.Right[error]("success"), result(context.Background())())
}
func TestOf(t *testing.T) {
result := Of(42)
assert.Equal(t, E.Right[error](42), result(context.Background())())
}
func TestMonadMap(t *testing.T) {
t.Run("Map over Right", func(t *testing.T) {
result := MonadMap(Of(5), func(x int) int { return x * 2 })
assert.Equal(t, E.Right[error](10), result(context.Background())())
})
t.Run("Map over Left", func(t *testing.T) {
err := errors.New("test error")
result := MonadMap(Left[int](err), func(x int) int { return x * 2 })
assert.Equal(t, E.Left[int](err), result(context.Background())())
})
}
func TestMap(t *testing.T) {
t.Run("Map with success", func(t *testing.T) {
mapper := Map(func(x int) int { return x * 2 })
result := mapper(Of(5))
assert.Equal(t, E.Right[error](10), result(context.Background())())
})
t.Run("Map with error", func(t *testing.T) {
err := errors.New("test error")
mapper := Map(func(x int) int { return x * 2 })
result := mapper(Left[int](err))
assert.Equal(t, E.Left[int](err), result(context.Background())())
})
}
func TestMonadMapTo(t *testing.T) {
t.Run("MapTo with success", func(t *testing.T) {
result := MonadMapTo(Of("original"), 42)
assert.Equal(t, E.Right[error](42), result(context.Background())())
})
t.Run("MapTo with error", func(t *testing.T) {
err := errors.New("test error")
result := MonadMapTo(Left[string](err), 42)
assert.Equal(t, E.Left[int](err), result(context.Background())())
})
}
func TestMapTo(t *testing.T) {
mapper := MapTo[string](42)
result := mapper(Of("original"))
assert.Equal(t, E.Right[error](42), result(context.Background())())
}
func TestMonadChain(t *testing.T) {
t.Run("Chain with success", func(t *testing.T) {
result := MonadChain(Of(5), func(x int) ReaderIOResult[int] {
return Of(x * 2)
})
assert.Equal(t, E.Right[error](10), result(context.Background())())
})
t.Run("Chain with error in first", func(t *testing.T) {
err := errors.New("test error")
result := MonadChain(Left[int](err), func(x int) ReaderIOResult[int] {
return Of(x * 2)
})
assert.Equal(t, E.Left[int](err), result(context.Background())())
})
t.Run("Chain with error in second", func(t *testing.T) {
err := errors.New("test error")
result := MonadChain(Of(5), func(x int) ReaderIOResult[int] {
return Left[int](err)
})
assert.Equal(t, E.Left[int](err), result(context.Background())())
})
}
func TestChain(t *testing.T) {
chainer := Chain(func(x int) ReaderIOResult[int] {
return Of(x * 2)
})
result := chainer(Of(5))
assert.Equal(t, E.Right[error](10), result(context.Background())())
}
func TestMonadChainFirst(t *testing.T) {
t.Run("ChainFirst keeps first value", func(t *testing.T) {
result := MonadChainFirst(Of(5), func(x int) ReaderIOResult[string] {
return Of("ignored")
})
assert.Equal(t, E.Right[error](5), result(context.Background())())
})
t.Run("ChainFirst propagates error from second", func(t *testing.T) {
err := errors.New("test error")
result := MonadChainFirst(Of(5), func(x int) ReaderIOResult[string] {
return Left[string](err)
})
assert.Equal(t, E.Left[int](err), result(context.Background())())
})
}
func TestChainFirst(t *testing.T) {
chainer := ChainFirst(func(x int) ReaderIOResult[string] {
return Of("ignored")
})
result := chainer(Of(5))
assert.Equal(t, E.Right[error](5), result(context.Background())())
}
func TestMonadApSeq(t *testing.T) {
t.Run("ApSeq with success", func(t *testing.T) {
fab := Of(func(x int) int { return x * 2 })
fa := Of(5)
result := MonadApSeq(fab, fa)
assert.Equal(t, E.Right[error](10), result(context.Background())())
})
t.Run("ApSeq with error in function", func(t *testing.T) {
err := errors.New("test error")
fab := Left[func(int) int](err)
fa := Of(5)
result := MonadApSeq(fab, fa)
assert.Equal(t, E.Left[int](err), result(context.Background())())
})
t.Run("ApSeq with error in value", func(t *testing.T) {
err := errors.New("test error")
fab := Of(func(x int) int { return x * 2 })
fa := Left[int](err)
result := MonadApSeq(fab, fa)
assert.Equal(t, E.Left[int](err), result(context.Background())())
})
}
func TestApSeq(t *testing.T) {
fa := Of(5)
fab := Of(func(x int) int { return x * 2 })
result := MonadApSeq(fab, fa)
assert.Equal(t, E.Right[error](10), result(context.Background())())
}
func TestApPar(t *testing.T) {
t.Run("ApPar with success", func(t *testing.T) {
fa := Of(5)
fab := Of(func(x int) int { return x * 2 })
result := MonadApPar(fab, fa)
assert.Equal(t, E.Right[error](10), result(context.Background())())
})
t.Run("ApPar with cancelled context", func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
fa := Of(5)
fab := Of(func(x int) int { return x * 2 })
result := MonadApPar(fab, fa)
res := result(ctx)()
assert.True(t, E.IsLeft(res))
})
}
func TestFromPredicate(t *testing.T) {
t.Run("Predicate true", func(t *testing.T) {
pred := FromPredicate(
func(x int) bool { return x > 0 },
func(x int) error { return fmt.Errorf("value %d is not positive", x) },
)
result := pred(5)
assert.Equal(t, E.Right[error](5), result(context.Background())())
})
t.Run("Predicate false", func(t *testing.T) {
pred := FromPredicate(
func(x int) bool { return x > 0 },
func(x int) error { return fmt.Errorf("value %d is not positive", x) },
)
result := pred(-5)
res := result(context.Background())()
assert.True(t, E.IsLeft(res))
})
}
func TestOrElse(t *testing.T) {
t.Run("OrElse with success", func(t *testing.T) {
fallback := OrElse(func(err error) ReaderIOResult[int] {
return Of(42)
})
result := fallback(Of(10))
assert.Equal(t, E.Right[error](10), result(context.Background())())
})
t.Run("OrElse with error", func(t *testing.T) {
err := errors.New("test error")
fallback := OrElse(func(err error) ReaderIOResult[int] {
return Of(42)
})
result := fallback(Left[int](err))
assert.Equal(t, E.Right[error](42), result(context.Background())())
})
}
func TestAsk(t *testing.T) {
result := Ask()
ctx := context.Background()
res := result(ctx)()
assert.True(t, E.IsRight(res))
ctxResult := E.ToOption(res)
assert.True(t, O.IsSome(ctxResult))
}
func TestMonadChainEitherK(t *testing.T) {
t.Run("ChainEitherK with success", func(t *testing.T) {
result := MonadChainEitherK(Of(5), func(x int) Either[int] {
return E.Right[error](x * 2)
})
assert.Equal(t, E.Right[error](10), result(context.Background())())
})
t.Run("ChainEitherK with error", func(t *testing.T) {
err := errors.New("test error")
result := MonadChainEitherK(Of(5), func(x int) Either[int] {
return E.Left[int](err)
})
assert.Equal(t, E.Left[int](err), result(context.Background())())
})
}
func TestChainEitherK(t *testing.T) {
chainer := ChainEitherK(func(x int) Either[int] {
return E.Right[error](x * 2)
})
result := chainer(Of(5))
assert.Equal(t, E.Right[error](10), result(context.Background())())
}
func TestMonadChainFirstEitherK(t *testing.T) {
t.Run("ChainFirstEitherK keeps first value", func(t *testing.T) {
result := MonadChainFirstEitherK(Of(5), func(x int) Either[string] {
return E.Right[error]("ignored")
})
assert.Equal(t, E.Right[error](5), result(context.Background())())
})
t.Run("ChainFirstEitherK propagates error", func(t *testing.T) {
err := errors.New("test error")
result := MonadChainFirstEitherK(Of(5), func(x int) Either[string] {
return E.Left[string](err)
})
assert.Equal(t, E.Left[int](err), result(context.Background())())
})
}
func TestChainFirstEitherK(t *testing.T) {
chainer := ChainFirstEitherK(func(x int) Either[string] {
return E.Right[error]("ignored")
})
result := chainer(Of(5))
assert.Equal(t, E.Right[error](5), result(context.Background())())
}
func TestChainOptionK(t *testing.T) {
t.Run("ChainOptionK with Some", func(t *testing.T) {
chainer := ChainOptionK[int, int](func() error {
return errors.New("none error")
})(func(x int) Option[int] {
return O.Some(x * 2)
})
result := chainer(Of(5))
assert.Equal(t, E.Right[error](10), result(context.Background())())
})
t.Run("ChainOptionK with None", func(t *testing.T) {
chainer := ChainOptionK[int, int](func() error {
return errors.New("none error")
})(func(x int) Option[int] {
return O.None[int]()
})
result := chainer(Of(5))
res := result(context.Background())()
assert.True(t, E.IsLeft(res))
})
}
func TestFromIOEither(t *testing.T) {
t.Run("FromIOEither with success", func(t *testing.T) {
ioe := IOE.Of[error](42)
result := FromIOEither(ioe)
assert.Equal(t, E.Right[error](42), result(context.Background())())
})
t.Run("FromIOEither with error", func(t *testing.T) {
err := errors.New("test error")
ioe := IOE.Left[int](err)
result := FromIOEither(ioe)
assert.Equal(t, E.Left[int](err), result(context.Background())())
})
}
func TestFromIOResult(t *testing.T) {
ioe := IOE.Of[error](42)
result := FromIOResult(ioe)
assert.Equal(t, E.Right[error](42), result(context.Background())())
}
func TestFromIO(t *testing.T) {
io := IOG.Of(42)
result := FromIO(io)
assert.Equal(t, E.Right[error](42), result(context.Background())())
}
func TestFromReader(t *testing.T) {
reader := R.Of[context.Context](42)
result := FromReader(reader)
assert.Equal(t, E.Right[error](42), result(context.Background())())
}
func TestFromLazy(t *testing.T) {
lazy := func() int { return 42 }
result := FromLazy(lazy)
assert.Equal(t, E.Right[error](42), result(context.Background())())
}
func TestNever(t *testing.T) {
t.Run("Never with cancelled context", func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
result := Never[int]()
// Cancel immediately
cancel()
res := result(ctx)()
assert.True(t, E.IsLeft(res))
})
t.Run("Never with timeout", func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
result := Never[int]()
res := result(ctx)()
assert.True(t, E.IsLeft(res))
})
}
func TestMonadChainIOK(t *testing.T) {
result := MonadChainIOK(Of(5), func(x int) IOG.IO[int] {
return IOG.Of(x * 2)
})
assert.Equal(t, E.Right[error](10), result(context.Background())())
}
func TestChainIOK(t *testing.T) {
chainer := ChainIOK(func(x int) IOG.IO[int] {
return IOG.Of(x * 2)
})
result := chainer(Of(5))
assert.Equal(t, E.Right[error](10), result(context.Background())())
}
func TestMonadChainFirstIOK(t *testing.T) {
result := MonadChainFirstIOK(Of(5), func(x int) IOG.IO[string] {
return IOG.Of("ignored")
})
assert.Equal(t, E.Right[error](5), result(context.Background())())
}
func TestChainFirstIOK(t *testing.T) {
chainer := ChainFirstIOK(func(x int) IOG.IO[string] {
return IOG.Of("ignored")
})
result := chainer(Of(5))
assert.Equal(t, E.Right[error](5), result(context.Background())())
}
func TestChainIOEitherK(t *testing.T) {
t.Run("ChainIOEitherK with success", func(t *testing.T) {
chainer := ChainIOEitherK(func(x int) IOResult[int] {
return IOE.Of[error](x * 2)
})
result := chainer(Of(5))
assert.Equal(t, E.Right[error](10), result(context.Background())())
})
t.Run("ChainIOEitherK with error", func(t *testing.T) {
err := errors.New("test error")
chainer := ChainIOEitherK(func(x int) IOResult[int] {
return IOE.Left[int](err)
})
result := chainer(Of(5))
assert.Equal(t, E.Left[int](err), result(context.Background())())
})
}
func TestDelay(t *testing.T) {
t.Run("Delay with success", func(t *testing.T) {
start := time.Now()
delayed := Delay[int](100 * time.Millisecond)
result := delayed(Of(42))
res := result(context.Background())()
elapsed := time.Since(start)
assert.True(t, E.IsRight(res))
assert.GreaterOrEqual(t, elapsed, 100*time.Millisecond)
})
t.Run("Delay with cancelled context", func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
delayed := Delay[int](100 * time.Millisecond)
result := delayed(Of(42))
// Cancel after starting but before delay completes
cancel()
res := result(ctx)()
// The result might be either Left (if cancelled) or Right (if completed before cancel)
// This is a race condition, so we just verify it completes
assert.True(t, E.IsLeft(res) || E.IsRight(res))
})
}
func TestDefer(t *testing.T) {
counter := 0
deferred := Defer(func() ReaderIOResult[int] {
counter++
return Of(counter)
})
// First execution
res1 := deferred(context.Background())()
assert.True(t, E.IsRight(res1))
// Second execution should generate a new computation
res2 := deferred(context.Background())()
assert.True(t, E.IsRight(res2))
// Counter should be incremented for each execution
assert.Equal(t, 2, counter)
}
func TestTryCatch(t *testing.T) {
t.Run("TryCatch with success", func(t *testing.T) {
result := TryCatch(func(ctx context.Context) func() (int, error) {
return func() (int, error) {
return 42, nil
}
})
assert.Equal(t, E.Right[error](42), result(context.Background())())
})
t.Run("TryCatch with error", func(t *testing.T) {
err := errors.New("test error")
result := TryCatch(func(ctx context.Context) func() (int, error) {
return func() (int, error) {
return 0, err
}
})
assert.Equal(t, E.Left[int](err), result(context.Background())())
})
}
func TestMonadAlt(t *testing.T) {
t.Run("Alt with first success", func(t *testing.T) {
first := Of(42)
second := func() ReaderIOResult[int] { return Of(100) }
result := MonadAlt(first, second)
assert.Equal(t, E.Right[error](42), result(context.Background())())
})
t.Run("Alt with first error", func(t *testing.T) {
err := errors.New("test error")
first := Left[int](err)
second := func() ReaderIOResult[int] { return Of(100) }
result := MonadAlt(first, second)
assert.Equal(t, E.Right[error](100), result(context.Background())())
})
}
func TestAlt(t *testing.T) {
err := errors.New("test error")
alternative := Alt(func() ReaderIOResult[int] { return Of(100) })
result := alternative(Left[int](err))
assert.Equal(t, E.Right[error](100), result(context.Background())())
}
func TestMemoize(t *testing.T) {
counter := 0
computation := Memoize(FromLazy(func() int {
counter++
return counter
}))
// First execution
res1 := computation(context.Background())()
assert.True(t, E.IsRight(res1))
val1 := E.ToOption(res1)
v1, _ := O.Unwrap(val1)
assert.Equal(t, 1, v1)
// Second execution should return cached value
res2 := computation(context.Background())()
assert.True(t, E.IsRight(res2))
val2 := E.ToOption(res2)
v2, _ := O.Unwrap(val2)
assert.Equal(t, 1, v2)
// Counter should only be incremented once
assert.Equal(t, 1, counter)
}
func TestFlatten(t *testing.T) {
nested := Of(Of(42))
result := Flatten(nested)
assert.Equal(t, E.Right[error](42), result(context.Background())())
}
func TestMonadFlap(t *testing.T) {
fab := Of(func(x int) int { return x * 2 })
result := MonadFlap(fab, 5)
assert.Equal(t, E.Right[error](10), result(context.Background())())
}
func TestFlap(t *testing.T) {
flapper := Flap[int](5)
result := flapper(Of(func(x int) int { return x * 2 }))
assert.Equal(t, E.Right[error](10), result(context.Background())())
}
func TestFold(t *testing.T) {
t.Run("Fold with success", func(t *testing.T) {
folder := Fold(
func(err error) ReaderIOResult[string] {
return Of("error: " + err.Error())
},
func(x int) ReaderIOResult[string] {
return Of(fmt.Sprintf("success: %d", x))
},
)
result := folder(Of(42))
assert.Equal(t, E.Right[error]("success: 42"), result(context.Background())())
})
t.Run("Fold with error", func(t *testing.T) {
err := errors.New("test error")
folder := Fold(
func(err error) ReaderIOResult[string] {
return Of("error: " + err.Error())
},
func(x int) ReaderIOResult[string] {
return Of(fmt.Sprintf("success: %d", x))
},
)
result := folder(Left[int](err))
assert.Equal(t, E.Right[error]("error: test error"), result(context.Background())())
})
}
func TestGetOrElse(t *testing.T) {
t.Run("GetOrElse with success", func(t *testing.T) {
getter := GetOrElse(func(err error) ReaderIO[int] {
return func(ctx context.Context) IOG.IO[int] {
return IOG.Of(0)
}
})
result := getter(Of(42))
assert.Equal(t, 42, result(context.Background())())
})
t.Run("GetOrElse with error", func(t *testing.T) {
err := errors.New("test error")
getter := GetOrElse(func(err error) ReaderIO[int] {
return func(ctx context.Context) IOG.IO[int] {
return IOG.Of(0)
}
})
result := getter(Left[int](err))
assert.Equal(t, 0, result(context.Background())())
})
}
func TestWithContext(t *testing.T) {
t.Run("WithContext with valid context", func(t *testing.T) {
computation := WithContext(Of(42))
result := computation(context.Background())()
assert.Equal(t, E.Right[error](42), result)
})
t.Run("WithContext with cancelled context", func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
computation := WithContext(Of(42))
result := computation(ctx)()
assert.True(t, E.IsLeft(result))
})
}
func TestEitherize0(t *testing.T) {
f := func(ctx context.Context) (int, error) {
return 42, nil
}
eitherized := Eitherize0(f)
result := eitherized()
assert.Equal(t, E.Right[error](42), result(context.Background())())
}
func TestUneitherize0(t *testing.T) {
f := func() ReaderIOResult[int] {
return Of(42)
}
uneitherized := Uneitherize0(f)
result, err := uneitherized(context.Background())
assert.NoError(t, err)
assert.Equal(t, 42, result)
}
func TestEitherize1(t *testing.T) {
f := func(ctx context.Context, x int) (int, error) {
return x * 2, nil
}
eitherized := Eitherize1(f)
result := eitherized(5)
assert.Equal(t, E.Right[error](10), result(context.Background())())
}
func TestUneitherize1(t *testing.T) {
f := func(x int) ReaderIOResult[int] {
return Of(x * 2)
}
uneitherized := Uneitherize1(f)
result, err := uneitherized(context.Background(), 5)
assert.NoError(t, err)
assert.Equal(t, 10, result)
}
func TestSequenceT2(t *testing.T) {
result := SequenceT2(Of(1), Of(2))
res := result(context.Background())()
assert.True(t, E.IsRight(res))
tuple := E.ToOption(res)
assert.True(t, O.IsSome(tuple))
t1, _ := O.Unwrap(tuple)
assert.Equal(t, 1, t1.F1)
assert.Equal(t, 2, t1.F2)
}
func TestSequenceSeqT2(t *testing.T) {
result := SequenceSeqT2(Of(1), Of(2))
res := result(context.Background())()
assert.True(t, E.IsRight(res))
}
func TestSequenceParT2(t *testing.T) {
result := SequenceParT2(Of(1), Of(2))
res := result(context.Background())()
assert.True(t, E.IsRight(res))
}
func TestTraverseArray(t *testing.T) {
t.Run("TraverseArray with success", func(t *testing.T) {
arr := []int{1, 2, 3}
traverser := TraverseArray(func(x int) ReaderIOResult[int] {
return Of(x * 2)
})
result := traverser(arr)
res := result(context.Background())()
assert.True(t, E.IsRight(res))
arrOpt := E.ToOption(res)
assert.True(t, O.IsSome(arrOpt))
resultArr, _ := O.Unwrap(arrOpt)
assert.Equal(t, []int{2, 4, 6}, resultArr)
})
t.Run("TraverseArray with error", func(t *testing.T) {
arr := []int{1, 2, 3}
err := errors.New("test error")
traverser := TraverseArray(func(x int) ReaderIOResult[int] {
if x == 2 {
return Left[int](err)
}
return Of(x * 2)
})
result := traverser(arr)
res := result(context.Background())()
assert.True(t, E.IsLeft(res))
})
}
func TestSequenceArray(t *testing.T) {
arr := []ReaderIOResult[int]{Of(1), Of(2), Of(3)}
result := SequenceArray(arr)
res := result(context.Background())()
assert.True(t, E.IsRight(res))
arrOpt := E.ToOption(res)
assert.True(t, O.IsSome(arrOpt))
resultArr, _ := O.Unwrap(arrOpt)
assert.Equal(t, []int{1, 2, 3}, resultArr)
}
func TestTraverseRecord(t *testing.T) {
rec := map[string]int{"a": 1, "b": 2}
result := TraverseRecord[string](func(x int) ReaderIOResult[int] {
return Of(x * 2)
})(rec)
res := result(context.Background())()
assert.True(t, E.IsRight(res))
recOpt := E.ToOption(res)
assert.True(t, O.IsSome(recOpt))
resultRec, _ := O.Unwrap(recOpt)
assert.Equal(t, 2, resultRec["a"])
assert.Equal(t, 4, resultRec["b"])
}
func TestSequenceRecord(t *testing.T) {
rec := map[string]ReaderIOResult[int]{
"a": Of(1),
"b": Of(2),
}
result := SequenceRecord(rec)
res := result(context.Background())()
assert.True(t, E.IsRight(res))
recOpt := E.ToOption(res)
assert.True(t, O.IsSome(recOpt))
resultRec, _ := O.Unwrap(recOpt)
assert.Equal(t, 1, resultRec["a"])
assert.Equal(t, 2, resultRec["b"])
}
func TestAltSemigroup(t *testing.T) {
sg := AltSemigroup[int]()
err := errors.New("test error")
result := sg.Concat(Left[int](err), Of(42))
res := result(context.Background())()
assert.Equal(t, E.Right[error](42), res)
}
func TestApplicativeMonoid(t *testing.T) {
// Test with int addition monoid
intAddMonoid := ApplicativeMonoid(M.MakeMonoid(
func(a, b int) int { return a + b },
0,
))
result := intAddMonoid.Concat(Of(5), Of(10))
res := result(context.Background())()
assert.Equal(t, E.Right[error](15), res)
}
func TestBracket(t *testing.T) {
t.Run("Bracket with success", func(t *testing.T) {
var acquired, released bool
acquire := FromLazy(func() int {
acquired = true
return 42
})
use := func(x int) ReaderIOResult[int] {
return Of(x * 2)
}
release := func(x int, result Either[int]) ReaderIOResult[any] {
return FromLazy(func() any {
released = true
return nil
})
}
result := Bracket(acquire, use, release)
res := result(context.Background())()
assert.True(t, acquired)
assert.True(t, released)
assert.Equal(t, E.Right[error](84), res)
})
t.Run("Bracket with error in use", func(t *testing.T) {
var acquired, released bool
err := errors.New("use error")
acquire := FromLazy(func() int {
acquired = true
return 42
})
use := func(x int) ReaderIOResult[int] {
return Left[int](err)
}
release := func(x int, result Either[int]) ReaderIOResult[any] {
return FromLazy(func() any {
released = true
return nil
})
}
result := Bracket(acquire, use, release)
res := result(context.Background())()
assert.True(t, acquired)
assert.True(t, released)
assert.Equal(t, E.Left[int](err), res)
})
}

View File

@@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package readerioeither package readerioresult
import ( import (
"context" "context"
@@ -143,7 +143,7 @@ func TestCanceledApply(t *testing.T) {
applied := F.Pipe1( applied := F.Pipe1(
fct, fct,
Ap[string, string](errValue), Ap[string](errValue),
) )
res := applied(context.Background())() res := applied(context.Background())()
@@ -156,7 +156,7 @@ func TestRegularApply(t *testing.T) {
applied := F.Pipe1( applied := F.Pipe1(
fct, fct,
Ap[string, string](value), Ap[string](value),
) )
res := applied(context.Background())() res := applied(context.Background())()
@@ -171,14 +171,14 @@ func TestWithResourceNoErrors(t *testing.T) {
return countAcquire return countAcquire
}) })
release := func(int) ReaderIOEither[int] { release := func(int) ReaderIOResult[int] {
return FromLazy(func() int { return FromLazy(func() int {
countRelease++ countRelease++
return countRelease return countRelease
}) })
} }
body := func(int) ReaderIOEither[int] { body := func(int) ReaderIOResult[int] {
return FromLazy(func() int { return FromLazy(func() int {
countBody++ countBody++
return countBody return countBody
@@ -203,7 +203,7 @@ func TestWithResourceErrorInBody(t *testing.T) {
return countAcquire return countAcquire
}) })
release := func(int) ReaderIOEither[int] { release := func(int) ReaderIOResult[int] {
return FromLazy(func() int { return FromLazy(func() int {
countRelease++ countRelease++
return countRelease return countRelease
@@ -211,7 +211,7 @@ func TestWithResourceErrorInBody(t *testing.T) {
} }
err := fmt.Errorf("error in body") err := fmt.Errorf("error in body")
body := func(int) ReaderIOEither[int] { body := func(int) ReaderIOResult[int] {
return Left[int](err) return Left[int](err)
} }
@@ -231,14 +231,14 @@ func TestWithResourceErrorInAcquire(t *testing.T) {
err := fmt.Errorf("error in acquire") err := fmt.Errorf("error in acquire")
acquire := Left[int](err) acquire := Left[int](err)
release := func(int) ReaderIOEither[int] { release := func(int) ReaderIOResult[int] {
return FromLazy(func() int { return FromLazy(func() int {
countRelease++ countRelease++
return countRelease return countRelease
}) })
} }
body := func(int) ReaderIOEither[int] { body := func(int) ReaderIOResult[int] {
return FromLazy(func() int { return FromLazy(func() int {
countBody++ countBody++
return countBody return countBody
@@ -264,11 +264,11 @@ func TestWithResourceErrorInRelease(t *testing.T) {
}) })
err := fmt.Errorf("error in release") err := fmt.Errorf("error in release")
release := func(int) ReaderIOEither[int] { release := func(int) ReaderIOResult[int] {
return Left[int](err) return Left[int](err)
} }
body := func(int) ReaderIOEither[int] { body := func(int) ReaderIOResult[int] {
return FromLazy(func() int { return FromLazy(func() int {
countBody++ countBody++
return countBody return countBody

View File

@@ -13,13 +13,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package readerioeither package readerioresult
import ( import (
"context" RIOR "github.com/IBM/fp-go/v2/readerioresult"
"github.com/IBM/fp-go/v2/function"
RIE "github.com/IBM/fp-go/v2/readerioeither"
) )
// WithResource constructs a function that creates a resource, then operates on it and then releases the resource. // WithResource constructs a function that creates a resource, then operates on it and then releases the resource.
@@ -32,22 +29,22 @@ import (
// - onRelease: Releases the resource (always called, even on error) // - onRelease: Releases the resource (always called, even on error)
// //
// Parameters: // Parameters:
// - onCreate: ReaderIOEither that creates the resource // - onCreate: ReaderIOResult that creates the resource
// - onRelease: Function to release the resource // - onRelease: Function to release the resource
// //
// Returns a function that takes a resource-using function and returns a ReaderIOEither. // Returns a function that takes a resource-using function and returns a ReaderIOResult.
// //
// Example: // Example:
// //
// file := WithResource( // file := WithResource(
// openFile("data.txt"), // openFile("data.txt"),
// func(f *os.File) ReaderIOEither[any] { // func(f *os.File) ReaderIOResult[any] {
// return TryCatch(func(ctx context.Context) func() (any, error) { // return TryCatch(func(ctx context.Context) func() (any, error) {
// return func() (any, error) { return nil, f.Close() } // return func() (any, error) { return nil, f.Close() }
// }) // })
// }, // },
// ) // )
// result := file(func(f *os.File) ReaderIOEither[string] { // result := file(func(f *os.File) ReaderIOResult[string] {
// return TryCatch(func(ctx context.Context) func() (string, error) { // return TryCatch(func(ctx context.Context) func() (string, error) {
// return func() (string, error) { // return func() (string, error) {
// data, err := io.ReadAll(f) // data, err := io.ReadAll(f)
@@ -55,9 +52,6 @@ import (
// } // }
// }) // })
// }) // })
func WithResource[A, R, ANY any](onCreate ReaderIOEither[R], onRelease func(R) ReaderIOEither[ANY]) func(func(R) ReaderIOEither[A]) ReaderIOEither[A] { func WithResource[A, R, ANY any](onCreate ReaderIOResult[R], onRelease Kleisli[R, ANY]) Kleisli[Kleisli[R, A], A] {
return function.Flow2( return RIOR.WithResource[A](onCreate, onRelease)
function.Bind2nd(function.Flow2[func(R) ReaderIOEither[A], Operator[A, A], R, ReaderIOEither[A], ReaderIOEither[A]], WithContext[A]),
RIE.WithResource[A, context.Context, error, R](WithContext(onCreate), onRelease),
)
} }

View File

@@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package readerioeither package readerioresult
import ( import (
"context" "context"
@@ -37,7 +37,7 @@ var (
) )
) )
func closeFile(f *os.File) ReaderIOEither[string] { func closeFile(f *os.File) ReaderIOResult[string] {
return F.Pipe1( return F.Pipe1(
TryCatch(func(_ context.Context) func() (string, error) { TryCatch(func(_ context.Context) func() (string, error) {
return func() (string, error) { return func() (string, error) {
@@ -52,7 +52,7 @@ func ExampleWithResource() {
stringReader := WithResource[string](openFile("data/file.txt"), closeFile) stringReader := WithResource[string](openFile("data/file.txt"), closeFile)
rdr := stringReader(func(f *os.File) ReaderIOEither[string] { rdr := stringReader(func(f *os.File) ReaderIOResult[string] {
return F.Pipe2( return F.Pipe2(
TryCatch(func(_ context.Context) func() ([]byte, error) { TryCatch(func(_ context.Context) func() ([]byte, error) {
return func() ([]byte, error) { return func() ([]byte, error) {

View File

@@ -13,21 +13,21 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package readerioeither package readerioresult
import ( import (
"github.com/IBM/fp-go/v2/semigroup" "github.com/IBM/fp-go/v2/semigroup"
) )
type ( type (
Semigroup[A any] = semigroup.Semigroup[ReaderIOEither[A]] Semigroup[A any] = semigroup.Semigroup[ReaderIOResult[A]]
) )
// AltSemigroup is a [Semigroup] that tries the first item and then the second one using an alternative. // AltSemigroup is a [Semigroup] that tries the first item and then the second one using an alternative.
// This creates a semigroup where combining two ReaderIOEither values means trying the first one, // This creates a semigroup where combining two ReaderIOResult values means trying the first one,
// and if it fails, trying the second one. This is useful for implementing fallback behavior. // and if it fails, trying the second one. This is useful for implementing fallback behavior.
// //
// Returns a Semigroup for ReaderIOEither[A] with Alt-based combination. // Returns a Semigroup for ReaderIOResult[A] with Alt-based combination.
// //
// Example: // Example:
// //

View File

@@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package readerioeither package readerioresult
import ( import (
"context" "context"
@@ -29,9 +29,9 @@ import (
// The lock parameter should return a CancelFunc that releases the lock when called. // The lock parameter should return a CancelFunc that releases the lock when called.
// //
// Parameters: // Parameters:
// - lock: ReaderIOEither that acquires a lock and returns a CancelFunc to release it // - lock: ReaderIOResult that acquires a lock and returns a CancelFunc to release it
// //
// Returns a function that wraps a ReaderIOEither with lock protection. // Returns a function that wraps a ReaderIOResult with lock protection.
// //
// Example: // Example:
// //
@@ -43,9 +43,9 @@ import (
// } // }
// }) // })
// protectedOp := WithLock(lock)(myOperation) // protectedOp := WithLock(lock)(myOperation)
func WithLock[A any](lock ReaderIOEither[context.CancelFunc]) Operator[A, A] { func WithLock[A any](lock ReaderIOResult[context.CancelFunc]) Operator[A, A] {
return function.Flow2( return function.Flow2(
function.Constant1[context.CancelFunc, ReaderIOEither[A]], function.Constant1[context.CancelFunc, ReaderIOResult[A]],
WithResource[A](lock, function.Flow2( WithResource[A](lock, function.Flow2(
io.FromImpure[context.CancelFunc], io.FromImpure[context.CancelFunc],
FromIO[any], FromIO[any],

View File

@@ -0,0 +1,306 @@
// 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 readerioresult
import (
"github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/internal/array"
"github.com/IBM/fp-go/v2/internal/record"
)
// TraverseArray transforms an array [[]A] into [[]ReaderIOResult[B]] and then resolves that into a [ReaderIOResult[[]B]].
// This uses the default applicative behavior (parallel or sequential based on useParallel flag).
//
// Parameters:
// - f: Function that transforms each element into a ReaderIOResult
//
// Returns a function that transforms an array into a ReaderIOResult of an array.
func TraverseArray[A, B any](f Kleisli[A, B]) Kleisli[[]A, []B] {
return array.Traverse[[]A](
Of[[]B],
Map[[]B, func(B) []B],
Ap[[]B, B],
f,
)
}
// TraverseArrayWithIndex transforms an array [[]A] into [[]ReaderIOResult[B]] and then resolves that into a [ReaderIOResult[[]B]].
// The transformation function receives both the index and the element.
//
// Parameters:
// - f: Function that transforms each element with its index into a ReaderIOResult
//
// Returns a function that transforms an array into a ReaderIOResult of an array.
func TraverseArrayWithIndex[A, B any](f func(int, A) ReaderIOResult[B]) Kleisli[[]A, []B] {
return array.TraverseWithIndex[[]A](
Of[[]B],
Map[[]B, func(B) []B],
Ap[[]B, B],
f,
)
}
// SequenceArray converts a homogeneous sequence of ReaderIOResult into a ReaderIOResult of sequence.
// This is equivalent to TraverseArray with the identity function.
//
// Parameters:
// - ma: Array of ReaderIOResult values
//
// Returns a ReaderIOResult containing an array of values.
func SequenceArray[A any](ma []ReaderIOResult[A]) ReaderIOResult[[]A] {
return TraverseArray(function.Identity[ReaderIOResult[A]])(ma)
}
// TraverseRecord transforms a record [map[K]A] into [map[K]ReaderIOResult[B]] and then resolves that into a [ReaderIOResult[map[K]B]].
//
// Parameters:
// - f: Function that transforms each value into a ReaderIOResult
//
// Returns a function that transforms a map into a ReaderIOResult of a map.
func TraverseRecord[K comparable, A, B any](f Kleisli[A, B]) Kleisli[map[K]A, map[K]B] {
return record.Traverse[map[K]A](
Of[map[K]B],
Map[map[K]B, func(B) map[K]B],
Ap[map[K]B, B],
f,
)
}
// TraverseRecordWithIndex transforms a record [map[K]A] into [map[K]ReaderIOResult[B]] and then resolves that into a [ReaderIOResult[map[K]B]].
// The transformation function receives both the key and the value.
//
// Parameters:
// - f: Function that transforms each key-value pair into a ReaderIOResult
//
// Returns a function that transforms a map into a ReaderIOResult of a map.
func TraverseRecordWithIndex[K comparable, A, B any](f func(K, A) ReaderIOResult[B]) Kleisli[map[K]A, map[K]B] {
return record.TraverseWithIndex[map[K]A](
Of[map[K]B],
Map[map[K]B, func(B) map[K]B],
Ap[map[K]B, B],
f,
)
}
// SequenceRecord converts a homogeneous map of ReaderIOResult into a ReaderIOResult of map.
//
// Parameters:
// - ma: Map of ReaderIOResult values
//
// Returns a ReaderIOResult containing a map of values.
func SequenceRecord[K comparable, A any](ma map[K]ReaderIOResult[A]) ReaderIOResult[map[K]A] {
return TraverseRecord[K](function.Identity[ReaderIOResult[A]])(ma)
}
// MonadTraverseArraySeq transforms an array [[]A] into [[]ReaderIOResult[B]] and then resolves that into a [ReaderIOResult[[]B]].
// This explicitly uses sequential execution.
//
// Parameters:
// - as: The array to traverse
// - f: Function that transforms each element into a ReaderIOResult
//
// Returns a ReaderIOResult containing an array of transformed values.
func MonadTraverseArraySeq[A, B any](as []A, f Kleisli[A, B]) ReaderIOResult[[]B] {
return array.MonadTraverse(
Of[[]B],
Map[[]B, func(B) []B],
ApSeq[[]B, B],
as,
f,
)
}
// TraverseArraySeq transforms an array [[]A] into [[]ReaderIOResult[B]] and then resolves that into a [ReaderIOResult[[]B]].
// This is the curried version of [MonadTraverseArraySeq] with sequential execution.
//
// Parameters:
// - f: Function that transforms each element into a ReaderIOResult
//
// Returns a function that transforms an array into a ReaderIOResult of an array.
func TraverseArraySeq[A, B any](f Kleisli[A, B]) Kleisli[[]A, []B] {
return array.Traverse[[]A](
Of[[]B],
Map[[]B, func(B) []B],
ApSeq[[]B, B],
f,
)
}
// TraverseArrayWithIndexSeq uses transforms an array [[]A] into [[]ReaderIOResult[B]] and then resolves that into a [ReaderIOResult[[]B]]
func TraverseArrayWithIndexSeq[A, B any](f func(int, A) ReaderIOResult[B]) Kleisli[[]A, []B] {
return array.TraverseWithIndex[[]A](
Of[[]B],
Map[[]B, func(B) []B],
ApSeq[[]B, B],
f,
)
}
// SequenceArraySeq converts a homogeneous sequence of ReaderIOResult into a ReaderIOResult of sequence.
// This explicitly uses sequential execution.
//
// Parameters:
// - ma: Array of ReaderIOResult values
//
// Returns a ReaderIOResult containing an array of values.
func SequenceArraySeq[A any](ma []ReaderIOResult[A]) ReaderIOResult[[]A] {
return MonadTraverseArraySeq(ma, function.Identity[ReaderIOResult[A]])
}
// MonadTraverseRecordSeq uses transforms a record [map[K]A] into [map[K]ReaderIOResult[B]] and then resolves that into a [ReaderIOResult[map[K]B]]
func MonadTraverseRecordSeq[K comparable, A, B any](as map[K]A, f Kleisli[A, B]) ReaderIOResult[map[K]B] {
return record.MonadTraverse(
Of[map[K]B],
Map[map[K]B, func(B) map[K]B],
ApSeq[map[K]B, B],
as,
f,
)
}
// TraverseRecordSeq uses transforms a record [map[K]A] into [map[K]ReaderIOResult[B]] and then resolves that into a [ReaderIOResult[map[K]B]]
func TraverseRecordSeq[K comparable, A, B any](f Kleisli[A, B]) Kleisli[map[K]A, map[K]B] {
return record.Traverse[map[K]A](
Of[map[K]B],
Map[map[K]B, func(B) map[K]B],
ApSeq[map[K]B, B],
f,
)
}
// TraverseRecordWithIndexSeq uses transforms a record [map[K]A] into [map[K]ReaderIOResult[B]] and then resolves that into a [ReaderIOResult[map[K]B]]
func TraverseRecordWithIndexSeq[K comparable, A, B any](f func(K, A) ReaderIOResult[B]) Kleisli[map[K]A, map[K]B] {
return record.TraverseWithIndex[map[K]A](
Of[map[K]B],
Map[map[K]B, func(B) map[K]B],
ApSeq[map[K]B, B],
f,
)
}
// SequenceRecordSeq converts a homogeneous sequence of either into an either of sequence
func SequenceRecordSeq[K comparable, A any](ma map[K]ReaderIOResult[A]) ReaderIOResult[map[K]A] {
return MonadTraverseRecordSeq(ma, function.Identity[ReaderIOResult[A]])
}
// MonadTraverseArrayPar transforms an array [[]A] into [[]ReaderIOResult[B]] and then resolves that into a [ReaderIOResult[[]B]].
// This explicitly uses parallel execution.
//
// Parameters:
// - as: The array to traverse
// - f: Function that transforms each element into a ReaderIOResult
//
// Returns a ReaderIOResult containing an array of transformed values.
func MonadTraverseArrayPar[A, B any](as []A, f Kleisli[A, B]) ReaderIOResult[[]B] {
return array.MonadTraverse(
Of[[]B],
Map[[]B, func(B) []B],
ApPar[[]B, B],
as,
f,
)
}
// TraverseArrayPar transforms an array [[]A] into [[]ReaderIOResult[B]] and then resolves that into a [ReaderIOResult[[]B]].
// This is the curried version of [MonadTraverseArrayPar] with parallel execution.
//
// Parameters:
// - f: Function that transforms each element into a ReaderIOResult
//
// Returns a function that transforms an array into a ReaderIOResult of an array.
func TraverseArrayPar[A, B any](f Kleisli[A, B]) Kleisli[[]A, []B] {
return array.Traverse[[]A](
Of[[]B],
Map[[]B, func(B) []B],
ApPar[[]B, B],
f,
)
}
// TraverseArrayWithIndexPar uses transforms an array [[]A] into [[]ReaderIOResult[B]] and then resolves that into a [ReaderIOResult[[]B]]
func TraverseArrayWithIndexPar[A, B any](f func(int, A) ReaderIOResult[B]) Kleisli[[]A, []B] {
return array.TraverseWithIndex[[]A](
Of[[]B],
Map[[]B, func(B) []B],
ApPar[[]B, B],
f,
)
}
// SequenceArrayPar converts a homogeneous sequence of ReaderIOResult into a ReaderIOResult of sequence.
// This explicitly uses parallel execution.
//
// Parameters:
// - ma: Array of ReaderIOResult values
//
// Returns a ReaderIOResult containing an array of values.
func SequenceArrayPar[A any](ma []ReaderIOResult[A]) ReaderIOResult[[]A] {
return MonadTraverseArrayPar(ma, function.Identity[ReaderIOResult[A]])
}
// TraverseRecordPar uses transforms a record [map[K]A] into [map[K]ReaderIOResult[B]] and then resolves that into a [ReaderIOResult[map[K]B]]
func TraverseRecordPar[K comparable, A, B any](f Kleisli[A, B]) Kleisli[map[K]A, map[K]B] {
return record.Traverse[map[K]A](
Of[map[K]B],
Map[map[K]B, func(B) map[K]B],
ApPar[map[K]B, B],
f,
)
}
// TraverseRecordWithIndexPar uses transforms a record [map[K]A] into [map[K]ReaderIOResult[B]] and then resolves that into a [ReaderIOResult[map[K]B]]
func TraverseRecordWithIndexPar[K comparable, A, B any](f func(K, A) ReaderIOResult[B]) Kleisli[map[K]A, map[K]B] {
return record.TraverseWithIndex[map[K]A](
Of[map[K]B],
Map[map[K]B, func(B) map[K]B],
ApPar[map[K]B, B],
f,
)
}
// MonadTraverseRecordPar uses transforms a record [map[K]A] into [map[K]ReaderIOResult[B]] and then resolves that into a [ReaderIOResult[map[K]B]]
func MonadTraverseRecordPar[K comparable, A, B any](as map[K]A, f Kleisli[A, B]) ReaderIOResult[map[K]B] {
return record.MonadTraverse(
Of[map[K]B],
Map[map[K]B, func(B) map[K]B],
ApPar[map[K]B, B],
as,
f,
)
}
// SequenceRecordPar converts a homogeneous map of ReaderIOResult into a ReaderIOResult of map.
// This explicitly uses parallel execution.
//
// Parameters:
// - ma: Map of ReaderIOResult values
//
// Returns a ReaderIOResult containing a map of values.
func SequenceRecordPar[K comparable, A any](ma map[K]ReaderIOResult[A]) ReaderIOResult[map[K]A] {
return MonadTraverseRecordPar(ma, function.Identity[ReaderIOResult[A]])
}

View File

@@ -13,19 +13,24 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package readerioeither package readerioresult
import ( import (
"context" "context"
"github.com/IBM/fp-go/v2/context/ioresult"
"github.com/IBM/fp-go/v2/context/readerresult"
"github.com/IBM/fp-go/v2/either" "github.com/IBM/fp-go/v2/either"
"github.com/IBM/fp-go/v2/io" "github.com/IBM/fp-go/v2/io"
"github.com/IBM/fp-go/v2/ioeither" "github.com/IBM/fp-go/v2/ioeither"
"github.com/IBM/fp-go/v2/lazy" "github.com/IBM/fp-go/v2/lazy"
"github.com/IBM/fp-go/v2/option" "github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/reader" "github.com/IBM/fp-go/v2/reader"
"github.com/IBM/fp-go/v2/readereither"
"github.com/IBM/fp-go/v2/readerio" "github.com/IBM/fp-go/v2/readerio"
"github.com/IBM/fp-go/v2/readerioeither" RIOR "github.com/IBM/fp-go/v2/readerioresult"
"github.com/IBM/fp-go/v2/readeroption"
"github.com/IBM/fp-go/v2/result"
) )
type ( type (
@@ -40,6 +45,8 @@ type (
// Either[A] is equivalent to Either[error, A] from the either package. // Either[A] is equivalent to Either[error, A] from the either package.
Either[A any] = either.Either[error, A] Either[A any] = either.Either[error, A]
Result[A any] = result.Result[A]
// Lazy represents a deferred computation that produces a value of type A when executed. // Lazy represents a deferred computation that produces a value of type A when executed.
// The computation is not executed until explicitly invoked. // The computation is not executed until explicitly invoked.
Lazy[A any] = lazy.Lazy[A] Lazy[A any] = lazy.Lazy[A]
@@ -56,6 +63,8 @@ type (
// IOEither[A] is equivalent to func() Either[error, A] // IOEither[A] is equivalent to func() Either[error, A]
IOEither[A any] = ioeither.IOEither[error, A] IOEither[A any] = ioeither.IOEither[error, A]
IOResult[A any] = ioresult.IOResult[A]
// Reader represents a computation that depends on a context of type R. // Reader represents a computation that depends on a context of type R.
// This is used for dependency injection and accessing shared context. // This is used for dependency injection and accessing shared context.
// //
@@ -68,21 +77,21 @@ type (
// ReaderIO[A] is equivalent to func(context.Context) func() A // ReaderIO[A] is equivalent to func(context.Context) func() A
ReaderIO[A any] = readerio.ReaderIO[context.Context, A] ReaderIO[A any] = readerio.ReaderIO[context.Context, A]
// ReaderIOEither is the main type of this package. It represents a computation that: // ReaderIOResult is the main type of this package. It represents a computation that:
// - Depends on a [context.Context] (Reader aspect) // - Depends on a [context.Context] (Reader aspect)
// - Performs side effects (IO aspect) // - Performs side effects (IO aspect)
// - Can fail with an [error] (Either aspect) // - Can fail with an [error] (Either aspect)
// - Produces a value of type A on success // - Produces a value of type A on success
// //
// This is a specialization of [readerioeither.ReaderIOEither] with: // This is a specialization of [readerioeither.ReaderIOResult] with:
// - Context type fixed to [context.Context] // - Context type fixed to [context.Context]
// - Error type fixed to [error] // - Error type fixed to [error]
// //
// The type is defined as: // The type is defined as:
// ReaderIOEither[A] = func(context.Context) func() Either[error, A] // ReaderIOResult[A] = func(context.Context) func() Either[error, A]
// //
// Example usage: // Example usage:
// func fetchUser(id string) ReaderIOEither[User] { // func fetchUser(id string) ReaderIOResult[User] {
// return func(ctx context.Context) func() Either[error, User] { // return func(ctx context.Context) func() Either[error, User] {
// return func() Either[error, User] { // return func() Either[error, User] {
// user, err := userService.Get(ctx, id) // user, err := userService.Get(ctx, id)
@@ -97,12 +106,14 @@ type (
// The computation is executed by providing a context and then invoking the result: // The computation is executed by providing a context and then invoking the result:
// ctx := context.Background() // ctx := context.Background()
// result := fetchUser("123")(ctx)() // result := fetchUser("123")(ctx)()
ReaderIOEither[A any] = readerioeither.ReaderIOEither[context.Context, error, A] ReaderIOResult[A any] = RIOR.ReaderIOResult[context.Context, A]
// Operator represents a transformation from one ReaderIOEither to another. Kleisli[A, B any] = reader.Reader[A, ReaderIOResult[B]]
// Operator represents a transformation from one ReaderIOResult to another.
// This is useful for point-free style composition and building reusable transformations. // This is useful for point-free style composition and building reusable transformations.
// //
// Operator[A, B] is equivalent to func(ReaderIOEither[A]) ReaderIOEither[B] // Operator[A, B] is equivalent to Kleisli[ReaderIOResult[A], B]
// //
// Example usage: // Example usage:
// // Define a reusable transformation // // Define a reusable transformation
@@ -110,5 +121,9 @@ type (
// //
// // Apply the transformation // // Apply the transformation
// result := toUpper(computation) // result := toUpper(computation)
Operator[A, B any] = Reader[ReaderIOEither[A], ReaderIOEither[B]] Operator[A, B any] = Kleisli[ReaderIOResult[A], B]
ReaderResult[A any] = readerresult.ReaderResult[A]
ReaderEither[R, E, A any] = readereither.ReaderEither[R, E, A]
ReaderOption[R, A any] = readeroption.ReaderOption[R, A]
) )

View File

@@ -13,21 +13,21 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package readereither package readerresult
import "github.com/IBM/fp-go/v2/readereither" import "github.com/IBM/fp-go/v2/readereither"
// TraverseArray transforms an array // TraverseArray transforms an array
func TraverseArray[A, B any](f func(A) ReaderEither[B]) func([]A) ReaderEither[[]B] { func TraverseArray[A, B any](f Kleisli[A, B]) Kleisli[[]A, []B] {
return readereither.TraverseArray(f) return readereither.TraverseArray(f)
} }
// TraverseArrayWithIndex transforms an array // TraverseArrayWithIndex transforms an array
func TraverseArrayWithIndex[A, B any](f func(int, A) ReaderEither[B]) func([]A) ReaderEither[[]B] { func TraverseArrayWithIndex[A, B any](f func(int, A) ReaderResult[B]) Kleisli[[]A, []B] {
return readereither.TraverseArrayWithIndex(f) return readereither.TraverseArrayWithIndex(f)
} }
// SequenceArray converts a homogeneous sequence of either into an either of sequence // SequenceArray converts a homogeneous sequence of either into an either of sequence
func SequenceArray[A any](ma []ReaderEither[A]) ReaderEither[[]A] { func SequenceArray[A any](ma []ReaderResult[A]) ReaderResult[[]A] {
return readereither.SequenceArray(ma) return readereither.SequenceArray(ma)
} }

View File

@@ -0,0 +1,304 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package readerresult
import (
F "github.com/IBM/fp-go/v2/function"
L "github.com/IBM/fp-go/v2/optics/lens"
G "github.com/IBM/fp-go/v2/readereither/generic"
)
// Do creates an empty context of type [S] to be used with the [Bind] operation.
// This is the starting point for do-notation style composition.
//
// Example:
//
// type State struct {
// UserID string
// TenantID string
// }
// result := readereither.Do(State{})
func Do[S any](
empty S,
) ReaderResult[S] {
return G.Do[ReaderResult[S]](empty)
}
// Bind attaches the result of a computation to a context [S1] to produce a context [S2].
// This enables sequential composition where each step can depend on the results of previous steps
// and access the context.Context from the environment.
//
// The setter function takes the result of the computation and returns a function that
// updates the context from S1 to S2.
//
// Example:
//
// type State struct {
// UserID string
// TenantID string
// }
//
// result := F.Pipe2(
// readereither.Do(State{}),
// readereither.Bind(
// func(uid string) func(State) State {
// return func(s State) State { s.UserID = uid; return s }
// },
// func(s State) readereither.ReaderResult[string] {
// return func(ctx context.Context) either.Either[error, string] {
// if uid, ok := ctx.Value("userID").(string); ok {
// return either.Right[error](uid)
// }
// return either.Left[string](errors.New("no userID"))
// }
// },
// ),
// readereither.Bind(
// func(tid string) func(State) State {
// return func(s State) State { s.TenantID = tid; return s }
// },
// func(s State) readereither.ReaderResult[string] {
// // This can access s.UserID from the previous step
// return func(ctx context.Context) either.Either[error, string] {
// return either.Right[error]("tenant-" + s.UserID)
// }
// },
// ),
// )
func Bind[S1, S2, T any](
setter func(T) func(S1) S2,
f Kleisli[S1, T],
) Kleisli[ReaderResult[S1], S2] {
return G.Bind[ReaderResult[S1], ReaderResult[S2]](setter, f)
}
// Let attaches the result of a computation to a context [S1] to produce a context [S2]
func Let[S1, S2, T any](
setter func(T) func(S1) S2,
f func(S1) T,
) Kleisli[ReaderResult[S1], S2] {
return G.Let[ReaderResult[S1], ReaderResult[S2]](setter, f)
}
// LetTo attaches the a value to a context [S1] to produce a context [S2]
func LetTo[S1, S2, T any](
setter func(T) func(S1) S2,
b T,
) Kleisli[ReaderResult[S1], S2] {
return G.LetTo[ReaderResult[S1], ReaderResult[S2]](setter, b)
}
// BindTo initializes a new state [S1] from a value [T]
func BindTo[S1, T any](
setter func(T) S1,
) Kleisli[ReaderResult[T], S1] {
return G.BindTo[ReaderResult[S1], ReaderResult[T]](setter)
}
// ApS attaches a value to a context [S1] to produce a context [S2] by considering
// the context and the value concurrently (using Applicative rather than Monad).
// This allows independent computations to be combined without one depending on the result of the other.
//
// Unlike Bind, which sequences operations, ApS can be used when operations are independent
// and can conceptually run in parallel.
//
// Example:
//
// type State struct {
// UserID string
// TenantID string
// }
//
// // These operations are independent and can be combined with ApS
// getUserID := func(ctx context.Context) either.Either[error, string] {
// return either.Right[error](ctx.Value("userID").(string))
// }
// getTenantID := func(ctx context.Context) either.Either[error, string] {
// return either.Right[error](ctx.Value("tenantID").(string))
// }
//
// result := F.Pipe2(
// readereither.Do(State{}),
// readereither.ApS(
// func(uid string) func(State) State {
// return func(s State) State { s.UserID = uid; return s }
// },
// getUserID,
// ),
// readereither.ApS(
// func(tid string) func(State) State {
// return func(s State) State { s.TenantID = tid; return s }
// },
// getTenantID,
// ),
// )
func ApS[S1, S2, T any](
setter func(T) func(S1) S2,
fa ReaderResult[T],
) Kleisli[ReaderResult[S1], S2] {
return G.ApS[ReaderResult[S1], ReaderResult[S2]](setter, fa)
}
// ApSL is a variant of ApS that uses a lens to focus on a specific field in the state.
// Instead of providing a setter function, you provide a lens that knows how to get and set
// the field. This is more convenient when working with nested structures.
//
// Parameters:
// - lens: A lens that focuses on a field of type T within state S
// - fa: A ReaderResult computation that produces a value of type T
//
// Returns:
// - A function that transforms ReaderResult[S] to ReaderResult[S] by setting the focused field
//
// 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 },
// )
//
// getAge := func(ctx context.Context) either.Either[error, int] {
// return either.Right[error](30)
// }
//
// result := F.Pipe1(
// readereither.Do(Person{Name: "Alice", Age: 25}),
// readereither.ApSL(ageLens, getAge),
// )
func ApSL[S, T any](
lens L.Lens[S, T],
fa ReaderResult[T],
) Kleisli[ReaderResult[S], S] {
return ApS(lens.Set, fa)
}
// BindL is a variant of Bind that uses a lens to focus on a specific field in the state.
// It combines the lens-based field access with monadic composition, allowing you to:
// 1. Extract a field value using the lens
// 2. Use that value in a computation that may fail
// 3. Update the field with the result
//
// Parameters:
// - lens: A lens that focuses on a field of type T within state S
// - f: A function that takes the current field value and returns a ReaderResult computation
//
// Returns:
// - A function that transforms ReaderResult[S] to ReaderResult[S]
//
// 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 := func(v int) readereither.ReaderResult[int] {
// return func(ctx context.Context) either.Either[error, int] {
// if v >= 100 {
// return either.Left[int](errors.New("value too large"))
// }
// return either.Right[error](v + 1)
// }
// }
//
// result := F.Pipe1(
// readereither.Of[error](Counter{Value: 42}),
// readereither.BindL(valueLens, increment),
// )
func BindL[S, T any](
lens L.Lens[S, T],
f Kleisli[T, T],
) Kleisli[ReaderResult[S], S] {
return Bind(lens.Set, F.Flow2(lens.Get, f))
}
// LetL is a variant of Let that uses a lens to focus on a specific field in the state.
// It applies a pure transformation to the focused field without any effects.
//
// Parameters:
// - lens: A lens that focuses on a field of type T within state S
// - f: A pure function that transforms the field value
//
// Returns:
// - A function that transforms ReaderResult[S] to ReaderResult[S]
//
// 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 := func(v int) int { return v * 2 }
//
// result := F.Pipe1(
// readereither.Of[error](Counter{Value: 21}),
// readereither.LetL(valueLens, double),
// )
// // result when executed will be Right(Counter{Value: 42})
func LetL[S, T any](
lens L.Lens[S, T],
f func(T) T,
) Kleisli[ReaderResult[S], S] {
return Let(lens.Set, F.Flow2(lens.Get, f))
}
// LetToL is a variant of LetTo that uses a lens to focus on a specific field in the state.
// It sets the focused field to a constant value.
//
// Parameters:
// - lens: A lens that focuses on a field of type T within state S
// - b: The constant value to set
//
// Returns:
// - A function that transforms ReaderResult[S] to ReaderResult[S]
//
// 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(
// readereither.Of[error](Config{Debug: true, Timeout: 30}),
// readereither.LetToL(debugLens, false),
// )
// // result when executed will be Right(Config{Debug: false, Timeout: 30})
func LetToL[S, T any](
lens L.Lens[S, T],
b T,
) Kleisli[ReaderResult[S], S] {
return LetTo(lens.Set, b)
}

View File

@@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package readereither package readerresult
import ( import (
"context" "context"
@@ -25,11 +25,11 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func getLastName(s utils.Initial) ReaderEither[string] { func getLastName(s utils.Initial) ReaderResult[string] {
return Of("Doe") return Of("Doe")
} }
func getGivenName(s utils.WithLastName) ReaderEither[string] { func getGivenName(s utils.WithLastName) ReaderResult[string] {
return Of("John") return Of("John")
} }

View File

@@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package readereither package readerresult
import ( import (
"context" "context"
@@ -21,8 +21,8 @@ import (
E "github.com/IBM/fp-go/v2/either" E "github.com/IBM/fp-go/v2/either"
) )
// withContext wraps an existing ReaderEither and performs a context check for cancellation before deletating // withContext wraps an existing ReaderResult and performs a context check for cancellation before deletating
func WithContext[A any](ma ReaderEither[A]) ReaderEither[A] { func WithContext[A any](ma ReaderResult[A]) ReaderResult[A] {
return func(ctx context.Context) E.Either[error, A] { return func(ctx context.Context) E.Either[error, A] {
if err := context.Cause(ctx); err != nil { if err := context.Cause(ctx); err != nil {
return E.Left[A](err) return E.Left[A](err)

View File

@@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package readereither package readerresult
import ( import (
"context" "context"
@@ -24,30 +24,30 @@ import (
// these functions curry a golang function with the context as the firsr parameter into a either reader with the context as the last parameter // these functions curry a golang function with the context as the firsr parameter into a either reader with the context as the last parameter
// this goes back to the advice in https://pkg.go.dev/context to put the context as a first parameter as a convention // this goes back to the advice in https://pkg.go.dev/context to put the context as a first parameter as a convention
func Curry0[A any](f func(context.Context) (A, error)) ReaderEither[A] { func Curry0[A any](f func(context.Context) (A, error)) ReaderResult[A] {
return readereither.Curry0(f) return readereither.Curry0(f)
} }
func Curry1[T1, A any](f func(context.Context, T1) (A, error)) func(T1) ReaderEither[A] { func Curry1[T1, A any](f func(context.Context, T1) (A, error)) Kleisli[T1, A] {
return readereither.Curry1(f) return readereither.Curry1(f)
} }
func Curry2[T1, T2, A any](f func(context.Context, T1, T2) (A, error)) func(T1) func(T2) ReaderEither[A] { func Curry2[T1, T2, A any](f func(context.Context, T1, T2) (A, error)) func(T1) Kleisli[T2, A] {
return readereither.Curry2(f) return readereither.Curry2(f)
} }
func Curry3[T1, T2, T3, A any](f func(context.Context, T1, T2, T3) (A, error)) func(T1) func(T2) func(T3) ReaderEither[A] { func Curry3[T1, T2, T3, A any](f func(context.Context, T1, T2, T3) (A, error)) func(T1) func(T2) Kleisli[T3, A] {
return readereither.Curry3(f) return readereither.Curry3(f)
} }
func Uncurry1[T1, A any](f func(T1) ReaderEither[A]) func(context.Context, T1) (A, error) { func Uncurry1[T1, A any](f Kleisli[T1, A]) func(context.Context, T1) (A, error) {
return readereither.Uncurry1(f) return readereither.Uncurry1(f)
} }
func Uncurry2[T1, T2, A any](f func(T1) func(T2) ReaderEither[A]) func(context.Context, T1, T2) (A, error) { func Uncurry2[T1, T2, A any](f func(T1) Kleisli[T2, A]) func(context.Context, T1, T2) (A, error) {
return readereither.Uncurry2(f) return readereither.Uncurry2(f)
} }
func Uncurry3[T1, T2, T3, A any](f func(T1) func(T2) func(T3) ReaderEither[A]) func(context.Context, T1, T2, T3) (A, error) { func Uncurry3[T1, T2, T3, A any](f func(T1) func(T2) Kleisli[T3, A]) func(context.Context, T1, T2, T3) (A, error) {
return readereither.Uncurry3(f) return readereither.Uncurry3(f)
} }

View File

@@ -18,11 +18,10 @@ package exec
import ( import (
"context" "context"
"github.com/IBM/fp-go/v2/context/readereither"
"github.com/IBM/fp-go/v2/either"
"github.com/IBM/fp-go/v2/exec" "github.com/IBM/fp-go/v2/exec"
"github.com/IBM/fp-go/v2/function" "github.com/IBM/fp-go/v2/function"
INTE "github.com/IBM/fp-go/v2/internal/exec" INTE "github.com/IBM/fp-go/v2/internal/exec"
"github.com/IBM/fp-go/v2/result"
) )
var ( var (
@@ -32,8 +31,8 @@ var (
Command = function.Curry3(command) Command = function.Curry3(command)
) )
func command(name string, args []string, in []byte) readereither.ReaderEither[exec.CommandOutput] { func command(name string, args []string, in []byte) ReaderResult[exec.CommandOutput] {
return func(ctx context.Context) either.Either[error, exec.CommandOutput] { return func(ctx context.Context) Result[exec.CommandOutput] {
return either.TryCatchError(INTE.Exec(ctx, name, args, in)) return result.TryCatchError(INTE.Exec(ctx, name, args, in))
} }
} }

View File

@@ -0,0 +1,27 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// package readerresult implements a specialization of the Reader monad assuming a golang context as the context of the monad and a standard golang error
package exec
import (
"github.com/IBM/fp-go/v2/context/readerresult"
"github.com/IBM/fp-go/v2/result"
)
type (
Result[T any] = result.Result[T]
ReaderResult[T any] = readerresult.ReaderResult[T]
)

View File

@@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package readereither package readerresult
import ( import (
"context" "context"
@@ -24,18 +24,18 @@ import (
// these functions curry a golang function with the context as the firsr parameter into a either reader with the context as the last parameter // these functions curry a golang function with the context as the firsr parameter into a either reader with the context as the last parameter
// this goes back to the advice in https://pkg.go.dev/context to put the context as a first parameter as a convention // this goes back to the advice in https://pkg.go.dev/context to put the context as a first parameter as a convention
func From0[A any](f func(context.Context) (A, error)) func() ReaderEither[A] { func From0[A any](f func(context.Context) (A, error)) func() ReaderResult[A] {
return readereither.From0(f) return readereither.From0(f)
} }
func From1[T1, A any](f func(context.Context, T1) (A, error)) func(T1) ReaderEither[A] { func From1[T1, A any](f func(context.Context, T1) (A, error)) Kleisli[T1, A] {
return readereither.From1(f) return readereither.From1(f)
} }
func From2[T1, T2, A any](f func(context.Context, T1, T2) (A, error)) func(T1, T2) ReaderEither[A] { func From2[T1, T2, A any](f func(context.Context, T1, T2) (A, error)) func(T1, T2) ReaderResult[A] {
return readereither.From2(f) return readereither.From2(f)
} }
func From3[T1, T2, T3, A any](f func(context.Context, T1, T2, T3) (A, error)) func(T1, T2, T3) ReaderEither[A] { func From3[T1, T2, T3, A any](f func(context.Context, T1, T2, T3) (A, error)) func(T1, T2, T3) ReaderResult[A] {
return readereither.From3(f) return readereither.From3(f)
} }

View File

@@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package readereither package readerresult
import ( import (
"context" "context"
@@ -21,19 +21,19 @@ import (
"github.com/IBM/fp-go/v2/readereither" "github.com/IBM/fp-go/v2/readereither"
) )
func FromEither[A any](e Either[A]) ReaderEither[A] { func FromEither[A any](e Either[A]) ReaderResult[A] {
return readereither.FromEither[context.Context](e) return readereither.FromEither[context.Context](e)
} }
func Left[A any](l error) ReaderEither[A] { func Left[A any](l error) ReaderResult[A] {
return readereither.Left[context.Context, A](l) return readereither.Left[context.Context, A](l)
} }
func Right[A any](r A) ReaderEither[A] { func Right[A any](r A) ReaderResult[A] {
return readereither.Right[context.Context, error](r) return readereither.Right[context.Context, error](r)
} }
func MonadMap[A, B any](fa ReaderEither[A], f func(A) B) ReaderEither[B] { func MonadMap[A, B any](fa ReaderResult[A], f func(A) B) ReaderResult[B] {
return readereither.MonadMap(fa, f) return readereither.MonadMap(fa, f)
} }
@@ -41,43 +41,43 @@ func Map[A, B any](f func(A) B) Operator[A, B] {
return readereither.Map[context.Context, error](f) return readereither.Map[context.Context, error](f)
} }
func MonadChain[A, B any](ma ReaderEither[A], f func(A) ReaderEither[B]) ReaderEither[B] { func MonadChain[A, B any](ma ReaderResult[A], f Kleisli[A, B]) ReaderResult[B] {
return readereither.MonadChain(ma, f) return readereither.MonadChain(ma, f)
} }
func Chain[A, B any](f func(A) ReaderEither[B]) Operator[A, B] { func Chain[A, B any](f Kleisli[A, B]) Operator[A, B] {
return readereither.Chain(f) return readereither.Chain(f)
} }
func Of[A any](a A) ReaderEither[A] { func Of[A any](a A) ReaderResult[A] {
return readereither.Of[context.Context, error](a) return readereither.Of[context.Context, error](a)
} }
func MonadAp[A, B any](fab ReaderEither[func(A) B], fa ReaderEither[A]) ReaderEither[B] { func MonadAp[A, B any](fab ReaderResult[func(A) B], fa ReaderResult[A]) ReaderResult[B] {
return readereither.MonadAp(fab, fa) return readereither.MonadAp(fab, fa)
} }
func Ap[A, B any](fa ReaderEither[A]) func(ReaderEither[func(A) B]) ReaderEither[B] { func Ap[A, B any](fa ReaderResult[A]) func(ReaderResult[func(A) B]) ReaderResult[B] {
return readereither.Ap[B](fa) return readereither.Ap[B](fa)
} }
func FromPredicate[A any](pred func(A) bool, onFalse func(A) error) func(A) ReaderEither[A] { func FromPredicate[A any](pred func(A) bool, onFalse func(A) error) Kleisli[A, A] {
return readereither.FromPredicate[context.Context](pred, onFalse) return readereither.FromPredicate[context.Context](pred, onFalse)
} }
func OrElse[A any](onLeft func(error) ReaderEither[A]) func(ReaderEither[A]) ReaderEither[A] { func OrElse[A any](onLeft Kleisli[error, A]) Kleisli[ReaderResult[A], A] {
return readereither.OrElse(onLeft) return readereither.OrElse(onLeft)
} }
func Ask() ReaderEither[context.Context] { func Ask() ReaderResult[context.Context] {
return readereither.Ask[context.Context, error]() return readereither.Ask[context.Context, error]()
} }
func MonadChainEitherK[A, B any](ma ReaderEither[A], f func(A) Either[B]) ReaderEither[B] { func MonadChainEitherK[A, B any](ma ReaderResult[A], f func(A) Either[B]) ReaderResult[B] {
return readereither.MonadChainEitherK(ma, f) return readereither.MonadChainEitherK(ma, f)
} }
func ChainEitherK[A, B any](f func(A) Either[B]) func(ma ReaderEither[A]) ReaderEither[B] { func ChainEitherK[A, B any](f func(A) Either[B]) func(ma ReaderResult[A]) ReaderResult[B] {
return readereither.ChainEitherK[context.Context](f) return readereither.ChainEitherK[context.Context](f)
} }
@@ -85,10 +85,15 @@ func ChainOptionK[A, B any](onNone func() error) func(func(A) Option[B]) Operato
return readereither.ChainOptionK[context.Context, A, B](onNone) return readereither.ChainOptionK[context.Context, A, B](onNone)
} }
func MonadFlap[B, A any](fab ReaderEither[func(A) B], a A) ReaderEither[B] { func MonadFlap[B, A any](fab ReaderResult[func(A) B], a A) ReaderResult[B] {
return readereither.MonadFlap(fab, a) return readereither.MonadFlap(fab, a)
} }
func Flap[B, A any](a A) Operator[func(A) B, B] { func Flap[B, A any](a A) Operator[func(A) B, B] {
return readereither.Flap[context.Context, error, B](a) return readereither.Flap[context.Context, error, B](a)
} }
//go:inline
func Read[A any](r context.Context) func(ReaderResult[A]) Result[A] {
return readereither.Read[error, A](r)
}

View File

@@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package readereither package readerresult
import ( import (
"github.com/IBM/fp-go/v2/readereither" "github.com/IBM/fp-go/v2/readereither"
@@ -22,18 +22,18 @@ import (
// SequenceT converts n inputs of higher kinded types into a higher kinded types of n strongly typed values, represented as a tuple // SequenceT converts n inputs of higher kinded types into a higher kinded types of n strongly typed values, represented as a tuple
func SequenceT1[A any](a ReaderEither[A]) ReaderEither[tuple.Tuple1[A]] { func SequenceT1[A any](a ReaderResult[A]) ReaderResult[tuple.Tuple1[A]] {
return readereither.SequenceT1(a) return readereither.SequenceT1(a)
} }
func SequenceT2[A, B any](a ReaderEither[A], b ReaderEither[B]) ReaderEither[tuple.Tuple2[A, B]] { func SequenceT2[A, B any](a ReaderResult[A], b ReaderResult[B]) ReaderResult[tuple.Tuple2[A, B]] {
return readereither.SequenceT2(a, b) return readereither.SequenceT2(a, b)
} }
func SequenceT3[A, B, C any](a ReaderEither[A], b ReaderEither[B], c ReaderEither[C]) ReaderEither[tuple.Tuple3[A, B, C]] { func SequenceT3[A, B, C any](a ReaderResult[A], b ReaderResult[B], c ReaderResult[C]) ReaderResult[tuple.Tuple3[A, B, C]] {
return readereither.SequenceT3(a, b, c) return readereither.SequenceT3(a, b, c)
} }
func SequenceT4[A, B, C, D any](a ReaderEither[A], b ReaderEither[B], c ReaderEither[C], d ReaderEither[D]) ReaderEither[tuple.Tuple4[A, B, C, D]] { func SequenceT4[A, B, C, D any](a ReaderResult[A], b ReaderResult[B], c ReaderResult[C], d ReaderResult[D]) ReaderResult[tuple.Tuple4[A, B, C, D]] {
return readereither.SequenceT4(a, b, c, d) return readereither.SequenceT4(a, b, c, d)
} }

View File

@@ -13,8 +13,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// Package readereither implements a specialization of the Reader monad assuming a golang context as the context of the monad and a standard golang error // package readerresult implements a specialization of the Reader monad assuming a golang context as the context of the monad and a standard golang error
package readereither package readerresult
import ( import (
"context" "context"
@@ -23,13 +23,16 @@ import (
"github.com/IBM/fp-go/v2/option" "github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/reader" "github.com/IBM/fp-go/v2/reader"
"github.com/IBM/fp-go/v2/readereither" "github.com/IBM/fp-go/v2/readereither"
"github.com/IBM/fp-go/v2/result"
) )
type ( type (
Option[A any] = option.Option[A] Option[A any] = option.Option[A]
Either[A any] = either.Either[error, A] Either[A any] = either.Either[error, A]
// ReaderEither is a specialization of the Reader monad for the typical golang scenario Result[A any] = result.Result[A]
ReaderEither[A any] = readereither.ReaderEither[context.Context, error, A] // ReaderResult is a specialization of the Reader monad for the typical golang scenario
ReaderResult[A any] = readereither.ReaderEither[context.Context, error, A]
Operator[A, B any] = reader.Reader[ReaderEither[A], ReaderEither[B]] Kleisli[A, B any] = reader.Reader[A, ReaderResult[B]]
Operator[A, B any] = Kleisli[ReaderResult[A], B]
) )

8340
v2/coverage.out Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -19,7 +19,7 @@ import (
DIE "github.com/IBM/fp-go/v2/di/erasure" DIE "github.com/IBM/fp-go/v2/di/erasure"
F "github.com/IBM/fp-go/v2/function" F "github.com/IBM/fp-go/v2/function"
IO "github.com/IBM/fp-go/v2/io" IO "github.com/IBM/fp-go/v2/io"
IOE "github.com/IBM/fp-go/v2/ioeither" IOR "github.com/IBM/fp-go/v2/ioresult"
) )
var ( var (
@@ -34,5 +34,5 @@ var (
var RunMain = F.Flow3( var RunMain = F.Flow3(
DIE.MakeInjector, DIE.MakeInjector,
Main, Main,
IOE.Fold(IO.Of[error], F.Constant1[any](IO.Of[error](nil))), IOR.Fold(IO.Of[error], F.Constant1[any](IO.Of[error](nil))),
) )

View File

@@ -64,8 +64,8 @@ Creating and using dependencies:
dbProvider := di.MakeProvider1( dbProvider := di.MakeProvider1(
DBToken, DBToken,
ConfigToken.Identity(), ConfigToken.Identity(),
func(cfg Config) IOE.IOEither[error, Database] { func(cfg Config) IOResult[Database] {
return IOE.Of[error](NewDatabase(cfg)) return ioresult.Of(NewDatabase(cfg))
}, },
) )
@@ -73,8 +73,8 @@ Creating and using dependencies:
APIToken, APIToken,
ConfigToken.Identity(), ConfigToken.Identity(),
DBToken.Identity(), DBToken.Identity(),
func(cfg Config, db Database) IOE.IOEither[error, APIService] { func(cfg Config, db Database) IOResult[APIService] {
return IOE.Of[error](NewAPIService(cfg, db)) return ioresult.Of(NewAPIService(cfg, db))
}, },
) )
@@ -116,7 +116,7 @@ MakeProvider0 - No dependencies:
provider := di.MakeProvider0( provider := di.MakeProvider0(
token, token,
IOE.Of[error](value), ioresult.Of(value),
) )
MakeProvider1 - One dependency: MakeProvider1 - One dependency:
@@ -124,8 +124,8 @@ MakeProvider1 - One dependency:
provider := di.MakeProvider1( provider := di.MakeProvider1(
resultToken, resultToken,
dep1Token.Identity(), dep1Token.Identity(),
func(dep1 Dep1Type) IOE.IOEither[error, ResultType] { func(dep1 Dep1Type) IOResult[ResultType] {
return IOE.Of[error](createResult(dep1)) return ioresult.Of(createResult(dep1))
}, },
) )
@@ -135,8 +135,8 @@ MakeProvider2 - Two dependencies:
resultToken, resultToken,
dep1Token.Identity(), dep1Token.Identity(),
dep2Token.Identity(), dep2Token.Identity(),
func(dep1 Dep1Type, dep2 Dep2Type) IOE.IOEither[error, ResultType] { func(dep1 Dep1Type, dep2 Dep2Type) IOResult[ResultType] {
return IOE.Of[error](createResult(dep1, dep2)) return ioresult.Of(createResult(dep1, dep2))
}, },
) )
@@ -153,7 +153,7 @@ provider is registered:
token := di.MakeTokenWithDefault0( token := di.MakeTokenWithDefault0(
"ServiceName", "ServiceName",
IOE.Of[error](defaultImplementation), ioresult.Of(defaultImplementation),
) )
// Or with dependencies // Or with dependencies
@@ -161,8 +161,8 @@ provider is registered:
"ServiceName", "ServiceName",
dep1Token.Identity(), dep1Token.Identity(),
dep2Token.Identity(), dep2Token.Identity(),
func(dep1 Dep1Type, dep2 Dep2Type) IOE.IOEither[error, ResultType] { func(dep1 Dep1Type, dep2 Dep2Type) IOResult[ResultType] {
return IOE.Of[error](createDefault(dep1, dep2)) return ioresult.Of(createDefault(dep1, dep2))
}, },
) )
@@ -208,8 +208,8 @@ The framework provides a convenient pattern for running applications:
mainProvider := di.MakeProvider1( mainProvider := di.MakeProvider1(
di.InjMain, di.InjMain,
APIToken.Identity(), APIToken.Identity(),
func(api APIService) IOE.IOEither[error, any] { func(api APIService) IOResult[any] {
return IOE.Of[error](api.Start()) return ioresult.Of(api.Start())
}, },
) )
@@ -247,8 +247,8 @@ Example 1: Configuration-based Service
clientProvider := di.MakeProvider1( clientProvider := di.MakeProvider1(
ClientToken, ClientToken,
ConfigToken.Identity(), ConfigToken.Identity(),
func(cfg Config) IOE.IOEither[error, HTTPClient] { func(cfg Config) IOResult[HTTPClient] {
return IOE.Of[error](HTTPClient{config: cfg}) return ioresult.Of(HTTPClient{config: cfg})
}, },
) )
@@ -263,8 +263,8 @@ Example 2: Optional Dependencies
serviceProvider := di.MakeProvider1( serviceProvider := di.MakeProvider1(
ServiceToken, ServiceToken,
CacheToken.Option(), // Optional dependency CacheToken.Option(), // Optional dependency
func(cache O.Option[Cache]) IOE.IOEither[error, Service] { func(cache Option[Cache]) IOResult[Service] {
return IOE.Of[error](NewService(cache)) return ioresult.Of(NewService(cache))
}, },
) )
@@ -279,8 +279,8 @@ Example 3: Lazy Dependencies
reporterProvider := di.MakeProvider1( reporterProvider := di.MakeProvider1(
ReporterToken, ReporterToken,
DBToken.IOEither(), // Lazy dependency DBToken.IOEither(), // Lazy dependency
func(dbIO IOE.IOEither[error, Database]) IOE.IOEither[error, Reporter] { func(dbIO IOResult[Database]) IOResult[Reporter] {
return IOE.Of[error](NewReporter(dbIO)) return ioresult.Of(NewReporter(dbIO))
}, },
) )

View File

@@ -20,7 +20,7 @@ import (
"github.com/IBM/fp-go/v2/errors" "github.com/IBM/fp-go/v2/errors"
F "github.com/IBM/fp-go/v2/function" F "github.com/IBM/fp-go/v2/function"
I "github.com/IBM/fp-go/v2/identity" I "github.com/IBM/fp-go/v2/identity"
IOE "github.com/IBM/fp-go/v2/ioeither" IOR "github.com/IBM/fp-go/v2/ioresult"
L "github.com/IBM/fp-go/v2/lazy" L "github.com/IBM/fp-go/v2/lazy"
O "github.com/IBM/fp-go/v2/option" O "github.com/IBM/fp-go/v2/option"
R "github.com/IBM/fp-go/v2/record" R "github.com/IBM/fp-go/v2/record"
@@ -42,8 +42,8 @@ var (
missingProviderError = F.Flow4( missingProviderError = F.Flow4(
Dependency.String, Dependency.String,
errors.OnSome[string]("no provider for dependency [%s]"), errors.OnSome[string]("no provider for dependency [%s]"),
IOE.Left[any, error], IOR.Left[any],
F.Constant1[InjectableFactory, IOE.IOEither[error, any]], F.Constant1[InjectableFactory, IOResult[any]],
) )
// missingProviderErrorOrDefault returns the default [ProviderFactory] or an error // missingProviderErrorOrDefault returns the default [ProviderFactory] or an error
@@ -56,7 +56,7 @@ var (
emptyMulti any = A.Empty[any]() emptyMulti any = A.Empty[any]()
// emptyMultiDependency returns a [ProviderFactory] for an empty, multi dependency // emptyMultiDependency returns a [ProviderFactory] for an empty, multi dependency
emptyMultiDependency = F.Constant1[Dependency](F.Constant1[InjectableFactory](IOE.Of[error](emptyMulti))) emptyMultiDependency = F.Constant1[Dependency](F.Constant1[InjectableFactory](IOR.Of(emptyMulti)))
// handleMissingProvider covers the case of a missing provider. It either // handleMissingProvider covers the case of a missing provider. It either
// returns an error or an empty multi value provider // returns an error or an empty multi value provider
@@ -93,21 +93,21 @@ var (
// isMultiDependency tests if a dependency is a container dependency // isMultiDependency tests if a dependency is a container dependency
func isMultiDependency(dep Dependency) bool { func isMultiDependency(dep Dependency) bool {
return dep.Flag()&Multi == Multi return dep.Flag()&MULTI == MULTI
} }
// isItemProvider tests if a provivder provides a single item // isItemProvider tests if a provivder provides a single item
func isItemProvider(provider Provider) bool { func isItemProvider(provider Provider) bool {
return provider.Provides().Flag()&Item == Item return provider.Provides().Flag()&ITEM == ITEM
} }
// itemProviderFactory combines multiple factories into one, returning an array // itemProviderFactory combines multiple factories into one, returning an array
func itemProviderFactory(fcts []ProviderFactory) ProviderFactory { func itemProviderFactory(fcts []ProviderFactory) ProviderFactory {
return func(inj InjectableFactory) IOE.IOEither[error, any] { return func(inj InjectableFactory) IOResult[any] {
return F.Pipe2( return F.Pipe2(
fcts, fcts,
IOE.TraverseArray(I.Flap[IOE.IOEither[error, any]](inj)), IOR.TraverseArray(I.Flap[IOResult[any]](inj)),
IOE.Map[error](F.ToAny[[]any]), IOR.Map(F.ToAny[[]any]),
) )
} }
} }
@@ -118,7 +118,7 @@ func itemProviderFactory(fcts []ProviderFactory) ProviderFactory {
// makes sure to transitively resolve the required dependencies. // makes sure to transitively resolve the required dependencies.
func MakeInjector(providers []Provider) InjectableFactory { func MakeInjector(providers []Provider) InjectableFactory {
type Result = IOE.IOEither[error, any] type Result = IOResult[any]
type LazyResult = L.Lazy[Result] type LazyResult = L.Lazy[Result]
// resolved stores the values resolved so far, key is the string ID // resolved stores the values resolved so far, key is the string ID
@@ -148,11 +148,11 @@ func MakeInjector(providers []Provider) InjectableFactory {
T.Map2(F.Flow3( T.Map2(F.Flow3(
Dependency.Id, Dependency.Id,
R.Lookup[ProviderFactory, string], R.Lookup[ProviderFactory, string],
I.Ap[O.Option[ProviderFactory]](factoryByID), I.Ap[Option[ProviderFactory]](factoryByID),
), handleMissingProvider), ), handleMissingProvider),
T.Tupled2(O.MonadGetOrElse[ProviderFactory]), T.Tupled2(O.MonadGetOrElse[ProviderFactory]),
I.Ap[IOE.IOEither[error, any]](injFct), I.Ap[IOResult[any]](injFct),
IOE.Memoize[error, any], IOR.Memoize[any],
) )
} }

View File

@@ -19,25 +19,23 @@ import (
"fmt" "fmt"
A "github.com/IBM/fp-go/v2/array" A "github.com/IBM/fp-go/v2/array"
E "github.com/IBM/fp-go/v2/either"
F "github.com/IBM/fp-go/v2/function" F "github.com/IBM/fp-go/v2/function"
I "github.com/IBM/fp-go/v2/identity" I "github.com/IBM/fp-go/v2/identity"
IO "github.com/IBM/fp-go/v2/io" IO "github.com/IBM/fp-go/v2/io"
IOE "github.com/IBM/fp-go/v2/ioeither" IOE "github.com/IBM/fp-go/v2/ioeither"
IOO "github.com/IBM/fp-go/v2/iooption"
Int "github.com/IBM/fp-go/v2/number/integer" Int "github.com/IBM/fp-go/v2/number/integer"
O "github.com/IBM/fp-go/v2/option"
R "github.com/IBM/fp-go/v2/record" R "github.com/IBM/fp-go/v2/record"
"github.com/IBM/fp-go/v2/result"
) )
type ( type (
// InjectableFactory is a factory function that can create an untyped instance of a service based on its [Dependency] identifier // InjectableFactory is a factory function that can create an untyped instance of a service based on its [Dependency] identifier
InjectableFactory = func(Dependency) IOE.IOEither[error, any] InjectableFactory = func(Dependency) IOResult[any]
ProviderFactory = func(InjectableFactory) IOE.IOEither[error, any] ProviderFactory = func(InjectableFactory) IOResult[any]
paramIndex = map[int]int paramIndex = map[int]int
paramValue = map[int]any paramValue = map[int]any
handler = func(paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] handler = func(paramIndex) func([]IOResult[any]) IOResult[paramValue]
mapping = map[int]paramIndex mapping = map[int]paramIndex
Provider interface { Provider interface {
@@ -83,50 +81,50 @@ var (
mergeMaps = R.UnionLastMonoid[int, any]() mergeMaps = R.UnionLastMonoid[int, any]()
collectParams = R.CollectOrd[any, any](Int.Ord)(F.SK[int, any]) collectParams = R.CollectOrd[any, any](Int.Ord)(F.SK[int, any])
mapDeps = F.Curry2(A.MonadMap[Dependency, IOE.IOEither[error, any]]) mapDeps = F.Curry2(A.MonadMap[Dependency, IOResult[any]])
handlers = map[int]handler{ handlers = map[int]handler{
Identity: func(mp paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] { IDENTITY: func(mp paramIndex) func([]IOResult[any]) IOResult[paramValue] {
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] { return func(res []IOResult[any]) IOResult[paramValue] {
return F.Pipe1( return F.Pipe1(
mp, mp,
IOE.TraverseRecord[int](getAt(res)), IOE.TraverseRecord[int](getAt(res)),
) )
} }
}, },
Option: func(mp paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] { OPTION: func(mp paramIndex) func([]IOResult[any]) IOResult[paramValue] {
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] { return func(res []IOResult[any]) IOResult[paramValue] {
return F.Pipe3( return F.Pipe3(
mp, mp,
IO.TraverseRecord[int](getAt(res)), IO.TraverseRecord[int](getAt(res)),
IO.Map(R.Map[int](F.Flow2( IO.Map(R.Map[int](F.Flow2(
E.ToOption[error, any], result.ToOption[any],
F.ToAny[O.Option[any]], F.ToAny[Option[any]],
))), ))),
IOE.FromIO[error, paramValue], IOE.FromIO[error, paramValue],
) )
} }
}, },
IOEither: func(mp paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] { IOEITHER: func(mp paramIndex) func([]IOResult[any]) IOResult[paramValue] {
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] { return func(res []IOResult[any]) IOResult[paramValue] {
return F.Pipe2( return F.Pipe2(
mp, mp,
R.Map[int](F.Flow2( R.Map[int](F.Flow2(
getAt(res), getAt(res),
F.ToAny[IOE.IOEither[error, any]], F.ToAny[IOResult[any]],
)), )),
IOE.Of[error, paramValue], IOE.Of[error, paramValue],
) )
} }
}, },
IOOption: func(mp paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] { IOOPTION: func(mp paramIndex) func([]IOResult[any]) IOResult[paramValue] {
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] { return func(res []IOResult[any]) IOResult[paramValue] {
return F.Pipe2( return F.Pipe2(
mp, mp,
R.Map[int](F.Flow3( R.Map[int](F.Flow3(
getAt(res), getAt(res),
IOE.ToIOOption[error, any], IOE.ToIOOption[error, any],
F.ToAny[IOO.IOOption[any]], F.ToAny[IOOption[any]],
)), )),
IOE.Of[error, paramValue], IOE.Of[error, paramValue],
) )
@@ -141,23 +139,23 @@ func getAt[T any](ar []T) func(idx int) T {
} }
} }
func handleMapping(mp mapping) func(res []IOE.IOEither[error, any]) IOE.IOEither[error, []any] { func handleMapping(mp mapping) func(res []IOResult[any]) IOResult[[]any] {
preFct := F.Pipe1( preFct := F.Pipe1(
mp, mp,
R.Collect(func(idx int, p paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] { R.Collect(func(idx int, p paramIndex) func([]IOResult[any]) IOResult[paramValue] {
return handlers[idx](p) return handlers[idx](p)
}), }),
) )
doFct := F.Flow2( doFct := F.Flow2(
I.Flap[IOE.IOEither[error, paramValue], []IOE.IOEither[error, any]], I.Flap[IOResult[paramValue], []IOResult[any]],
IOE.TraverseArray[error, func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue], paramValue], IOE.TraverseArray[error, func([]IOResult[any]) IOResult[paramValue], paramValue],
) )
postFct := IOE.Map[error](F.Flow2( postFct := IOE.Map[error](F.Flow2(
A.Fold(mergeMaps), A.Fold(mergeMaps),
collectParams, collectParams,
)) ))
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, []any] { return func(res []IOResult[any]) IOResult[[]any] {
return F.Pipe2( return F.Pipe2(
preFct, preFct,
doFct(res), doFct(res),
@@ -170,7 +168,7 @@ func handleMapping(mp mapping) func(res []IOE.IOEither[error, any]) IOE.IOEither
// a function that accepts the resolved dependencies to return a result // a function that accepts the resolved dependencies to return a result
func MakeProviderFactory( func MakeProviderFactory(
deps []Dependency, deps []Dependency,
fct func(param ...any) IOE.IOEither[error, any]) ProviderFactory { fct func(param ...any) IOResult[any]) ProviderFactory {
return F.Flow3( return F.Flow3(
mapDeps(deps), mapDeps(deps),

View File

@@ -17,20 +17,18 @@ package erasure
import ( import (
"fmt" "fmt"
O "github.com/IBM/fp-go/v2/option"
) )
const ( const (
BehaviourMask = 0x0f BehaviourMask = 0x0f
Identity = 0 // required dependency IDENTITY = 0 // required dependency
Option = 1 // optional dependency OPTION = 1 // optional dependency
IOEither = 2 // lazy and required IOEITHER = 2 // lazy and required
IOOption = 3 // lazy and optional IOOPTION = 3 // lazy and optional
TypeMask = 0xf0 TypeMask = 0xf0
Multi = 1 << 4 // array of implementations MULTI = 1 << 4 // array of implementations
Item = 2 << 4 // item of a multi token ITEM = 2 << 4 // item of a multi token
) )
// Dependency describes the relationship to a service // Dependency describes the relationship to a service
@@ -41,5 +39,5 @@ type Dependency interface {
// Flag returns a tag that identifies the behaviour of the dependency // Flag returns a tag that identifies the behaviour of the dependency
Flag() int Flag() int
// ProviderFactory optionally returns an attached [ProviderFactory] that represents the default for this dependency // ProviderFactory optionally returns an attached [ProviderFactory] that represents the default for this dependency
ProviderFactory() O.Option[ProviderFactory] ProviderFactory() Option[ProviderFactory]
} }

13
v2/di/erasure/types.go Normal file
View File

@@ -0,0 +1,13 @@
package erasure
import (
"github.com/IBM/fp-go/v2/iooption"
"github.com/IBM/fp-go/v2/ioresult"
"github.com/IBM/fp-go/v2/option"
)
type (
Option[T any] = option.Option[T]
IOResult[T any] = ioresult.IOResult[T]
IOOption[T any] = iooption.IOOption[T]
)

File diff suppressed because it is too large Load Diff

View File

@@ -19,14 +19,14 @@ import (
DIE "github.com/IBM/fp-go/v2/di/erasure" DIE "github.com/IBM/fp-go/v2/di/erasure"
F "github.com/IBM/fp-go/v2/function" F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/identity" "github.com/IBM/fp-go/v2/identity"
IOE "github.com/IBM/fp-go/v2/ioeither" IOR "github.com/IBM/fp-go/v2/ioresult"
RIOE "github.com/IBM/fp-go/v2/readerioeither" RIOR "github.com/IBM/fp-go/v2/readerioresult"
) )
// Resolve performs a type safe resolution of a dependency // Resolve performs a type safe resolution of a dependency
func Resolve[T any](token InjectionToken[T]) RIOE.ReaderIOEither[DIE.InjectableFactory, error, T] { func Resolve[T any](token InjectionToken[T]) RIOR.ReaderIOResult[DIE.InjectableFactory, T] {
return F.Flow2( return F.Flow2(
identity.Ap[IOE.IOEither[error, any]](asDependency(token)), identity.Ap[IOResult[any]](asDependency(token)),
IOE.ChainEitherK(token.Unerase), IOR.ChainResultK(token.Unerase),
) )
} }

View File

@@ -22,9 +22,10 @@ import (
"github.com/IBM/fp-go/v2/errors" "github.com/IBM/fp-go/v2/errors"
F "github.com/IBM/fp-go/v2/function" F "github.com/IBM/fp-go/v2/function"
IOE "github.com/IBM/fp-go/v2/ioeither" IOE "github.com/IBM/fp-go/v2/ioeither"
"github.com/IBM/fp-go/v2/ioresult"
) )
func lookupAt[T any](idx int, token Dependency[T]) func(params []any) E.Either[error, T] { func lookupAt[T any](idx int, token Dependency[T]) func(params []any) Result[T] {
return F.Flow3( return F.Flow3(
A.Lookup[any](idx), A.Lookup[any](idx),
E.FromOption[any](errors.OnNone("No parameter at position %d", idx)), E.FromOption[any](errors.OnNone("No parameter at position %d", idx)),
@@ -32,7 +33,7 @@ func lookupAt[T any](idx int, token Dependency[T]) func(params []any) E.Either[e
) )
} }
func eraseTuple[A, R any](f func(A) IOE.IOEither[error, R]) func(E.Either[error, A]) IOE.IOEither[error, any] { func eraseTuple[A, R any](f func(A) IOResult[R]) func(Result[A]) IOResult[any] {
return F.Flow3( return F.Flow3(
IOE.FromEither[error, A], IOE.FromEither[error, A],
IOE.Chain(f), IOE.Chain(f),
@@ -40,8 +41,8 @@ func eraseTuple[A, R any](f func(A) IOE.IOEither[error, R]) func(E.Either[error,
) )
} }
func eraseProviderFactory0[R any](f IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] { func eraseProviderFactory0[R any](f IOResult[R]) func(params ...any) IOResult[any] {
return func(_ ...any) IOE.IOEither[error, any] { return func(_ ...any) IOResult[any] {
return F.Pipe1( return F.Pipe1(
f, f,
IOE.Map[error](F.ToAny[R]), IOE.Map[error](F.ToAny[R]),
@@ -50,7 +51,7 @@ func eraseProviderFactory0[R any](f IOE.IOEither[error, R]) func(params ...any)
} }
func MakeProviderFactory0[R any]( func MakeProviderFactory0[R any](
fct IOE.IOEither[error, R], fct IOResult[R],
) DIE.ProviderFactory { ) DIE.ProviderFactory {
return DIE.MakeProviderFactory( return DIE.MakeProviderFactory(
A.Empty[DIE.Dependency](), A.Empty[DIE.Dependency](),
@@ -59,13 +60,13 @@ func MakeProviderFactory0[R any](
} }
// MakeTokenWithDefault0 creates a unique [InjectionToken] for a specific type with an attached default [DIE.Provider] // MakeTokenWithDefault0 creates a unique [InjectionToken] for a specific type with an attached default [DIE.Provider]
func MakeTokenWithDefault0[R any](name string, fct IOE.IOEither[error, R]) InjectionToken[R] { func MakeTokenWithDefault0[R any](name string, fct IOResult[R]) InjectionToken[R] {
return MakeTokenWithDefault[R](name, MakeProviderFactory0(fct)) return MakeTokenWithDefault[R](name, MakeProviderFactory0(fct))
} }
func MakeProvider0[R any]( func MakeProvider0[R any](
token InjectionToken[R], token InjectionToken[R],
fct IOE.IOEither[error, R], fct IOResult[R],
) DIE.Provider { ) DIE.Provider {
return DIE.MakeProvider( return DIE.MakeProvider(
token, token,
@@ -75,5 +76,5 @@ func MakeProvider0[R any](
// ConstProvider simple implementation for a provider with a constant value // ConstProvider simple implementation for a provider with a constant value
func ConstProvider[R any](token InjectionToken[R], value R) DIE.Provider { func ConstProvider[R any](token InjectionToken[R], value R) DIE.Provider {
return MakeProvider0[R](token, IOE.Of[error](value)) return MakeProvider0(token, ioresult.Of(value))
} }

View File

@@ -25,7 +25,8 @@ import (
E "github.com/IBM/fp-go/v2/either" E "github.com/IBM/fp-go/v2/either"
F "github.com/IBM/fp-go/v2/function" F "github.com/IBM/fp-go/v2/function"
IOE "github.com/IBM/fp-go/v2/ioeither" IOE "github.com/IBM/fp-go/v2/ioeither"
O "github.com/IBM/fp-go/v2/option" "github.com/IBM/fp-go/v2/ioresult"
"github.com/IBM/fp-go/v2/result"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -39,19 +40,19 @@ func TestSimpleProvider(t *testing.T) {
var staticCount int var staticCount int
staticValue := func(value string) IOE.IOEither[error, string] { staticValue := func(value string) IOResult[string] {
return func() E.Either[error, string] { return func() Result[string] {
staticCount++ staticCount++
return E.Of[error](fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now())) return result.Of(fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now()))
} }
} }
var dynamicCount int var dynamicCount int
dynamicValue := func(value string) IOE.IOEither[error, string] { dynamicValue := func(value string) IOResult[string] {
return func() E.Either[error, string] { return func() Result[string] {
dynamicCount++ dynamicCount++
return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now())) return result.Of(fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
} }
} }
@@ -81,19 +82,19 @@ func TestOptionalProvider(t *testing.T) {
var staticCount int var staticCount int
staticValue := func(value string) IOE.IOEither[error, string] { staticValue := func(value string) IOResult[string] {
return func() E.Either[error, string] { return func() Result[string] {
staticCount++ staticCount++
return E.Of[error](fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now())) return result.Of(fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now()))
} }
} }
var dynamicCount int var dynamicCount int
dynamicValue := func(value O.Option[string]) IOE.IOEither[error, string] { dynamicValue := func(value Option[string]) IOResult[string] {
return func() E.Either[error, string] { return func() Result[string] {
dynamicCount++ dynamicCount++
return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now())) return result.Of(fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
} }
} }
@@ -123,10 +124,10 @@ func TestOptionalProviderMissingDependency(t *testing.T) {
var dynamicCount int var dynamicCount int
dynamicValue := func(value O.Option[string]) IOE.IOEither[error, string] { dynamicValue := func(value Option[string]) IOResult[string] {
return func() E.Either[error, string] { return func() Result[string] {
dynamicCount++ dynamicCount++
return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now())) return result.Of(fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
} }
} }
@@ -151,10 +152,10 @@ func TestProviderMissingDependency(t *testing.T) {
var dynamicCount int var dynamicCount int
dynamicValue := func(value string) IOE.IOEither[error, string] { dynamicValue := func(value string) IOResult[string] {
return func() E.Either[error, string] { return func() Result[string] {
dynamicCount++ dynamicCount++
return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now())) return result.Of(fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
} }
} }
@@ -179,31 +180,31 @@ func TestEagerAndLazyProvider(t *testing.T) {
var staticCount int var staticCount int
staticValue := func(value string) IOE.IOEither[error, string] { staticValue := func(value string) IOResult[string] {
return func() E.Either[error, string] { return func() Result[string] {
staticCount++ staticCount++
return E.Of[error](fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now())) return result.Of(fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now()))
} }
} }
var dynamicCount int var dynamicCount int
dynamicValue := func(value string) IOE.IOEither[error, string] { dynamicValue := func(value string) IOResult[string] {
return func() E.Either[error, string] { return func() Result[string] {
dynamicCount++ dynamicCount++
return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now())) return result.Of(fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
} }
} }
var lazyEagerCount int var lazyEagerCount int
lazyEager := func(laz IOE.IOEither[error, string], eager string) IOE.IOEither[error, string] { lazyEager := func(laz IOResult[string], eager string) IOResult[string] {
return F.Pipe1( return F.Pipe1(
laz, laz,
IOE.Chain(func(lazValue string) IOE.IOEither[error, string] { IOE.Chain(func(lazValue string) IOResult[string] {
return func() E.Either[error, string] { return func() Result[string] {
lazyEagerCount++ lazyEagerCount++
return E.Of[error](fmt.Sprintf("Dynamic based on [%s], [%s] at [%s]", lazValue, eager, time.Now())) return result.Of(fmt.Sprintf("Dynamic based on [%s], [%s] at [%s]", lazValue, eager, time.Now()))
} }
}), }),
) )
@@ -248,7 +249,7 @@ func TestItemProvider(t *testing.T) {
value := multiInj() value := multiInj()
assert.Equal(t, E.Of[error](A.From("Value1", "Value2")), value) assert.Equal(t, result.Of(A.From("Value1", "Value2")), value)
} }
func TestEmptyItemProvider(t *testing.T) { func TestEmptyItemProvider(t *testing.T) {
@@ -269,7 +270,7 @@ func TestEmptyItemProvider(t *testing.T) {
value := multiInj() value := multiInj()
assert.Equal(t, E.Of[error](A.Empty[string]()), value) assert.Equal(t, result.Of(A.Empty[string]()), value)
} }
func TestDependencyOnMultiProvider(t *testing.T) { func TestDependencyOnMultiProvider(t *testing.T) {
@@ -283,8 +284,8 @@ func TestDependencyOnMultiProvider(t *testing.T) {
p1 := ConstProvider(INJ_KEY1, "Value3") p1 := ConstProvider(INJ_KEY1, "Value3")
p2 := ConstProvider(INJ_KEY2, "Value4") p2 := ConstProvider(INJ_KEY2, "Value4")
fromMulti := func(val string, multi []string) IOE.IOEither[error, string] { fromMulti := func(val string, multi []string) IOResult[string] {
return IOE.Of[error](fmt.Sprintf("Val: %s, Multi: %s", val, multi)) return ioresult.Of(fmt.Sprintf("Val: %s, Multi: %s", val, multi))
} }
p3 := MakeProvider2(INJ_KEY3, INJ_KEY1.Identity(), injMulti.Container().Identity(), fromMulti) p3 := MakeProvider2(INJ_KEY3, INJ_KEY1.Identity(), injMulti.Container().Identity(), fromMulti)
@@ -295,19 +296,19 @@ func TestDependencyOnMultiProvider(t *testing.T) {
v := r3(inj)() v := r3(inj)()
assert.Equal(t, E.Of[error]("Val: Value3, Multi: [Value1 Value2]"), v) assert.Equal(t, result.Of("Val: Value3, Multi: [Value1 Value2]"), v)
} }
func TestTokenWithDefaultProvider(t *testing.T) { func TestTokenWithDefaultProvider(t *testing.T) {
// token without a default // token without a default
injToken1 := MakeToken[string]("Token1") injToken1 := MakeToken[string]("Token1")
// token with a default // token with a default
injToken2 := MakeTokenWithDefault0("Token2", IOE.Of[error]("Carsten")) injToken2 := MakeTokenWithDefault0("Token2", ioresult.Of("Carsten"))
// dependency // dependency
injToken3 := MakeToken[string]("Token3") injToken3 := MakeToken[string]("Token3")
p3 := MakeProvider1(injToken3, injToken2.Identity(), func(data string) IOE.IOEither[error, string] { p3 := MakeProvider1(injToken3, injToken2.Identity(), func(data string) IOResult[string] {
return IOE.Of[error](fmt.Sprintf("Token: %s", data)) return ioresult.Of(fmt.Sprintf("Token: %s", data))
}) })
// populate the injector // populate the injector
@@ -320,19 +321,19 @@ func TestTokenWithDefaultProvider(t *testing.T) {
// inj1 should not be available // inj1 should not be available
assert.True(t, E.IsLeft(r1(inj)())) assert.True(t, E.IsLeft(r1(inj)()))
// r3 should work // r3 should work
assert.Equal(t, E.Of[error]("Token: Carsten"), r3(inj)()) assert.Equal(t, result.Of("Token: Carsten"), r3(inj)())
} }
func TestTokenWithDefaultProviderAndOverride(t *testing.T) { func TestTokenWithDefaultProviderAndOverride(t *testing.T) {
// token with a default // token with a default
injToken2 := MakeTokenWithDefault0("Token2", IOE.Of[error]("Carsten")) injToken2 := MakeTokenWithDefault0("Token2", ioresult.Of("Carsten"))
// dependency // dependency
injToken3 := MakeToken[string]("Token3") injToken3 := MakeToken[string]("Token3")
p2 := ConstProvider(injToken2, "Override") p2 := ConstProvider(injToken2, "Override")
p3 := MakeProvider1(injToken3, injToken2.Identity(), func(data string) IOE.IOEither[error, string] { p3 := MakeProvider1(injToken3, injToken2.Identity(), func(data string) IOResult[string] {
return IOE.Of[error](fmt.Sprintf("Token: %s", data)) return ioresult.Of(fmt.Sprintf("Token: %s", data))
}) })
// populate the injector // populate the injector
@@ -342,5 +343,5 @@ func TestTokenWithDefaultProviderAndOverride(t *testing.T) {
r3 := Resolve(injToken3) r3 := Resolve(injToken3)
// r3 should work // r3 should work
assert.Equal(t, E.Of[error]("Token: Override"), r3(inj)()) assert.Equal(t, result.Of("Token: Override"), r3(inj)())
} }

View File

@@ -21,10 +21,7 @@ import (
"sync/atomic" "sync/atomic"
DIE "github.com/IBM/fp-go/v2/di/erasure" DIE "github.com/IBM/fp-go/v2/di/erasure"
E "github.com/IBM/fp-go/v2/either"
IO "github.com/IBM/fp-go/v2/io" IO "github.com/IBM/fp-go/v2/io"
IOE "github.com/IBM/fp-go/v2/ioeither"
IOO "github.com/IBM/fp-go/v2/iooption"
O "github.com/IBM/fp-go/v2/option" O "github.com/IBM/fp-go/v2/option"
) )
@@ -33,7 +30,7 @@ import (
type Dependency[T any] interface { type Dependency[T any] interface {
DIE.Dependency DIE.Dependency
// Unerase converts a value with erased type signature into a strongly typed value // Unerase converts a value with erased type signature into a strongly typed value
Unerase(val any) E.Either[error, T] Unerase(val any) Result[T]
} }
// InjectionToken uniquely identifies a dependency by giving it an Id, Type and name // InjectionToken uniquely identifies a dependency by giving it an Id, Type and name
@@ -42,17 +39,17 @@ type InjectionToken[T any] interface {
// Identity idenifies this dependency as a mandatory, required dependency, it will be resolved eagerly and injected as `T`. // Identity idenifies this dependency as a mandatory, required dependency, it will be resolved eagerly and injected as `T`.
// If the dependency cannot be resolved, the resolution process fails // If the dependency cannot be resolved, the resolution process fails
Identity() Dependency[T] Identity() Dependency[T]
// Option identifies this dependency as optional, it will be resolved eagerly and injected as [O.Option[T]]. // Option identifies this dependency as optional, it will be resolved eagerly and injected as [Option[T]].
// If the dependency cannot be resolved, the resolution process continues and the dependency is represented as [O.None[T]] // If the dependency cannot be resolved, the resolution process continues and the dependency is represented as [O.None[T]]
Option() Dependency[O.Option[T]] Option() Dependency[Option[T]]
// IOEither identifies this dependency as mandatory but it will be resolved lazily as a [IOE.IOEither[error, T]]. This // IOEither identifies this dependency as mandatory but it will be resolved lazily as a [IOResult[T]]. This
// value is memoized to make sure the dependency is a singleton. // value is memoized to make sure the dependency is a singleton.
// If the dependency cannot be resolved, the resolution process fails // If the dependency cannot be resolved, the resolution process fails
IOEither() Dependency[IOE.IOEither[error, T]] IOEither() Dependency[IOResult[T]]
// IOOption identifies this dependency as optional but it will be resolved lazily as a [IOO.IOOption[T]]. This // IOOption identifies this dependency as optional but it will be resolved lazily as a [IOOption[T]]. This
// value is memoized to make sure the dependency is a singleton. // value is memoized to make sure the dependency is a singleton.
// If the dependency cannot be resolved, the resolution process continues and the dependency is represented as the none value. // If the dependency cannot be resolved, the resolution process continues and the dependency is represented as the none value.
IOOption() Dependency[IOO.IOOption[T]] IOOption() Dependency[IOOption[T]]
} }
// MultiInjectionToken uniquely identifies a dependency by giving it an Id, Type and name that can have multiple implementations. // MultiInjectionToken uniquely identifies a dependency by giving it an Id, Type and name that can have multiple implementations.
@@ -79,12 +76,12 @@ type tokenBase struct {
name string name string
id string id string
flag int flag int
providerFactory O.Option[DIE.ProviderFactory] providerFactory Option[DIE.ProviderFactory]
} }
type token[T any] struct { type token[T any] struct {
base *tokenBase base *tokenBase
toType func(val any) E.Either[error, T] toType func(val any) Result[T]
} }
func (t *token[T]) Id() string { func (t *token[T]) Id() string {
@@ -99,26 +96,26 @@ func (t *token[T]) String() string {
return t.base.name return t.base.name
} }
func (t *token[T]) Unerase(val any) E.Either[error, T] { func (t *token[T]) Unerase(val any) Result[T] {
return t.toType(val) return t.toType(val)
} }
func (t *token[T]) ProviderFactory() O.Option[DIE.ProviderFactory] { func (t *token[T]) ProviderFactory() Option[DIE.ProviderFactory] {
return t.base.providerFactory return t.base.providerFactory
} }
func makeTokenBase(name string, id string, typ int, providerFactory O.Option[DIE.ProviderFactory]) *tokenBase { func makeTokenBase(name string, id string, typ int, providerFactory Option[DIE.ProviderFactory]) *tokenBase {
return &tokenBase{name, id, typ, providerFactory} return &tokenBase{name, id, typ, providerFactory}
} }
func makeToken[T any](name string, id string, typ int, unerase func(val any) E.Either[error, T], providerFactory O.Option[DIE.ProviderFactory]) Dependency[T] { func makeToken[T any](name string, id string, typ int, unerase func(val any) Result[T], providerFactory Option[DIE.ProviderFactory]) Dependency[T] {
return &token[T]{makeTokenBase(name, id, typ, providerFactory), unerase} return &token[T]{makeTokenBase(name, id, typ, providerFactory), unerase}
} }
type injectionToken[T any] struct { type injectionToken[T any] struct {
token[T] token[T]
option Dependency[O.Option[T]] option Dependency[Option[T]]
ioeither Dependency[IOE.IOEither[error, T]] ioeither Dependency[IOResult[T]]
iooption Dependency[IOO.IOOption[T]] iooption Dependency[IOOption[T]]
} }
type multiInjectionToken[T any] struct { type multiInjectionToken[T any] struct {
@@ -130,19 +127,19 @@ func (i *injectionToken[T]) Identity() Dependency[T] {
return i return i
} }
func (i *injectionToken[T]) Option() Dependency[O.Option[T]] { func (i *injectionToken[T]) Option() Dependency[Option[T]] {
return i.option return i.option
} }
func (i *injectionToken[T]) IOEither() Dependency[IOE.IOEither[error, T]] { func (i *injectionToken[T]) IOEither() Dependency[IOResult[T]] {
return i.ioeither return i.ioeither
} }
func (i *injectionToken[T]) IOOption() Dependency[IOO.IOOption[T]] { func (i *injectionToken[T]) IOOption() Dependency[IOOption[T]] {
return i.iooption return i.iooption
} }
func (i *injectionToken[T]) ProviderFactory() O.Option[DIE.ProviderFactory] { func (i *injectionToken[T]) ProviderFactory() Option[DIE.ProviderFactory] {
return i.base.providerFactory return i.base.providerFactory
} }
@@ -155,14 +152,14 @@ func (m *multiInjectionToken[T]) Item() InjectionToken[T] {
} }
// makeToken create a unique [InjectionToken] for a specific type // makeToken create a unique [InjectionToken] for a specific type
func makeInjectionToken[T any](name string, providerFactory O.Option[DIE.ProviderFactory]) InjectionToken[T] { func makeInjectionToken[T any](name string, providerFactory Option[DIE.ProviderFactory]) InjectionToken[T] {
id := genID() id := genID()
toIdentity := toType[T]() toIdentity := toType[T]()
return &injectionToken[T]{ return &injectionToken[T]{
token[T]{makeTokenBase(name, id, DIE.Identity, providerFactory), toIdentity}, token[T]{makeTokenBase(name, id, DIE.IDENTITY, providerFactory), toIdentity},
makeToken[O.Option[T]](fmt.Sprintf("Option[%s]", name), id, DIE.Option, toOptionType(toIdentity), providerFactory), makeToken(fmt.Sprintf("Option[%s]", name), id, DIE.OPTION, toOptionType(toIdentity), providerFactory),
makeToken[IOE.IOEither[error, T]](fmt.Sprintf("IOEither[%s]", name), id, DIE.IOEither, toIOEitherType(toIdentity), providerFactory), makeToken(fmt.Sprintf("IOEither[%s]", name), id, DIE.IOEITHER, toIOEitherType(toIdentity), providerFactory),
makeToken[IOO.IOOption[T]](fmt.Sprintf("IOOption[%s]", name), id, DIE.IOOption, toIOOptionType(toIdentity), providerFactory), makeToken(fmt.Sprintf("IOOption[%s]", name), id, DIE.IOOPTION, toIOOptionType(toIdentity), providerFactory),
} }
} }
@@ -187,17 +184,17 @@ func MakeMultiToken[T any](name string) MultiInjectionToken[T] {
providerFactory := O.None[DIE.ProviderFactory]() providerFactory := O.None[DIE.ProviderFactory]()
// container // container
container := &injectionToken[[]T]{ container := &injectionToken[[]T]{
token[[]T]{makeTokenBase(containerName, id, DIE.Multi|DIE.Identity, providerFactory), toContainer}, token[[]T]{makeTokenBase(containerName, id, DIE.MULTI|DIE.IDENTITY, providerFactory), toContainer},
makeToken[O.Option[[]T]](fmt.Sprintf("Option[%s]", containerName), id, DIE.Multi|DIE.Option, toOptionType(toContainer), providerFactory), makeToken(fmt.Sprintf("Option[%s]", containerName), id, DIE.MULTI|DIE.OPTION, toOptionType(toContainer), providerFactory),
makeToken[IOE.IOEither[error, []T]](fmt.Sprintf("IOEither[%s]", containerName), id, DIE.Multi|DIE.IOEither, toIOEitherType(toContainer), providerFactory), makeToken(fmt.Sprintf("IOEither[%s]", containerName), id, DIE.OPTION|DIE.IOEITHER, toIOEitherType(toContainer), providerFactory),
makeToken[IOO.IOOption[[]T]](fmt.Sprintf("IOOption[%s]", containerName), id, DIE.Multi|DIE.IOOption, toIOOptionType(toContainer), providerFactory), makeToken(fmt.Sprintf("IOOption[%s]", containerName), id, DIE.OPTION|DIE.IOOPTION, toIOOptionType(toContainer), providerFactory),
} }
// item // item
item := &injectionToken[T]{ item := &injectionToken[T]{
token[T]{makeTokenBase(itemName, id, DIE.Item|DIE.Identity, providerFactory), toItem}, token[T]{makeTokenBase(itemName, id, DIE.ITEM|DIE.IDENTITY, providerFactory), toItem},
makeToken[O.Option[T]](fmt.Sprintf("Option[%s]", itemName), id, DIE.Item|DIE.Option, toOptionType(toItem), providerFactory), makeToken(fmt.Sprintf("Option[%s]", itemName), id, DIE.ITEM|DIE.OPTION, toOptionType(toItem), providerFactory),
makeToken[IOE.IOEither[error, T]](fmt.Sprintf("IOEither[%s]", itemName), id, DIE.Item|DIE.IOEither, toIOEitherType(toItem), providerFactory), makeToken(fmt.Sprintf("IOEither[%s]", itemName), id, DIE.ITEM|DIE.IOEITHER, toIOEitherType(toItem), providerFactory),
makeToken[IOO.IOOption[T]](fmt.Sprintf("IOOption[%s]", itemName), id, DIE.Item|DIE.IOOption, toIOOptionType(toItem), providerFactory), makeToken(fmt.Sprintf("IOOption[%s]", itemName), id, DIE.ITEM|DIE.IOOPTION, toIOOptionType(toItem), providerFactory),
} }
// returns the token // returns the token
return &multiInjectionToken[T]{container, item} return &multiInjectionToken[T]{container, item}

View File

@@ -23,7 +23,9 @@ import (
F "github.com/IBM/fp-go/v2/function" F "github.com/IBM/fp-go/v2/function"
IOE "github.com/IBM/fp-go/v2/ioeither" IOE "github.com/IBM/fp-go/v2/ioeither"
IOO "github.com/IBM/fp-go/v2/iooption" IOO "github.com/IBM/fp-go/v2/iooption"
"github.com/IBM/fp-go/v2/ioresult"
O "github.com/IBM/fp-go/v2/option" O "github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/result"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -75,9 +77,9 @@ func TestTokenUnerase(t *testing.T) {
token := MakeToken[int]("IntToken") token := MakeToken[int]("IntToken")
// Test successful unerase // Test successful unerase
result := token.Unerase(42) res := token.Unerase(42)
assert.True(t, E.IsRight(result)) assert.True(t, E.IsRight(res))
assert.Equal(t, E.Of[error](42), result) assert.Equal(t, result.Of(42), res)
// Test failed unerase (wrong type) // Test failed unerase (wrong type)
result2 := token.Unerase("not an int") result2 := token.Unerase("not an int")
@@ -104,7 +106,7 @@ func TestTokenProviderFactory(t *testing.T) {
assert.True(t, O.IsNone(token1.ProviderFactory())) assert.True(t, O.IsNone(token1.ProviderFactory()))
// Token with default // Token with default
token2 := MakeTokenWithDefault0("Token2", IOE.Of[error](42)) token2 := MakeTokenWithDefault0("Token2", ioresult.Of(42))
assert.True(t, O.IsSome(token2.ProviderFactory())) assert.True(t, O.IsSome(token2.ProviderFactory()))
} }
@@ -148,13 +150,13 @@ func TestOptionTokenUnerase(t *testing.T) {
optionToken := token.Option() optionToken := token.Option()
// Test successful unerase with Some // Test successful unerase with Some
result := optionToken.Unerase(O.Of[any](42)) res := optionToken.Unerase(O.Of[any](42))
assert.True(t, E.IsRight(result)) assert.True(t, E.IsRight(res))
// Test successful unerase with None // Test successful unerase with None
noneResult := optionToken.Unerase(O.None[any]()) noneResult := optionToken.Unerase(O.None[any]())
assert.True(t, E.IsRight(noneResult)) assert.True(t, E.IsRight(noneResult))
assert.Equal(t, E.Of[error](O.None[int]()), noneResult) assert.Equal(t, result.Of(O.None[int]()), noneResult)
// Test failed unerase (wrong type) // Test failed unerase (wrong type)
badResult := optionToken.Unerase(42) // Not an Option badResult := optionToken.Unerase(42) // Not an Option
@@ -166,7 +168,7 @@ func TestIOEitherTokenUnerase(t *testing.T) {
ioeitherToken := token.IOEither() ioeitherToken := token.IOEither()
// Test successful unerase // Test successful unerase
ioValue := IOE.Of[error](any(42)) ioValue := ioresult.Of(any(42))
result := ioeitherToken.Unerase(ioValue) result := ioeitherToken.Unerase(ioValue)
assert.True(t, E.IsRight(result)) assert.True(t, E.IsRight(result))
@@ -222,7 +224,7 @@ func TestMultiTokenContainerUnerase(t *testing.T) {
} }
func TestMakeTokenWithDefault(t *testing.T) { func TestMakeTokenWithDefault(t *testing.T) {
factory := MakeProviderFactory0(IOE.Of[error](42)) factory := MakeProviderFactory0(ioresult.Of(42))
token := MakeTokenWithDefault[int]("TokenWithDefault", factory) token := MakeTokenWithDefault[int]("TokenWithDefault", factory)
assert.NotNil(t, token) assert.NotNil(t, token)

15
v2/di/types.go Normal file
View File

@@ -0,0 +1,15 @@
package di
import (
"github.com/IBM/fp-go/v2/context/ioresult"
"github.com/IBM/fp-go/v2/iooption"
"github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/result"
)
type (
Option[T any] = option.Option[T]
Result[T any] = result.Result[T]
IOResult[T any] = ioresult.IOResult[T]
IOOption[T any] = iooption.IOOption[T]
)

View File

@@ -23,12 +23,13 @@ import (
IOE "github.com/IBM/fp-go/v2/ioeither" IOE "github.com/IBM/fp-go/v2/ioeither"
IOO "github.com/IBM/fp-go/v2/iooption" IOO "github.com/IBM/fp-go/v2/iooption"
O "github.com/IBM/fp-go/v2/option" O "github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/result"
) )
var ( var (
toOptionAny = toType[O.Option[any]]() toOptionAny = toType[Option[any]]()
toIOEitherAny = toType[IOE.IOEither[error, any]]() toIOEitherAny = toType[IOResult[any]]()
toIOOptionAny = toType[IOO.IOOption[any]]() toIOOptionAny = toType[IOOption[any]]()
toArrayAny = toType[[]any]() toArrayAny = toType[[]any]()
) )
@@ -38,45 +39,45 @@ func asDependency[T DIE.Dependency](t T) DIE.Dependency {
} }
// toType converts an any to a T // toType converts an any to a T
func toType[T any]() func(t any) E.Either[error, T] { func toType[T any]() result.Kleisli[any, T] {
return E.ToType[T](errors.OnSome[any]("Value of type [%T] cannot be converted.")) return E.ToType[T](errors.OnSome[any]("Value of type [%T] cannot be converted."))
} }
// toOptionType converts an any to an Option[any] and then to an Option[T] // toOptionType converts an any to an Option[any] and then to an Option[T]
func toOptionType[T any](item func(any) E.Either[error, T]) func(t any) E.Either[error, O.Option[T]] { func toOptionType[T any](item result.Kleisli[any, T]) result.Kleisli[any, Option[T]] {
return F.Flow2( return F.Flow2(
toOptionAny, toOptionAny,
E.Chain(O.Fold( E.Chain(O.Fold(
F.Nullary2(O.None[T], E.Of[error, O.Option[T]]), F.Nullary2(O.None[T], E.Of[error, Option[T]]),
F.Flow2( F.Flow2(
item, item,
E.Map[error](O.Of[T]), result.Map(O.Of[T]),
), ),
)), )),
) )
} }
// toIOEitherType converts an any to an IOEither[error, any] and then to an IOEither[error, T] // toIOEitherType converts an any to an IOEither[error, any] and then to an IOEither[error, T]
func toIOEitherType[T any](item func(any) E.Either[error, T]) func(t any) E.Either[error, IOE.IOEither[error, T]] { func toIOEitherType[T any](item result.Kleisli[any, T]) result.Kleisli[any, IOResult[T]] {
return F.Flow2( return F.Flow2(
toIOEitherAny, toIOEitherAny,
E.Map[error](IOE.ChainEitherK(item)), result.Map(IOE.ChainEitherK(item)),
) )
} }
// toIOOptionType converts an any to an IOOption[any] and then to an IOOption[T] // toIOOptionType converts an any to an IOOption[any] and then to an IOOption[T]
func toIOOptionType[T any](item func(any) E.Either[error, T]) func(t any) E.Either[error, IOO.IOOption[T]] { func toIOOptionType[T any](item result.Kleisli[any, T]) result.Kleisli[any, IOOption[T]] {
return F.Flow2( return F.Flow2(
toIOOptionAny, toIOOptionAny,
E.Map[error](IOO.ChainOptionK(F.Flow2( result.Map(IOO.ChainOptionK(F.Flow2(
item, item,
E.ToOption[error, T], result.ToOption[T],
))), ))),
) )
} }
// toArrayType converts an any to a []T // toArrayType converts an any to a []T
func toArrayType[T any](item func(any) E.Either[error, T]) func(t any) E.Either[error, []T] { func toArrayType[T any](item result.Kleisli[any, T]) result.Kleisli[any, []T] {
return F.Flow2( return F.Flow2(
toArrayAny, toArrayAny,
E.Chain(E.TraverseArray(item)), E.Chain(E.TraverseArray(item)),

Some files were not shown because too many files have changed in this diff Show More