mirror of
https://github.com/IBM/fp-go.git
synced 2025-12-09 23:11:40 +02:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
311ed55f06 | ||
|
|
23333ce52c | ||
|
|
eb7fc9f77b | ||
|
|
fd0550e71b | ||
|
|
13063bbd88 |
210
v2/README.md
210
v2/README.md
@@ -2,25 +2,152 @@
|
||||
|
||||
[](https://pkg.go.dev/github.com/IBM/fp-go/v2)
|
||||
[](https://coveralls.io/github/IBM/fp-go?branch=main)
|
||||
[](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
|
||||
|
||||
- [Overview](#-overview)
|
||||
- [Features](#-features)
|
||||
- [Requirements](#-requirements)
|
||||
- [Breaking Changes](#-breaking-changes)
|
||||
- [Installation](#-installation)
|
||||
- [Quick Start](#-quick-start)
|
||||
- [Breaking Changes](#️-breaking-changes)
|
||||
- [Key Improvements](#-key-improvements)
|
||||
- [Migration Guide](#-migration-guide)
|
||||
- [Installation](#-installation)
|
||||
- [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
|
||||
|
||||
- **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.
|
||||
|
||||
@@ -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]]
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
### 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).
|
||||
|
||||
@@ -91,16 +218,16 @@ func processData(input string) ET.Either[error, OPT.Option[int]] {
|
||||
**V2 Approach:**
|
||||
```go
|
||||
import (
|
||||
"github.com/IBM/fp-go/v2/either"
|
||||
"github.com/IBM/fp-go/v2/result"
|
||||
"github.com/IBM/fp-go/v2/option"
|
||||
)
|
||||
|
||||
// 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]
|
||||
|
||||
// Use them throughout your codebase
|
||||
func processData(input string) Either[Option[int]] {
|
||||
func processData(input string) Result[Option[int]] {
|
||||
// implementation
|
||||
}
|
||||
```
|
||||
@@ -230,20 +357,14 @@ Create project-wide type aliases for common patterns:
|
||||
package myapp
|
||||
|
||||
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/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 IOEither[A any] = ioeither.IOEither[error, A]
|
||||
```
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
```bash
|
||||
go get github.com/IBM/fp-go/v2
|
||||
type IOResult[A any] = ioresult.IOResult[A]
|
||||
```
|
||||
|
||||
## 🆕 What's New
|
||||
@@ -277,25 +398,37 @@ func process() IOET.IOEither[error, string] {
|
||||
**V2 Simplified Example:**
|
||||
```go
|
||||
import (
|
||||
"github.com/IBM/fp-go/v2/either"
|
||||
"github.com/IBM/fp-go/v2/ioeither"
|
||||
"strconv"
|
||||
"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] {
|
||||
return ioeither.Map(
|
||||
func process() IOResult[string] {
|
||||
return ioresult.Map(
|
||||
strconv.Itoa,
|
||||
)(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)
|
||||
- [Code Samples](../samples/)
|
||||
- [Go 1.24 Release Notes](https://tip.golang.org/doc/go1.24)
|
||||
- **[API Documentation](https://pkg.go.dev/github.com/IBM/fp-go/v2)** - Complete API reference
|
||||
- **[Code Samples](./samples/)** - Practical examples and use cases
|
||||
- **[Go 1.24 Release Notes](https://tip.golang.org/doc/go1.24)** - Information about generic type aliases
|
||||
|
||||
### 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?
|
||||
|
||||
@@ -310,10 +443,25 @@ func process() IOEither[string] {
|
||||
- ⚠️ Migration effort outweighs benefits for your project
|
||||
- ⚠️ 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
|
||||
|
||||
Found a bug or have a suggestion? Please [open an issue](https://github.com/IBM/fp-go/issues) on GitHub.
|
||||
|
||||
## 📄 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**
|
||||
@@ -87,6 +87,6 @@ func Example_sort() {
|
||||
// [abc klm zyx]
|
||||
// [zyx klm abc]
|
||||
// [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}}]
|
||||
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
E "github.com/IBM/fp-go/v2/either"
|
||||
EQ "github.com/IBM/fp-go/v2/eq"
|
||||
"github.com/IBM/fp-go/v2/eq"
|
||||
"github.com/IBM/fp-go/v2/result"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -28,82 +28,82 @@ var (
|
||||
errTest = fmt.Errorf("test failure")
|
||||
|
||||
// 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] {
|
||||
return 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) Result[T] {
|
||||
ok := wrapped(t, expected, actual)
|
||||
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
|
||||
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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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] {
|
||||
return func(actual []T) E.Either[error, []T] {
|
||||
func Length[T any](t *testing.T, expected int) result.Kleisli[[]T, []T] {
|
||||
return func(actual []T) Result[[]T] {
|
||||
ok := assert.Len(t, actual, expected)
|
||||
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
|
||||
func NoError[T any](t *testing.T) func(actual E.Either[error, T]) E.Either[error, T] {
|
||||
return func(actual E.Either[error, T]) E.Either[error, T] {
|
||||
return E.MonadFold(actual, func(e error) E.Either[error, T] {
|
||||
func NoError[T any](t *testing.T) result.Operator[T, T] {
|
||||
return func(actual Result[T]) Result[T] {
|
||||
return result.MonadFold(actual, func(e error) Result[T] {
|
||||
assert.NoError(t, e)
|
||||
return E.Left[T](e)
|
||||
}, func(value T) E.Either[error, T] {
|
||||
return result.Left[T](e)
|
||||
}, func(value T) Result[T] {
|
||||
assert.NoError(t, nil)
|
||||
return E.Right[error](value)
|
||||
return result.Of(value)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 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] {
|
||||
return func(actual []T) E.Either[error, []T] {
|
||||
func ArrayContains[T any](t *testing.T, expected T) result.Kleisli[[]T, []T] {
|
||||
return func(actual []T) Result[[]T] {
|
||||
ok := assert.Contains(t, actual, expected)
|
||||
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
|
||||
func ContainsKey[T any, K comparable](t *testing.T, expected K) func(actual map[K]T) E.Either[error, map[K]T] {
|
||||
return 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) Result[map[K]T] {
|
||||
ok := assert.Contains(t, actual, expected)
|
||||
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
|
||||
func NotContainsKey[T any, K comparable](t *testing.T, expected K) func(actual map[K]T) E.Either[error, map[K]T] {
|
||||
return 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) Result[map[K]T] {
|
||||
ok := assert.NotContains(t, actual, expected)
|
||||
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
7
v2/assert/types.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package assert
|
||||
|
||||
import "github.com/IBM/fp-go/v2/result"
|
||||
|
||||
type (
|
||||
Result[T any] = result.Result[T]
|
||||
)
|
||||
@@ -19,13 +19,17 @@ 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 (
|
||||
@@ -747,3 +751,93 @@ func GetOrElse[A any](onLeft func(error) ReaderIO[A]) func(ReaderIOResult[A]) Re
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -883,5 +883,3 @@ func BenchmarkExecuteApPar_CanceledContext(b *testing.B) {
|
||||
benchResult = rioe(ctx)()
|
||||
}
|
||||
}
|
||||
|
||||
// Made with Bob
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -19,14 +19,17 @@ import (
|
||||
"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/io"
|
||||
"github.com/IBM/fp-go/v2/ioeither"
|
||||
"github.com/IBM/fp-go/v2/lazy"
|
||||
"github.com/IBM/fp-go/v2/option"
|
||||
"github.com/IBM/fp-go/v2/reader"
|
||||
"github.com/IBM/fp-go/v2/readereither"
|
||||
"github.com/IBM/fp-go/v2/readerio"
|
||||
RIOR "github.com/IBM/fp-go/v2/readerioresult"
|
||||
"github.com/IBM/fp-go/v2/readeroption"
|
||||
"github.com/IBM/fp-go/v2/result"
|
||||
)
|
||||
|
||||
@@ -119,4 +122,8 @@ type (
|
||||
// // Apply the transformation
|
||||
// result := toUpper(computation)
|
||||
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]
|
||||
)
|
||||
|
||||
@@ -92,3 +92,8 @@ func MonadFlap[B, A any](fab ReaderResult[func(A) B], a A) ReaderResult[B] {
|
||||
func Flap[B, A any](a A) Operator[func(A) B, B] {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -23,11 +23,13 @@ import (
|
||||
"github.com/IBM/fp-go/v2/option"
|
||||
"github.com/IBM/fp-go/v2/reader"
|
||||
"github.com/IBM/fp-go/v2/readereither"
|
||||
"github.com/IBM/fp-go/v2/result"
|
||||
)
|
||||
|
||||
type (
|
||||
Option[A any] = option.Option[A]
|
||||
Either[A any] = either.Either[error, A]
|
||||
Result[A any] = result.Result[A]
|
||||
// ReaderResult is a specialization of the Reader monad for the typical golang scenario
|
||||
ReaderResult[A any] = readereither.ReaderEither[context.Context, error, A]
|
||||
|
||||
|
||||
8340
v2/coverage.out
Normal file
8340
v2/coverage.out
Normal file
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,7 @@ import (
|
||||
DIE "github.com/IBM/fp-go/v2/di/erasure"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
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 (
|
||||
@@ -34,5 +34,5 @@ var (
|
||||
var RunMain = F.Flow3(
|
||||
DIE.MakeInjector,
|
||||
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))),
|
||||
)
|
||||
|
||||
40
v2/di/doc.go
40
v2/di/doc.go
@@ -64,8 +64,8 @@ Creating and using dependencies:
|
||||
dbProvider := di.MakeProvider1(
|
||||
DBToken,
|
||||
ConfigToken.Identity(),
|
||||
func(cfg Config) IOE.IOEither[error, Database] {
|
||||
return IOE.Of[error](NewDatabase(cfg))
|
||||
func(cfg Config) IOResult[Database] {
|
||||
return ioresult.Of(NewDatabase(cfg))
|
||||
},
|
||||
)
|
||||
|
||||
@@ -73,8 +73,8 @@ Creating and using dependencies:
|
||||
APIToken,
|
||||
ConfigToken.Identity(),
|
||||
DBToken.Identity(),
|
||||
func(cfg Config, db Database) IOE.IOEither[error, APIService] {
|
||||
return IOE.Of[error](NewAPIService(cfg, db))
|
||||
func(cfg Config, db Database) IOResult[APIService] {
|
||||
return ioresult.Of(NewAPIService(cfg, db))
|
||||
},
|
||||
)
|
||||
|
||||
@@ -116,7 +116,7 @@ MakeProvider0 - No dependencies:
|
||||
|
||||
provider := di.MakeProvider0(
|
||||
token,
|
||||
IOE.Of[error](value),
|
||||
ioresult.Of(value),
|
||||
)
|
||||
|
||||
MakeProvider1 - One dependency:
|
||||
@@ -124,8 +124,8 @@ MakeProvider1 - One dependency:
|
||||
provider := di.MakeProvider1(
|
||||
resultToken,
|
||||
dep1Token.Identity(),
|
||||
func(dep1 Dep1Type) IOE.IOEither[error, ResultType] {
|
||||
return IOE.Of[error](createResult(dep1))
|
||||
func(dep1 Dep1Type) IOResult[ResultType] {
|
||||
return ioresult.Of(createResult(dep1))
|
||||
},
|
||||
)
|
||||
|
||||
@@ -135,8 +135,8 @@ MakeProvider2 - Two dependencies:
|
||||
resultToken,
|
||||
dep1Token.Identity(),
|
||||
dep2Token.Identity(),
|
||||
func(dep1 Dep1Type, dep2 Dep2Type) IOE.IOEither[error, ResultType] {
|
||||
return IOE.Of[error](createResult(dep1, dep2))
|
||||
func(dep1 Dep1Type, dep2 Dep2Type) IOResult[ResultType] {
|
||||
return ioresult.Of(createResult(dep1, dep2))
|
||||
},
|
||||
)
|
||||
|
||||
@@ -153,7 +153,7 @@ provider is registered:
|
||||
|
||||
token := di.MakeTokenWithDefault0(
|
||||
"ServiceName",
|
||||
IOE.Of[error](defaultImplementation),
|
||||
ioresult.Of(defaultImplementation),
|
||||
)
|
||||
|
||||
// Or with dependencies
|
||||
@@ -161,8 +161,8 @@ provider is registered:
|
||||
"ServiceName",
|
||||
dep1Token.Identity(),
|
||||
dep2Token.Identity(),
|
||||
func(dep1 Dep1Type, dep2 Dep2Type) IOE.IOEither[error, ResultType] {
|
||||
return IOE.Of[error](createDefault(dep1, dep2))
|
||||
func(dep1 Dep1Type, dep2 Dep2Type) IOResult[ResultType] {
|
||||
return ioresult.Of(createDefault(dep1, dep2))
|
||||
},
|
||||
)
|
||||
|
||||
@@ -208,8 +208,8 @@ The framework provides a convenient pattern for running applications:
|
||||
mainProvider := di.MakeProvider1(
|
||||
di.InjMain,
|
||||
APIToken.Identity(),
|
||||
func(api APIService) IOE.IOEither[error, any] {
|
||||
return IOE.Of[error](api.Start())
|
||||
func(api APIService) IOResult[any] {
|
||||
return ioresult.Of(api.Start())
|
||||
},
|
||||
)
|
||||
|
||||
@@ -247,8 +247,8 @@ Example 1: Configuration-based Service
|
||||
clientProvider := di.MakeProvider1(
|
||||
ClientToken,
|
||||
ConfigToken.Identity(),
|
||||
func(cfg Config) IOE.IOEither[error, HTTPClient] {
|
||||
return IOE.Of[error](HTTPClient{config: cfg})
|
||||
func(cfg Config) IOResult[HTTPClient] {
|
||||
return ioresult.Of(HTTPClient{config: cfg})
|
||||
},
|
||||
)
|
||||
|
||||
@@ -263,8 +263,8 @@ Example 2: Optional Dependencies
|
||||
serviceProvider := di.MakeProvider1(
|
||||
ServiceToken,
|
||||
CacheToken.Option(), // Optional dependency
|
||||
func(cache O.Option[Cache]) IOE.IOEither[error, Service] {
|
||||
return IOE.Of[error](NewService(cache))
|
||||
func(cache Option[Cache]) IOResult[Service] {
|
||||
return ioresult.Of(NewService(cache))
|
||||
},
|
||||
)
|
||||
|
||||
@@ -279,8 +279,8 @@ Example 3: Lazy Dependencies
|
||||
reporterProvider := di.MakeProvider1(
|
||||
ReporterToken,
|
||||
DBToken.IOEither(), // Lazy dependency
|
||||
func(dbIO IOE.IOEither[error, Database]) IOE.IOEither[error, Reporter] {
|
||||
return IOE.Of[error](NewReporter(dbIO))
|
||||
func(dbIO IOResult[Database]) IOResult[Reporter] {
|
||||
return ioresult.Of(NewReporter(dbIO))
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
"github.com/IBM/fp-go/v2/errors"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
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"
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
R "github.com/IBM/fp-go/v2/record"
|
||||
@@ -42,8 +42,8 @@ var (
|
||||
missingProviderError = F.Flow4(
|
||||
Dependency.String,
|
||||
errors.OnSome[string]("no provider for dependency [%s]"),
|
||||
IOE.Left[any, error],
|
||||
F.Constant1[InjectableFactory, IOE.IOEither[error, any]],
|
||||
IOR.Left[any],
|
||||
F.Constant1[InjectableFactory, IOResult[any]],
|
||||
)
|
||||
|
||||
// missingProviderErrorOrDefault returns the default [ProviderFactory] or an error
|
||||
@@ -56,7 +56,7 @@ var (
|
||||
emptyMulti any = A.Empty[any]()
|
||||
|
||||
// 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
|
||||
// returns an error or an empty multi value provider
|
||||
@@ -93,21 +93,21 @@ var (
|
||||
|
||||
// isMultiDependency tests if a dependency is a container dependency
|
||||
func isMultiDependency(dep Dependency) bool {
|
||||
return dep.Flag()&Multi == Multi
|
||||
return dep.Flag()&MULTI == MULTI
|
||||
}
|
||||
|
||||
// isItemProvider tests if a provivder provides a single item
|
||||
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
|
||||
func itemProviderFactory(fcts []ProviderFactory) ProviderFactory {
|
||||
return func(inj InjectableFactory) IOE.IOEither[error, any] {
|
||||
return func(inj InjectableFactory) IOResult[any] {
|
||||
return F.Pipe2(
|
||||
fcts,
|
||||
IOE.TraverseArray(I.Flap[IOE.IOEither[error, any]](inj)),
|
||||
IOE.Map[error](F.ToAny[[]any]),
|
||||
IOR.TraverseArray(I.Flap[IOResult[any]](inj)),
|
||||
IOR.Map(F.ToAny[[]any]),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -118,7 +118,7 @@ func itemProviderFactory(fcts []ProviderFactory) ProviderFactory {
|
||||
// makes sure to transitively resolve the required dependencies.
|
||||
func MakeInjector(providers []Provider) InjectableFactory {
|
||||
|
||||
type Result = IOE.IOEither[error, any]
|
||||
type Result = IOResult[any]
|
||||
type LazyResult = L.Lazy[Result]
|
||||
|
||||
// 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(
|
||||
Dependency.Id,
|
||||
R.Lookup[ProviderFactory, string],
|
||||
I.Ap[O.Option[ProviderFactory]](factoryByID),
|
||||
I.Ap[Option[ProviderFactory]](factoryByID),
|
||||
), handleMissingProvider),
|
||||
T.Tupled2(O.MonadGetOrElse[ProviderFactory]),
|
||||
I.Ap[IOE.IOEither[error, any]](injFct),
|
||||
IOE.Memoize[error, any],
|
||||
I.Ap[IOResult[any]](injFct),
|
||||
IOR.Memoize[any],
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,25 +19,23 @@ import (
|
||||
"fmt"
|
||||
|
||||
A "github.com/IBM/fp-go/v2/array"
|
||||
E "github.com/IBM/fp-go/v2/either"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
I "github.com/IBM/fp-go/v2/identity"
|
||||
IO "github.com/IBM/fp-go/v2/io"
|
||||
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"
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
R "github.com/IBM/fp-go/v2/record"
|
||||
"github.com/IBM/fp-go/v2/result"
|
||||
)
|
||||
|
||||
type (
|
||||
// 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]
|
||||
ProviderFactory = func(InjectableFactory) IOE.IOEither[error, any]
|
||||
InjectableFactory = func(Dependency) IOResult[any]
|
||||
ProviderFactory = func(InjectableFactory) IOResult[any]
|
||||
|
||||
paramIndex = map[int]int
|
||||
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
|
||||
|
||||
Provider interface {
|
||||
@@ -83,50 +81,50 @@ var (
|
||||
mergeMaps = R.UnionLastMonoid[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{
|
||||
Identity: func(mp paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
|
||||
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
|
||||
IDENTITY: func(mp paramIndex) func([]IOResult[any]) IOResult[paramValue] {
|
||||
return func(res []IOResult[any]) IOResult[paramValue] {
|
||||
return F.Pipe1(
|
||||
mp,
|
||||
IOE.TraverseRecord[int](getAt(res)),
|
||||
)
|
||||
}
|
||||
},
|
||||
Option: func(mp paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
|
||||
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
|
||||
OPTION: func(mp paramIndex) func([]IOResult[any]) IOResult[paramValue] {
|
||||
return func(res []IOResult[any]) IOResult[paramValue] {
|
||||
return F.Pipe3(
|
||||
mp,
|
||||
IO.TraverseRecord[int](getAt(res)),
|
||||
IO.Map(R.Map[int](F.Flow2(
|
||||
E.ToOption[error, any],
|
||||
F.ToAny[O.Option[any]],
|
||||
result.ToOption[any],
|
||||
F.ToAny[Option[any]],
|
||||
))),
|
||||
IOE.FromIO[error, paramValue],
|
||||
)
|
||||
}
|
||||
},
|
||||
IOEither: func(mp paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
|
||||
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
|
||||
IOEITHER: func(mp paramIndex) func([]IOResult[any]) IOResult[paramValue] {
|
||||
return func(res []IOResult[any]) IOResult[paramValue] {
|
||||
return F.Pipe2(
|
||||
mp,
|
||||
R.Map[int](F.Flow2(
|
||||
getAt(res),
|
||||
F.ToAny[IOE.IOEither[error, any]],
|
||||
F.ToAny[IOResult[any]],
|
||||
)),
|
||||
IOE.Of[error, paramValue],
|
||||
)
|
||||
}
|
||||
},
|
||||
IOOption: func(mp paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
|
||||
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
|
||||
IOOPTION: func(mp paramIndex) func([]IOResult[any]) IOResult[paramValue] {
|
||||
return func(res []IOResult[any]) IOResult[paramValue] {
|
||||
return F.Pipe2(
|
||||
mp,
|
||||
R.Map[int](F.Flow3(
|
||||
getAt(res),
|
||||
IOE.ToIOOption[error, any],
|
||||
F.ToAny[IOO.IOOption[any]],
|
||||
F.ToAny[IOOption[any]],
|
||||
)),
|
||||
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(
|
||||
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)
|
||||
}),
|
||||
)
|
||||
doFct := F.Flow2(
|
||||
I.Flap[IOE.IOEither[error, paramValue], []IOE.IOEither[error, any]],
|
||||
IOE.TraverseArray[error, func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue], paramValue],
|
||||
I.Flap[IOResult[paramValue], []IOResult[any]],
|
||||
IOE.TraverseArray[error, func([]IOResult[any]) IOResult[paramValue], paramValue],
|
||||
)
|
||||
postFct := IOE.Map[error](F.Flow2(
|
||||
A.Fold(mergeMaps),
|
||||
collectParams,
|
||||
))
|
||||
|
||||
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, []any] {
|
||||
return func(res []IOResult[any]) IOResult[[]any] {
|
||||
return F.Pipe2(
|
||||
preFct,
|
||||
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
|
||||
func MakeProviderFactory(
|
||||
deps []Dependency,
|
||||
fct func(param ...any) IOE.IOEither[error, any]) ProviderFactory {
|
||||
fct func(param ...any) IOResult[any]) ProviderFactory {
|
||||
|
||||
return F.Flow3(
|
||||
mapDeps(deps),
|
||||
|
||||
@@ -17,20 +17,18 @@ package erasure
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
)
|
||||
|
||||
const (
|
||||
BehaviourMask = 0x0f
|
||||
Identity = 0 // required dependency
|
||||
Option = 1 // optional dependency
|
||||
IOEither = 2 // lazy and required
|
||||
IOOption = 3 // lazy and optional
|
||||
IDENTITY = 0 // required dependency
|
||||
OPTION = 1 // optional dependency
|
||||
IOEITHER = 2 // lazy and required
|
||||
IOOPTION = 3 // lazy and optional
|
||||
|
||||
TypeMask = 0xf0
|
||||
Multi = 1 << 4 // array of implementations
|
||||
Item = 2 << 4 // item of a multi token
|
||||
MULTI = 1 << 4 // array of implementations
|
||||
ITEM = 2 << 4 // item of a multi token
|
||||
)
|
||||
|
||||
// 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() int
|
||||
// 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
13
v2/di/erasure/types.go
Normal 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]
|
||||
)
|
||||
3181
v2/di/gen.go
3181
v2/di/gen.go
File diff suppressed because it is too large
Load Diff
@@ -19,14 +19,14 @@ import (
|
||||
DIE "github.com/IBM/fp-go/v2/di/erasure"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
"github.com/IBM/fp-go/v2/identity"
|
||||
IOE "github.com/IBM/fp-go/v2/ioeither"
|
||||
RIOE "github.com/IBM/fp-go/v2/readerioeither"
|
||||
IOR "github.com/IBM/fp-go/v2/ioresult"
|
||||
RIOR "github.com/IBM/fp-go/v2/readerioresult"
|
||||
)
|
||||
|
||||
// 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(
|
||||
identity.Ap[IOE.IOEither[error, any]](asDependency(token)),
|
||||
IOE.ChainEitherK(token.Unerase),
|
||||
identity.Ap[IOResult[any]](asDependency(token)),
|
||||
IOR.ChainResultK(token.Unerase),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -22,9 +22,10 @@ import (
|
||||
"github.com/IBM/fp-go/v2/errors"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
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(
|
||||
A.Lookup[any](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(
|
||||
IOE.FromEither[error, A],
|
||||
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] {
|
||||
return func(_ ...any) IOE.IOEither[error, any] {
|
||||
func eraseProviderFactory0[R any](f IOResult[R]) func(params ...any) IOResult[any] {
|
||||
return func(_ ...any) IOResult[any] {
|
||||
return F.Pipe1(
|
||||
f,
|
||||
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](
|
||||
fct IOE.IOEither[error, R],
|
||||
fct IOResult[R],
|
||||
) DIE.ProviderFactory {
|
||||
return DIE.MakeProviderFactory(
|
||||
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]
|
||||
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))
|
||||
}
|
||||
|
||||
func MakeProvider0[R any](
|
||||
token InjectionToken[R],
|
||||
fct IOE.IOEither[error, R],
|
||||
fct IOResult[R],
|
||||
) DIE.Provider {
|
||||
return DIE.MakeProvider(
|
||||
token,
|
||||
@@ -75,5 +76,5 @@ func MakeProvider0[R any](
|
||||
|
||||
// ConstProvider simple implementation for a provider with a constant value
|
||||
func ConstProvider[R any](token InjectionToken[R], value R) DIE.Provider {
|
||||
return MakeProvider0(token, IOE.Of[error](value))
|
||||
return MakeProvider0(token, ioresult.Of(value))
|
||||
}
|
||||
|
||||
@@ -25,7 +25,8 @@ import (
|
||||
E "github.com/IBM/fp-go/v2/either"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
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"
|
||||
)
|
||||
|
||||
@@ -39,19 +40,19 @@ func TestSimpleProvider(t *testing.T) {
|
||||
|
||||
var staticCount int
|
||||
|
||||
staticValue := func(value string) IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
staticValue := func(value string) IOResult[string] {
|
||||
return func() Result[string] {
|
||||
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
|
||||
|
||||
dynamicValue := func(value string) IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
dynamicValue := func(value string) IOResult[string] {
|
||||
return func() Result[string] {
|
||||
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
|
||||
|
||||
staticValue := func(value string) IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
staticValue := func(value string) IOResult[string] {
|
||||
return func() Result[string] {
|
||||
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
|
||||
|
||||
dynamicValue := func(value O.Option[string]) IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
dynamicValue := func(value Option[string]) IOResult[string] {
|
||||
return func() Result[string] {
|
||||
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
|
||||
|
||||
dynamicValue := func(value O.Option[string]) IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
dynamicValue := func(value Option[string]) IOResult[string] {
|
||||
return func() Result[string] {
|
||||
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
|
||||
|
||||
dynamicValue := func(value string) IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
dynamicValue := func(value string) IOResult[string] {
|
||||
return func() Result[string] {
|
||||
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
|
||||
|
||||
staticValue := func(value string) IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
staticValue := func(value string) IOResult[string] {
|
||||
return func() Result[string] {
|
||||
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
|
||||
|
||||
dynamicValue := func(value string) IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
dynamicValue := func(value string) IOResult[string] {
|
||||
return func() Result[string] {
|
||||
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
|
||||
|
||||
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(
|
||||
laz,
|
||||
IOE.Chain(func(lazValue string) IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
IOE.Chain(func(lazValue string) IOResult[string] {
|
||||
return func() Result[string] {
|
||||
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()
|
||||
|
||||
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) {
|
||||
@@ -269,7 +270,7 @@ func TestEmptyItemProvider(t *testing.T) {
|
||||
|
||||
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) {
|
||||
@@ -283,8 +284,8 @@ func TestDependencyOnMultiProvider(t *testing.T) {
|
||||
p1 := ConstProvider(INJ_KEY1, "Value3")
|
||||
p2 := ConstProvider(INJ_KEY2, "Value4")
|
||||
|
||||
fromMulti := func(val string, multi []string) IOE.IOEither[error, string] {
|
||||
return IOE.Of[error](fmt.Sprintf("Val: %s, Multi: %s", val, multi))
|
||||
fromMulti := func(val string, multi []string) IOResult[string] {
|
||||
return ioresult.Of(fmt.Sprintf("Val: %s, Multi: %s", val, multi))
|
||||
}
|
||||
p3 := MakeProvider2(INJ_KEY3, INJ_KEY1.Identity(), injMulti.Container().Identity(), fromMulti)
|
||||
|
||||
@@ -295,19 +296,19 @@ func TestDependencyOnMultiProvider(t *testing.T) {
|
||||
|
||||
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) {
|
||||
// token without a default
|
||||
injToken1 := MakeToken[string]("Token1")
|
||||
// token with a default
|
||||
injToken2 := MakeTokenWithDefault0("Token2", IOE.Of[error]("Carsten"))
|
||||
injToken2 := MakeTokenWithDefault0("Token2", ioresult.Of("Carsten"))
|
||||
// dependency
|
||||
injToken3 := MakeToken[string]("Token3")
|
||||
|
||||
p3 := MakeProvider1(injToken3, injToken2.Identity(), func(data string) IOE.IOEither[error, string] {
|
||||
return IOE.Of[error](fmt.Sprintf("Token: %s", data))
|
||||
p3 := MakeProvider1(injToken3, injToken2.Identity(), func(data string) IOResult[string] {
|
||||
return ioresult.Of(fmt.Sprintf("Token: %s", data))
|
||||
})
|
||||
|
||||
// populate the injector
|
||||
@@ -320,19 +321,19 @@ func TestTokenWithDefaultProvider(t *testing.T) {
|
||||
// inj1 should not be available
|
||||
assert.True(t, E.IsLeft(r1(inj)()))
|
||||
// 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) {
|
||||
// token with a default
|
||||
injToken2 := MakeTokenWithDefault0("Token2", IOE.Of[error]("Carsten"))
|
||||
injToken2 := MakeTokenWithDefault0("Token2", ioresult.Of("Carsten"))
|
||||
// dependency
|
||||
injToken3 := MakeToken[string]("Token3")
|
||||
|
||||
p2 := ConstProvider(injToken2, "Override")
|
||||
|
||||
p3 := MakeProvider1(injToken3, injToken2.Identity(), func(data string) IOE.IOEither[error, string] {
|
||||
return IOE.Of[error](fmt.Sprintf("Token: %s", data))
|
||||
p3 := MakeProvider1(injToken3, injToken2.Identity(), func(data string) IOResult[string] {
|
||||
return ioresult.Of(fmt.Sprintf("Token: %s", data))
|
||||
})
|
||||
|
||||
// populate the injector
|
||||
@@ -342,5 +343,5 @@ func TestTokenWithDefaultProviderAndOverride(t *testing.T) {
|
||||
r3 := Resolve(injToken3)
|
||||
|
||||
// r3 should work
|
||||
assert.Equal(t, E.Of[error]("Token: Override"), r3(inj)())
|
||||
assert.Equal(t, result.Of("Token: Override"), r3(inj)())
|
||||
}
|
||||
|
||||
@@ -21,10 +21,7 @@ import (
|
||||
"sync/atomic"
|
||||
|
||||
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"
|
||||
IOE "github.com/IBM/fp-go/v2/ioeither"
|
||||
IOO "github.com/IBM/fp-go/v2/iooption"
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
)
|
||||
|
||||
@@ -33,7 +30,7 @@ import (
|
||||
type Dependency[T any] interface {
|
||||
DIE.Dependency
|
||||
// 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
|
||||
@@ -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`.
|
||||
// If the dependency cannot be resolved, the resolution process fails
|
||||
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]]
|
||||
Option() Dependency[O.Option[T]]
|
||||
// IOEither identifies this dependency as mandatory but it will be resolved lazily as a [IOE.IOEither[error, T]]. This
|
||||
Option() Dependency[Option[T]]
|
||||
// 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.
|
||||
// If the dependency cannot be resolved, the resolution process fails
|
||||
IOEither() Dependency[IOE.IOEither[error, T]]
|
||||
// IOOption identifies this dependency as optional but it will be resolved lazily as a [IOO.IOOption[T]]. This
|
||||
IOEither() Dependency[IOResult[T]]
|
||||
// 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.
|
||||
// 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.
|
||||
@@ -79,12 +76,12 @@ type tokenBase struct {
|
||||
name string
|
||||
id string
|
||||
flag int
|
||||
providerFactory O.Option[DIE.ProviderFactory]
|
||||
providerFactory Option[DIE.ProviderFactory]
|
||||
}
|
||||
|
||||
type token[T any] struct {
|
||||
base *tokenBase
|
||||
toType func(val any) E.Either[error, T]
|
||||
toType func(val any) Result[T]
|
||||
}
|
||||
|
||||
func (t *token[T]) Id() string {
|
||||
@@ -99,26 +96,26 @@ func (t *token[T]) String() string {
|
||||
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)
|
||||
}
|
||||
|
||||
func (t *token[T]) ProviderFactory() O.Option[DIE.ProviderFactory] {
|
||||
func (t *token[T]) ProviderFactory() Option[DIE.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}
|
||||
}
|
||||
|
||||
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}
|
||||
}
|
||||
|
||||
type injectionToken[T any] struct {
|
||||
token[T]
|
||||
option Dependency[O.Option[T]]
|
||||
ioeither Dependency[IOE.IOEither[error, T]]
|
||||
iooption Dependency[IOO.IOOption[T]]
|
||||
option Dependency[Option[T]]
|
||||
ioeither Dependency[IOResult[T]]
|
||||
iooption Dependency[IOOption[T]]
|
||||
}
|
||||
|
||||
type multiInjectionToken[T any] struct {
|
||||
@@ -130,19 +127,19 @@ func (i *injectionToken[T]) Identity() Dependency[T] {
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *injectionToken[T]) Option() Dependency[O.Option[T]] {
|
||||
func (i *injectionToken[T]) Option() Dependency[Option[T]] {
|
||||
return i.option
|
||||
}
|
||||
|
||||
func (i *injectionToken[T]) IOEither() Dependency[IOE.IOEither[error, T]] {
|
||||
func (i *injectionToken[T]) IOEither() Dependency[IOResult[T]] {
|
||||
return i.ioeither
|
||||
}
|
||||
|
||||
func (i *injectionToken[T]) IOOption() Dependency[IOO.IOOption[T]] {
|
||||
func (i *injectionToken[T]) IOOption() Dependency[IOOption[T]] {
|
||||
return i.iooption
|
||||
}
|
||||
|
||||
func (i *injectionToken[T]) ProviderFactory() O.Option[DIE.ProviderFactory] {
|
||||
func (i *injectionToken[T]) ProviderFactory() Option[DIE.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
|
||||
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()
|
||||
toIdentity := toType[T]()
|
||||
return &injectionToken[T]{
|
||||
token[T]{makeTokenBase(name, id, DIE.Identity, providerFactory), toIdentity},
|
||||
makeToken(fmt.Sprintf("Option[%s]", name), id, DIE.Option, toOptionType(toIdentity), providerFactory),
|
||||
makeToken(fmt.Sprintf("IOEither[%s]", name), id, DIE.IOEither, toIOEitherType(toIdentity), providerFactory),
|
||||
makeToken(fmt.Sprintf("IOOption[%s]", name), id, DIE.IOOption, toIOOptionType(toIdentity), providerFactory),
|
||||
token[T]{makeTokenBase(name, id, DIE.IDENTITY, providerFactory), toIdentity},
|
||||
makeToken(fmt.Sprintf("Option[%s]", name), id, DIE.OPTION, toOptionType(toIdentity), providerFactory),
|
||||
makeToken(fmt.Sprintf("IOEither[%s]", name), id, DIE.IOEITHER, toIOEitherType(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]()
|
||||
// container
|
||||
container := &injectionToken[[]T]{
|
||||
token[[]T]{makeTokenBase(containerName, id, DIE.Multi|DIE.Identity, providerFactory), toContainer},
|
||||
makeToken(fmt.Sprintf("Option[%s]", containerName), id, DIE.Multi|DIE.Option, toOptionType(toContainer), providerFactory),
|
||||
makeToken(fmt.Sprintf("IOEither[%s]", containerName), id, DIE.Multi|DIE.IOEither, toIOEitherType(toContainer), providerFactory),
|
||||
makeToken(fmt.Sprintf("IOOption[%s]", containerName), id, DIE.Multi|DIE.IOOption, toIOOptionType(toContainer), providerFactory),
|
||||
token[[]T]{makeTokenBase(containerName, id, DIE.MULTI|DIE.IDENTITY, providerFactory), toContainer},
|
||||
makeToken(fmt.Sprintf("Option[%s]", containerName), id, DIE.MULTI|DIE.OPTION, toOptionType(toContainer), providerFactory),
|
||||
makeToken(fmt.Sprintf("IOEither[%s]", containerName), id, DIE.OPTION|DIE.IOEITHER, toIOEitherType(toContainer), providerFactory),
|
||||
makeToken(fmt.Sprintf("IOOption[%s]", containerName), id, DIE.OPTION|DIE.IOOPTION, toIOOptionType(toContainer), providerFactory),
|
||||
}
|
||||
// item
|
||||
item := &injectionToken[T]{
|
||||
token[T]{makeTokenBase(itemName, id, DIE.Item|DIE.Identity, providerFactory), toItem},
|
||||
makeToken(fmt.Sprintf("Option[%s]", itemName), id, DIE.Item|DIE.Option, toOptionType(toItem), providerFactory),
|
||||
makeToken(fmt.Sprintf("IOEither[%s]", itemName), id, DIE.Item|DIE.IOEither, toIOEitherType(toItem), providerFactory),
|
||||
makeToken(fmt.Sprintf("IOOption[%s]", itemName), id, DIE.Item|DIE.IOOption, toIOOptionType(toItem), providerFactory),
|
||||
token[T]{makeTokenBase(itemName, id, DIE.ITEM|DIE.IDENTITY, providerFactory), toItem},
|
||||
makeToken(fmt.Sprintf("Option[%s]", itemName), id, DIE.ITEM|DIE.OPTION, toOptionType(toItem), providerFactory),
|
||||
makeToken(fmt.Sprintf("IOEither[%s]", itemName), id, DIE.ITEM|DIE.IOEITHER, toIOEitherType(toItem), providerFactory),
|
||||
makeToken(fmt.Sprintf("IOOption[%s]", itemName), id, DIE.ITEM|DIE.IOOPTION, toIOOptionType(toItem), providerFactory),
|
||||
}
|
||||
// returns the token
|
||||
return &multiInjectionToken[T]{container, item}
|
||||
|
||||
@@ -23,7 +23,9 @@ import (
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
IOE "github.com/IBM/fp-go/v2/ioeither"
|
||||
IOO "github.com/IBM/fp-go/v2/iooption"
|
||||
"github.com/IBM/fp-go/v2/ioresult"
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
"github.com/IBM/fp-go/v2/result"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -75,9 +77,9 @@ func TestTokenUnerase(t *testing.T) {
|
||||
token := MakeToken[int]("IntToken")
|
||||
|
||||
// Test successful unerase
|
||||
result := token.Unerase(42)
|
||||
assert.True(t, E.IsRight(result))
|
||||
assert.Equal(t, E.Of[error](42), result)
|
||||
res := token.Unerase(42)
|
||||
assert.True(t, E.IsRight(res))
|
||||
assert.Equal(t, result.Of(42), res)
|
||||
|
||||
// Test failed unerase (wrong type)
|
||||
result2 := token.Unerase("not an int")
|
||||
@@ -104,7 +106,7 @@ func TestTokenProviderFactory(t *testing.T) {
|
||||
assert.True(t, O.IsNone(token1.ProviderFactory()))
|
||||
|
||||
// Token with default
|
||||
token2 := MakeTokenWithDefault0("Token2", IOE.Of[error](42))
|
||||
token2 := MakeTokenWithDefault0("Token2", ioresult.Of(42))
|
||||
assert.True(t, O.IsSome(token2.ProviderFactory()))
|
||||
}
|
||||
|
||||
@@ -148,13 +150,13 @@ func TestOptionTokenUnerase(t *testing.T) {
|
||||
optionToken := token.Option()
|
||||
|
||||
// Test successful unerase with Some
|
||||
result := optionToken.Unerase(O.Of[any](42))
|
||||
assert.True(t, E.IsRight(result))
|
||||
res := optionToken.Unerase(O.Of[any](42))
|
||||
assert.True(t, E.IsRight(res))
|
||||
|
||||
// Test successful unerase with None
|
||||
noneResult := optionToken.Unerase(O.None[any]())
|
||||
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)
|
||||
badResult := optionToken.Unerase(42) // Not an Option
|
||||
@@ -166,7 +168,7 @@ func TestIOEitherTokenUnerase(t *testing.T) {
|
||||
ioeitherToken := token.IOEither()
|
||||
|
||||
// Test successful unerase
|
||||
ioValue := IOE.Of[error](any(42))
|
||||
ioValue := ioresult.Of(any(42))
|
||||
result := ioeitherToken.Unerase(ioValue)
|
||||
assert.True(t, E.IsRight(result))
|
||||
|
||||
@@ -222,7 +224,7 @@ func TestMultiTokenContainerUnerase(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMakeTokenWithDefault(t *testing.T) {
|
||||
factory := MakeProviderFactory0(IOE.Of[error](42))
|
||||
factory := MakeProviderFactory0(ioresult.Of(42))
|
||||
token := MakeTokenWithDefault[int]("TokenWithDefault", factory)
|
||||
|
||||
assert.NotNil(t, token)
|
||||
|
||||
15
v2/di/types.go
Normal file
15
v2/di/types.go
Normal 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]
|
||||
)
|
||||
@@ -23,12 +23,13 @@ import (
|
||||
IOE "github.com/IBM/fp-go/v2/ioeither"
|
||||
IOO "github.com/IBM/fp-go/v2/iooption"
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
"github.com/IBM/fp-go/v2/result"
|
||||
)
|
||||
|
||||
var (
|
||||
toOptionAny = toType[O.Option[any]]()
|
||||
toIOEitherAny = toType[IOE.IOEither[error, any]]()
|
||||
toIOOptionAny = toType[IOO.IOOption[any]]()
|
||||
toOptionAny = toType[Option[any]]()
|
||||
toIOEitherAny = toType[IOResult[any]]()
|
||||
toIOOptionAny = toType[IOOption[any]]()
|
||||
toArrayAny = toType[[]any]()
|
||||
)
|
||||
|
||||
@@ -38,45 +39,45 @@ func asDependency[T DIE.Dependency](t T) DIE.Dependency {
|
||||
}
|
||||
|
||||
// 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."))
|
||||
}
|
||||
|
||||
// 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(
|
||||
toOptionAny,
|
||||
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(
|
||||
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]
|
||||
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(
|
||||
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]
|
||||
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(
|
||||
toIOOptionAny,
|
||||
E.Map[error](IOO.ChainOptionK(F.Flow2(
|
||||
result.Map(IOO.ChainOptionK(F.Flow2(
|
||||
item,
|
||||
E.ToOption[error, T],
|
||||
result.ToOption[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(
|
||||
toArrayAny,
|
||||
E.Chain(E.TraverseArray(item)),
|
||||
|
||||
@@ -21,8 +21,9 @@ import (
|
||||
A "github.com/IBM/fp-go/v2/array"
|
||||
E "github.com/IBM/fp-go/v2/either"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
IOE "github.com/IBM/fp-go/v2/ioeither"
|
||||
"github.com/IBM/fp-go/v2/ioresult"
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
"github.com/IBM/fp-go/v2/result"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -33,13 +34,13 @@ var (
|
||||
|
||||
func TestToType(t *testing.T) {
|
||||
// good cases
|
||||
assert.Equal(t, E.Of[error](10), toInt(any(10)))
|
||||
assert.Equal(t, E.Of[error]("Carsten"), toString(any("Carsten")))
|
||||
assert.Equal(t, E.Of[error](O.Of("Carsten")), toType[O.Option[string]]()(any(O.Of("Carsten"))))
|
||||
assert.Equal(t, E.Of[error](O.Of(any("Carsten"))), toType[O.Option[any]]()(any(O.Of(any("Carsten")))))
|
||||
assert.Equal(t, result.Of(10), toInt(any(10)))
|
||||
assert.Equal(t, result.Of("Carsten"), toString(any("Carsten")))
|
||||
assert.Equal(t, result.Of(O.Of("Carsten")), toType[Option[string]]()(any(O.Of("Carsten"))))
|
||||
assert.Equal(t, result.Of(O.Of(any("Carsten"))), toType[Option[any]]()(any(O.Of(any("Carsten")))))
|
||||
// failure
|
||||
assert.False(t, E.IsRight(toInt(any("Carsten"))))
|
||||
assert.False(t, E.IsRight(toType[O.Option[string]]()(O.Of(any("Carsten")))))
|
||||
assert.False(t, E.IsRight(toType[Option[string]]()(O.Of(any("Carsten")))))
|
||||
}
|
||||
|
||||
func TestToOptionType(t *testing.T) {
|
||||
@@ -47,17 +48,17 @@ func TestToOptionType(t *testing.T) {
|
||||
toOptInt := toOptionType(toInt)
|
||||
toOptString := toOptionType(toString)
|
||||
// good cases
|
||||
assert.Equal(t, E.Of[error](O.Of(10)), toOptInt(any(O.Of(any(10)))))
|
||||
assert.Equal(t, E.Of[error](O.Of("Carsten")), toOptString(any(O.Of(any("Carsten")))))
|
||||
assert.Equal(t, result.Of(O.Of(10)), toOptInt(any(O.Of(any(10)))))
|
||||
assert.Equal(t, result.Of(O.Of("Carsten")), toOptString(any(O.Of(any("Carsten")))))
|
||||
// bad cases
|
||||
assert.False(t, E.IsRight(toOptInt(any(10))))
|
||||
assert.False(t, E.IsRight(toOptInt(any(O.Of(10)))))
|
||||
}
|
||||
|
||||
func invokeIOEither[T any](e E.Either[error, IOE.IOEither[error, T]]) E.Either[error, T] {
|
||||
func invokeIOEither[T any](e Result[IOResult[T]]) Result[T] {
|
||||
return F.Pipe1(
|
||||
e,
|
||||
E.Chain(func(ioe IOE.IOEither[error, T]) E.Either[error, T] {
|
||||
E.Chain(func(ioe IOResult[T]) Result[T] {
|
||||
return ioe()
|
||||
}),
|
||||
)
|
||||
@@ -68,11 +69,11 @@ func TestToIOEitherType(t *testing.T) {
|
||||
toIOEitherInt := toIOEitherType(toInt)
|
||||
toIOEitherString := toIOEitherType(toString)
|
||||
// good cases
|
||||
assert.Equal(t, E.Of[error](10), invokeIOEither(toIOEitherInt(any(IOE.Of[error](any(10))))))
|
||||
assert.Equal(t, E.Of[error]("Carsten"), invokeIOEither(toIOEitherString(any(IOE.Of[error](any("Carsten"))))))
|
||||
assert.Equal(t, result.Of(10), invokeIOEither(toIOEitherInt(any(ioresult.Of(any(10))))))
|
||||
assert.Equal(t, result.Of("Carsten"), invokeIOEither(toIOEitherString(any(ioresult.Of(any("Carsten"))))))
|
||||
// bad cases
|
||||
assert.False(t, E.IsRight(invokeIOEither(toIOEitherString(any(IOE.Of[error](any(10)))))))
|
||||
assert.False(t, E.IsRight(invokeIOEither(toIOEitherString(any(IOE.Of[error]("Carsten"))))))
|
||||
assert.False(t, E.IsRight(invokeIOEither(toIOEitherString(any(ioresult.Of(any(10)))))))
|
||||
assert.False(t, E.IsRight(invokeIOEither(toIOEitherString(any(ioresult.Of("Carsten"))))))
|
||||
assert.False(t, E.IsRight(invokeIOEither(toIOEitherString(any("Carsten")))))
|
||||
}
|
||||
|
||||
@@ -80,5 +81,5 @@ func TestToArrayType(t *testing.T) {
|
||||
// shortcuts
|
||||
toArrayString := toArrayType(toString)
|
||||
// good cases
|
||||
assert.Equal(t, E.Of[error](A.From("a", "b")), toArrayString(any(A.From(any("a"), any("b")))))
|
||||
assert.Equal(t, result.Of(A.From("a", "b")), toArrayString(any(A.From(any("a"), any("b")))))
|
||||
}
|
||||
|
||||
@@ -648,5 +648,3 @@ func BenchmarkString_Left(b *testing.B) {
|
||||
benchString = left.String()
|
||||
}
|
||||
}
|
||||
|
||||
// Made with Bob
|
||||
|
||||
@@ -76,8 +76,8 @@ func TestAp(t *testing.T) {
|
||||
f := S.Size
|
||||
|
||||
assert.Equal(t, Right[string](3), F.Pipe1(Right[string](f), Ap[int](Right[string]("abc"))))
|
||||
assert.Equal(t, Left[int]("maError"), F.Pipe1(Right[string](f), Ap[int](Left[string, string]("maError"))))
|
||||
assert.Equal(t, Left[int]("mabError"), F.Pipe1(Left[func(string) int]("mabError"), Ap[int](Left[string, string]("maError"))))
|
||||
assert.Equal(t, Left[int]("maError"), F.Pipe1(Right[string](f), Ap[int](Left[string]("maError"))))
|
||||
assert.Equal(t, Left[int]("mabError"), F.Pipe1(Left[func(string) int]("mabError"), Ap[int](Left[string]("maError"))))
|
||||
}
|
||||
|
||||
func TestAlt(t *testing.T) {
|
||||
|
||||
@@ -41,7 +41,7 @@ import (
|
||||
// curriedAdd := endomorphism.Curry2(add)
|
||||
// addFive := curriedAdd(5) // Returns an endomorphism that adds 5
|
||||
// result := addFive(10) // Returns: 15
|
||||
func Curry2[FCT ~func(T0, T1) T1, T0, T1 any](f FCT) func(T0) Endomorphism[T1] {
|
||||
func Curry2[FCT ~func(T0, T1) T1, T0, T1 any](f FCT) Kleisli[T0, T1] {
|
||||
return function.Curry2(f)
|
||||
}
|
||||
|
||||
@@ -68,6 +68,6 @@ func Curry2[FCT ~func(T0, T1) T1, T0, T1 any](f FCT) func(T0) Endomorphism[T1] {
|
||||
// curriedCombine := endomorphism.Curry3(combine)
|
||||
// addTen := curriedCombine(5)(5) // Returns an endomorphism that adds 10
|
||||
// result := addTen(20) // Returns: 30
|
||||
func Curry3[FCT ~func(T0, T1, T2) T2, T0, T1, T2 any](f FCT) func(T0) func(T1) Endomorphism[T2] {
|
||||
func Curry3[FCT ~func(T0, T1, T2) T2, T0, T1, T2 any](f FCT) func(T0) Kleisli[T1, T2] {
|
||||
return function.Curry3(f)
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ type (
|
||||
// var g endomorphism.Endomorphism[int] = increment
|
||||
Endomorphism[A any] = func(A) A
|
||||
|
||||
Kleisli[A, B any] = func(A) Endomorphism[B]
|
||||
|
||||
// Operator represents a transformation from one endomorphism to another.
|
||||
//
|
||||
// An Operator takes an endomorphism on type A and produces an endomorphism on type B.
|
||||
@@ -52,5 +54,5 @@ type (
|
||||
// return strconv.Itoa(result)
|
||||
// }
|
||||
// }
|
||||
Operator[A, B any] = func(Endomorphism[A]) Endomorphism[B]
|
||||
Operator[A, B any] = Kleisli[Endomorphism[A], B]
|
||||
)
|
||||
|
||||
87
v2/internal/fromoption/option.go
Normal file
87
v2/internal/fromoption/option.go
Normal file
@@ -0,0 +1,87 @@
|
||||
// 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 fromoption
|
||||
|
||||
import (
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
)
|
||||
|
||||
func FromPredicate[A, HKTEA any](fromOption func(Option[A]) HKTEA, pred func(A) bool) func(A) HKTEA {
|
||||
return F.Flow2(O.FromPredicate(pred), fromOption)
|
||||
}
|
||||
|
||||
// func MonadFromOption[E, A, HKTEA any](
|
||||
// fromEither func(ET.Either[E, A]) HKTEA,
|
||||
// onNone func() E,
|
||||
// ma O.Option[A],
|
||||
// ) HKTEA {
|
||||
// return F.Pipe1(
|
||||
// O.MonadFold(
|
||||
// ma,
|
||||
// F.Nullary2(onNone, ET.Left[A, E]),
|
||||
// ET.Right[E, A],
|
||||
// ),
|
||||
// fromEither,
|
||||
// )
|
||||
// }
|
||||
|
||||
// func FromOptionK[A, E, B, HKTEB any](
|
||||
// fromEither func(ET.Either[E, B]) HKTEB,
|
||||
// onNone func() E) func(f func(A) O.Option[B]) func(A) HKTEB {
|
||||
// // helper
|
||||
// return F.Bind2nd(F.Flow2[func(A) O.Option[B], func(O.Option[B]) HKTEB, A, O.Option[B], HKTEB], FromOption(fromEither, onNone))
|
||||
// }
|
||||
|
||||
func MonadChainOptionK[A, B, HKTEA, HKTEB any](
|
||||
mchain func(HKTEA, func(A) HKTEB) HKTEB,
|
||||
fromOption func(Option[B]) HKTEB,
|
||||
ma HKTEA,
|
||||
f func(A) Option[B]) HKTEB {
|
||||
return mchain(ma, F.Flow2(f, fromOption))
|
||||
}
|
||||
|
||||
func ChainOptionK[A, B, HKTEA, HKTEB any](
|
||||
mchain func(func(A) HKTEB) func(HKTEA) HKTEB,
|
||||
fromOption func(Option[B]) HKTEB,
|
||||
f func(A) Option[B]) func(HKTEA) HKTEB {
|
||||
return mchain(F.Flow2(f, fromOption))
|
||||
}
|
||||
|
||||
// func ChainOptionK[A, E, B, HKTEA, HKTEB any](
|
||||
// mchain func(HKTEA, func(A) HKTEB) HKTEB,
|
||||
// fromEither func(ET.Either[E, B]) HKTEB,
|
||||
// onNone func() E,
|
||||
// ) func(f func(A) O.Option[B]) func(ma HKTEA) HKTEB {
|
||||
// return F.Flow2(FromOptionK[A](fromEither, onNone), F.Bind1st(F.Bind2nd[HKTEA, func(A) HKTEB, HKTEB], mchain))
|
||||
// }
|
||||
|
||||
// func MonadChainFirstEitherK[A, E, B, HKTEA, HKTEB any](
|
||||
// mchain func(HKTEA, func(A) HKTEA) HKTEA,
|
||||
// mmap func(HKTEB, func(B) A) HKTEA,
|
||||
// fromEither func(ET.Either[E, B]) HKTEB,
|
||||
// ma HKTEA,
|
||||
// f func(A) ET.Either[E, B]) HKTEA {
|
||||
// return C.MonadChainFirst(mchain, mmap, ma, F.Flow2(f, fromEither))
|
||||
// }
|
||||
|
||||
// func ChainFirstEitherK[A, E, B, HKTEA, HKTEB any](
|
||||
// mchain func(func(A) HKTEA) func(HKTEA) HKTEA,
|
||||
// mmap func(func(B) A) func(HKTEB) HKTEA,
|
||||
// fromEither func(ET.Either[E, B]) HKTEB,
|
||||
// f func(A) ET.Either[E, B]) func(HKTEA) HKTEA {
|
||||
// return C.ChainFirst(mchain, mmap, F.Flow2(f, fromEither))
|
||||
// }
|
||||
28
v2/internal/fromoption/types.go
Normal file
28
v2/internal/fromoption/types.go
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) 2024 - 2025 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package fromoption
|
||||
|
||||
import (
|
||||
"github.com/IBM/fp-go/v2/option"
|
||||
)
|
||||
|
||||
type (
|
||||
Option[T any] = option.Option[T]
|
||||
|
||||
FromOption[A, HKTA any] interface {
|
||||
FromEither(Option[A]) HKTA
|
||||
}
|
||||
)
|
||||
@@ -46,9 +46,26 @@ func MonadChainReaderK[GB ~func(R) B, R, A, B, HKTRA, HKTRB any](
|
||||
}
|
||||
|
||||
func ChainReaderK[GB ~func(R) B, R, A, B, HKTRA, HKTRB any](
|
||||
mchain func(HKTRA, func(A) HKTRB) HKTRB,
|
||||
mchain func(func(A) HKTRB) func(HKTRA) HKTRB,
|
||||
fromReader func(GB) HKTRB,
|
||||
f func(A) GB,
|
||||
) func(HKTRA) HKTRB {
|
||||
return F.Bind2nd(mchain, FromReaderK(fromReader, f))
|
||||
return mchain(FromReaderK(fromReader, f))
|
||||
}
|
||||
|
||||
func MonadChainFirstReaderK[GB ~func(R) B, R, A, B, HKTRA, HKTRB any](
|
||||
mchain func(HKTRA, func(A) HKTRB) HKTRA,
|
||||
fromReader func(GB) HKTRB,
|
||||
ma HKTRA,
|
||||
f func(A) GB,
|
||||
) HKTRA {
|
||||
return mchain(ma, FromReaderK(fromReader, f))
|
||||
}
|
||||
|
||||
func ChainFirstReaderK[GB ~func(R) B, R, A, B, HKTRA, HKTRB any](
|
||||
mchain func(func(A) HKTRB) func(HKTRA) HKTRA,
|
||||
fromReader func(GB) HKTRB,
|
||||
f func(A) GB,
|
||||
) func(HKTRA) HKTRA {
|
||||
return mchain(FromReaderK(fromReader, f))
|
||||
}
|
||||
|
||||
@@ -78,8 +78,27 @@ func Ap[A, B, HKTFAB, HKTFGAB, HKTFA, HKTFB any](
|
||||
return apply.Ap(fap, fmap, O.Ap[B, A], fa)
|
||||
}
|
||||
|
||||
func MatchE[A, HKTEA, HKTB any](mchain func(HKTEA, func(O.Option[A]) HKTB) HKTB, onNone func() HKTB, onSome func(A) HKTB) func(HKTEA) HKTB {
|
||||
return F.Bind2nd(mchain, O.Fold(onNone, onSome))
|
||||
func MonadMatchE[A, HKTEA, HKTB any](
|
||||
fa HKTEA,
|
||||
mchain func(HKTEA, func(O.Option[A]) HKTB) HKTB,
|
||||
onNone func() HKTB,
|
||||
onSome func(A) HKTB) HKTB {
|
||||
return mchain(fa, O.Fold(onNone, onSome))
|
||||
}
|
||||
|
||||
func MatchE[A, HKTEA, HKTB any](
|
||||
mchain func(func(O.Option[A]) HKTB) func(HKTEA) HKTB,
|
||||
onNone func() HKTB,
|
||||
onSome func(A) HKTB) func(HKTEA) HKTB {
|
||||
return mchain(O.Fold(onNone, onSome))
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func GetOrElse[A, HKTEA, HKTB any](
|
||||
mchain func(func(O.Option[A]) HKTB) func(HKTEA) HKTB,
|
||||
onNone func() HKTB,
|
||||
onSome func(A) HKTB) func(HKTEA) HKTB {
|
||||
return MatchE(mchain, onNone, onSome)
|
||||
}
|
||||
|
||||
func FromOptionK[A, B, HKTB any](
|
||||
@@ -123,3 +142,7 @@ func Alt[LAZY ~func() HKTFA, A, HKTFA any](
|
||||
|
||||
return fchain(O.Fold(second, F.Flow2(O.Of[A], fof)))
|
||||
}
|
||||
|
||||
func SomeF[A, HKTA, HKTEA any](fmap func(HKTA, func(A) O.Option[A]) HKTEA, fa HKTA) HKTEA {
|
||||
return fmap(fa, O.Some[A])
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ func After[GA ~func() O.Option[A], A any](timestamp time.Time) func(GA) GA {
|
||||
|
||||
// Fold convers an IOOption into an IO
|
||||
func Fold[GA ~func() O.Option[A], GB ~func() B, A, B any](onNone func() GB, onSome func(A) GB) func(GA) GB {
|
||||
return optiont.MatchE(IO.MonadChain[GA, GB, O.Option[A], B], onNone, onSome)
|
||||
return optiont.MatchE(IO.Chain[GA, GB, O.Option[A], B], onNone, onSome)
|
||||
}
|
||||
|
||||
// Defer creates an IO by creating a brand new IO via a generator function, each time
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/IBM/fp-go/v2/internal/fromio"
|
||||
"github.com/IBM/fp-go/v2/internal/optiont"
|
||||
"github.com/IBM/fp-go/v2/io"
|
||||
"github.com/IBM/fp-go/v2/lazy"
|
||||
"github.com/IBM/fp-go/v2/option"
|
||||
)
|
||||
|
||||
@@ -47,7 +48,7 @@ func FromOption[A any](o Option[A]) IOOption[A] {
|
||||
return io.Of(o)
|
||||
}
|
||||
|
||||
func ChainOptionK[A, B any](f func(A) Option[B]) func(IOOption[A]) IOOption[B] {
|
||||
func ChainOptionK[A, B any](f func(A) Option[B]) Operator[A, B] {
|
||||
return optiont.ChainOptionK(
|
||||
io.Chain[Option[A], Option[B]],
|
||||
FromOption[B],
|
||||
@@ -55,7 +56,7 @@ func ChainOptionK[A, B any](f func(A) Option[B]) func(IOOption[A]) IOOption[B] {
|
||||
)
|
||||
}
|
||||
|
||||
func MonadChainIOK[A, B any](ma IOOption[A], f func(A) IO[B]) IOOption[B] {
|
||||
func MonadChainIOK[A, B any](ma IOOption[A], f io.Kleisli[A, B]) IOOption[B] {
|
||||
return fromio.MonadChainIOK(
|
||||
MonadChain[A, B],
|
||||
FromIO[B],
|
||||
@@ -64,7 +65,7 @@ func MonadChainIOK[A, B any](ma IOOption[A], f func(A) IO[B]) IOOption[B] {
|
||||
)
|
||||
}
|
||||
|
||||
func ChainIOK[A, B any](f func(A) IO[B]) func(IOOption[A]) IOOption[B] {
|
||||
func ChainIOK[A, B any](f io.Kleisli[A, B]) Operator[A, B] {
|
||||
return fromio.ChainIOK(
|
||||
Chain[A, B],
|
||||
FromIO[B],
|
||||
@@ -80,15 +81,15 @@ func MonadMap[A, B any](fa IOOption[A], f func(A) B) IOOption[B] {
|
||||
return optiont.MonadMap(io.MonadMap[Option[A], Option[B]], fa, f)
|
||||
}
|
||||
|
||||
func Map[A, B any](f func(A) B) func(IOOption[A]) IOOption[B] {
|
||||
func Map[A, B any](f func(A) B) Operator[A, B] {
|
||||
return optiont.Map(io.Map[Option[A], Option[B]], f)
|
||||
}
|
||||
|
||||
func MonadChain[A, B any](fa IOOption[A], f func(A) IOOption[B]) IOOption[B] {
|
||||
func MonadChain[A, B any](fa IOOption[A], f Kleisli[A, B]) IOOption[B] {
|
||||
return optiont.MonadChain(io.MonadChain[Option[A], Option[B]], io.MonadOf[Option[B]], fa, f)
|
||||
}
|
||||
|
||||
func Chain[A, B any](f func(A) IOOption[B]) func(IOOption[A]) IOOption[B] {
|
||||
func Chain[A, B any](f Kleisli[A, B]) Operator[A, B] {
|
||||
return optiont.Chain(io.Chain[Option[A], Option[B]], io.Of[Option[B]], f)
|
||||
}
|
||||
|
||||
@@ -99,21 +100,21 @@ func MonadAp[B, A any](mab IOOption[func(A) B], ma IOOption[A]) IOOption[B] {
|
||||
mab, ma)
|
||||
}
|
||||
|
||||
func Ap[B, A any](ma IOOption[A]) func(IOOption[func(A) B]) IOOption[B] {
|
||||
func Ap[B, A any](ma IOOption[A]) Operator[func(A) B, B] {
|
||||
return optiont.Ap(
|
||||
io.Ap[Option[B], Option[A]],
|
||||
io.Map[Option[func(A) B], func(Option[A]) Option[B]],
|
||||
ma)
|
||||
}
|
||||
|
||||
func ApSeq[B, A any](ma IOOption[A]) func(IOOption[func(A) B]) IOOption[B] {
|
||||
func ApSeq[B, A any](ma IOOption[A]) Operator[func(A) B, B] {
|
||||
return optiont.Ap(
|
||||
io.ApSeq[Option[B], Option[A]],
|
||||
io.Map[Option[func(A) B], func(Option[A]) Option[B]],
|
||||
ma)
|
||||
}
|
||||
|
||||
func ApPar[B, A any](ma IOOption[A]) func(IOOption[func(A) B]) IOOption[B] {
|
||||
func ApPar[B, A any](ma IOOption[A]) Operator[func(A) B, B] {
|
||||
return optiont.Ap(
|
||||
io.ApPar[Option[B], Option[A]],
|
||||
io.Map[Option[func(A) B], func(Option[A]) Option[B]],
|
||||
@@ -124,14 +125,14 @@ func Flatten[A any](mma IOOption[IOOption[A]]) IOOption[A] {
|
||||
return MonadChain(mma, function.Identity[IOOption[A]])
|
||||
}
|
||||
|
||||
func Optionize0[A any](f func() (A, bool)) func() IOOption[A] {
|
||||
func Optionize0[A any](f func() (A, bool)) Lazy[IOOption[A]] {
|
||||
ef := option.Optionize0(f)
|
||||
return func() IOOption[A] {
|
||||
return ef
|
||||
}
|
||||
}
|
||||
|
||||
func Optionize1[T1, A any](f func(t1 T1) (A, bool)) func(T1) IOOption[A] {
|
||||
func Optionize1[T1, A any](f func(t1 T1) (A, bool)) Kleisli[T1, A] {
|
||||
ef := option.Optionize1(f)
|
||||
return func(t1 T1) IOOption[A] {
|
||||
return func() Option[A] {
|
||||
@@ -172,8 +173,8 @@ func Memoize[A any](ma IOOption[A]) IOOption[A] {
|
||||
}
|
||||
|
||||
// Fold convers an [IOOption] into an [IO]
|
||||
func Fold[A, B any](onNone func() IO[B], onSome func(A) IO[B]) func(IOOption[A]) IO[B] {
|
||||
return optiont.MatchE(io.MonadChain[Option[A], B], onNone, onSome)
|
||||
func Fold[A, B any](onNone IO[B], onSome io.Kleisli[A, B]) func(IOOption[A]) IO[B] {
|
||||
return optiont.MatchE(io.Chain[Option[A], B], function.Constant(onNone), onSome)
|
||||
}
|
||||
|
||||
// Defer creates an IO by creating a brand new IO via a generator function, each time
|
||||
@@ -191,28 +192,28 @@ func FromEither[E, A any](e Either[E, A]) IOOption[A] {
|
||||
}
|
||||
|
||||
// MonadAlt identifies an associative operation on a type constructor
|
||||
func MonadAlt[A any](first IOOption[A], second Lazy[IOOption[A]]) IOOption[A] {
|
||||
func MonadAlt[A any](first IOOption[A], second IOOption[A]) IOOption[A] {
|
||||
return optiont.MonadAlt(
|
||||
io.MonadOf[Option[A]],
|
||||
io.MonadChain[Option[A], Option[A]],
|
||||
|
||||
first,
|
||||
second,
|
||||
lazy.Of(second),
|
||||
)
|
||||
}
|
||||
|
||||
// Alt identifies an associative operation on a type constructor
|
||||
func Alt[A any](second Lazy[IOOption[A]]) func(IOOption[A]) IOOption[A] {
|
||||
func Alt[A any](second IOOption[A]) Operator[A, A] {
|
||||
return optiont.Alt(
|
||||
io.Of[Option[A]],
|
||||
io.Chain[Option[A], Option[A]],
|
||||
|
||||
second,
|
||||
lazy.Of(second),
|
||||
)
|
||||
}
|
||||
|
||||
// MonadChainFirst runs the monad returned by the function but returns the result of the original monad
|
||||
func MonadChainFirst[A, B any](ma IOOption[A], f func(A) IOOption[B]) IOOption[A] {
|
||||
func MonadChainFirst[A, B any](ma IOOption[A], f Kleisli[A, B]) IOOption[A] {
|
||||
return chain.MonadChainFirst(
|
||||
MonadChain[A, A],
|
||||
MonadMap[B, A],
|
||||
@@ -222,7 +223,7 @@ func MonadChainFirst[A, B any](ma IOOption[A], f func(A) IOOption[B]) IOOption[A
|
||||
}
|
||||
|
||||
// ChainFirst runs the monad returned by the function but returns the result of the original monad
|
||||
func ChainFirst[A, B any](f func(A) IOOption[B]) func(IOOption[A]) IOOption[A] {
|
||||
func ChainFirst[A, B any](f Kleisli[A, B]) Operator[A, A] {
|
||||
return chain.ChainFirst(
|
||||
Chain[A, A],
|
||||
Map[B, A],
|
||||
@@ -231,7 +232,7 @@ func ChainFirst[A, B any](f func(A) IOOption[B]) func(IOOption[A]) IOOption[A] {
|
||||
}
|
||||
|
||||
// MonadChainFirstIOK runs the monad returned by the function but returns the result of the original monad
|
||||
func MonadChainFirstIOK[A, B any](first IOOption[A], f func(A) IO[B]) IOOption[A] {
|
||||
func MonadChainFirstIOK[A, B any](first IOOption[A], f io.Kleisli[A, B]) IOOption[A] {
|
||||
return fromio.MonadChainFirstIOK(
|
||||
MonadChain[A, A],
|
||||
MonadMap[B, A],
|
||||
@@ -242,7 +243,7 @@ func MonadChainFirstIOK[A, B any](first IOOption[A], f func(A) IO[B]) IOOption[A
|
||||
}
|
||||
|
||||
// ChainFirstIOK runs the monad returned by the function but returns the result of the original monad
|
||||
func ChainFirstIOK[A, B any](f func(A) IO[B]) func(IOOption[A]) IOOption[A] {
|
||||
func ChainFirstIOK[A, B any](f io.Kleisli[A, B]) Operator[A, A] {
|
||||
return fromio.ChainFirstIOK(
|
||||
Chain[A, A],
|
||||
Map[B, A],
|
||||
@@ -252,11 +253,11 @@ func ChainFirstIOK[A, B any](f func(A) IO[B]) func(IOOption[A]) IOOption[A] {
|
||||
}
|
||||
|
||||
// Delay creates an operation that passes in the value after some delay
|
||||
func Delay[A any](delay time.Duration) func(IOOption[A]) IOOption[A] {
|
||||
func Delay[A any](delay time.Duration) Operator[A, A] {
|
||||
return io.Delay[Option[A]](delay)
|
||||
}
|
||||
|
||||
// After creates an operation that passes after the given [time.Time]
|
||||
func After[A any](timestamp time.Time) func(IOOption[A]) IOOption[A] {
|
||||
func After[A any](timestamp time.Time) Operator[A, A] {
|
||||
return io.After[Option[A]](timestamp)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import "github.com/IBM/fp-go/v2/function"
|
||||
|
||||
// WithResource constructs a function that creates a resource, then operates on it and then releases the resource
|
||||
func WithResource[
|
||||
R, A, ANY any](onCreate IOOption[R], onRelease func(R) IOOption[ANY]) Kleisli[Kleisli[R, A], A] {
|
||||
R, A, ANY any](onCreate IOOption[R], onRelease Kleisli[R, ANY]) Kleisli[Kleisli[R, A], A] {
|
||||
// simply map to implementation of bracket
|
||||
return function.Bind13of3(Bracket[R, A, ANY])(onCreate, function.Ignore2of2[Option[A]](onRelease))
|
||||
}
|
||||
|
||||
@@ -21,10 +21,74 @@ import (
|
||||
S "github.com/IBM/fp-go/v2/semigroup"
|
||||
)
|
||||
|
||||
// ApplySemigroup lifts a Semigroup[A] to a Semigroup[Lazy[A]].
|
||||
// This allows you to combine lazy computations using the semigroup operation
|
||||
// on their underlying values.
|
||||
//
|
||||
// The resulting semigroup's Concat operation will evaluate both lazy computations
|
||||
// and combine their results using the original semigroup's operation.
|
||||
//
|
||||
// Parameters:
|
||||
// - s: A semigroup for values of type A
|
||||
//
|
||||
// Returns:
|
||||
// - A semigroup for lazy computations of type A
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// import (
|
||||
// M "github.com/IBM/fp-go/v2/monoid"
|
||||
// "github.com/IBM/fp-go/v2/lazy"
|
||||
// )
|
||||
//
|
||||
// // Create a semigroup for lazy integers using addition
|
||||
// intAddSemigroup := lazy.ApplySemigroup(M.MonoidSum[int]())
|
||||
//
|
||||
// lazy1 := lazy.Of(5)
|
||||
// lazy2 := lazy.Of(10)
|
||||
//
|
||||
// // Combine the lazy computations
|
||||
// result := intAddSemigroup.Concat(lazy1, lazy2)() // 15
|
||||
func ApplySemigroup[A any](s S.Semigroup[A]) S.Semigroup[Lazy[A]] {
|
||||
return IO.ApplySemigroup(s)
|
||||
}
|
||||
|
||||
// ApplicativeMonoid lifts a Monoid[A] to a Monoid[Lazy[A]].
|
||||
// This allows you to combine lazy computations using the monoid operation
|
||||
// on their underlying values, with an identity element.
|
||||
//
|
||||
// The resulting monoid's Concat operation will evaluate both lazy computations
|
||||
// and combine their results using the original monoid's operation. The Empty
|
||||
// operation returns a lazy computation that produces the monoid's identity element.
|
||||
//
|
||||
// Parameters:
|
||||
// - m: A monoid for values of type A
|
||||
//
|
||||
// Returns:
|
||||
// - A monoid for lazy computations of type A
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// import (
|
||||
// M "github.com/IBM/fp-go/v2/monoid"
|
||||
// "github.com/IBM/fp-go/v2/lazy"
|
||||
// )
|
||||
//
|
||||
// // Create a monoid for lazy integers using addition
|
||||
// intAddMonoid := lazy.ApplicativeMonoid(M.MonoidSum[int]())
|
||||
//
|
||||
// // Get the identity element (0 wrapped in lazy)
|
||||
// empty := intAddMonoid.Empty()() // 0
|
||||
//
|
||||
// lazy1 := lazy.Of(5)
|
||||
// lazy2 := lazy.Of(10)
|
||||
//
|
||||
// // Combine the lazy computations
|
||||
// result := intAddMonoid.Concat(lazy1, lazy2)() // 15
|
||||
//
|
||||
// // Identity laws hold:
|
||||
// // Concat(Empty(), x) == x
|
||||
// // Concat(x, Empty()) == x
|
||||
func ApplicativeMonoid[A any](m M.Monoid[A]) M.Monoid[Lazy[A]] {
|
||||
return IO.ApplicativeMonoid(m)
|
||||
}
|
||||
|
||||
267
v2/lazy/doc.go
Normal file
267
v2/lazy/doc.go
Normal file
@@ -0,0 +1,267 @@
|
||||
// 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 lazy provides a functional programming abstraction for synchronous computations
|
||||
// without side effects. It represents deferred computations that are evaluated only when
|
||||
// their result is needed.
|
||||
//
|
||||
// # Overview
|
||||
//
|
||||
// A Lazy[A] is simply a function that takes no arguments and returns a value of type A:
|
||||
//
|
||||
// type Lazy[A any] = func() A
|
||||
//
|
||||
// This allows you to defer the evaluation of a computation until it's actually needed,
|
||||
// which is useful for:
|
||||
// - Avoiding unnecessary computations
|
||||
// - Creating infinite data structures
|
||||
// - Implementing memoization
|
||||
// - Composing computations in a pure functional style
|
||||
//
|
||||
// # Core Concepts
|
||||
//
|
||||
// The lazy package implements several functional programming patterns:
|
||||
//
|
||||
// **Functor**: Transform values inside a Lazy context using Map
|
||||
//
|
||||
// **Applicative**: Combine multiple Lazy computations using Ap and ApS
|
||||
//
|
||||
// **Monad**: Chain dependent computations using Chain and Bind
|
||||
//
|
||||
// **Memoization**: Cache computation results using Memoize
|
||||
//
|
||||
// # Basic Usage
|
||||
//
|
||||
// Creating and evaluating lazy computations:
|
||||
//
|
||||
// import (
|
||||
// "fmt"
|
||||
// "github.com/IBM/fp-go/v2/lazy"
|
||||
// F "github.com/IBM/fp-go/v2/function"
|
||||
// )
|
||||
//
|
||||
// // Create a lazy computation
|
||||
// computation := lazy.Of(42)
|
||||
//
|
||||
// // Transform it
|
||||
// doubled := F.Pipe1(
|
||||
// computation,
|
||||
// lazy.Map(func(x int) int { return x * 2 }),
|
||||
// )
|
||||
//
|
||||
// // Evaluate when needed
|
||||
// result := doubled() // 84
|
||||
//
|
||||
// # Memoization
|
||||
//
|
||||
// Lazy computations can be memoized to ensure they're evaluated only once:
|
||||
//
|
||||
// import "math/rand"
|
||||
//
|
||||
// // Without memoization - generates different values each time
|
||||
// random := lazy.FromLazy(rand.Int)
|
||||
// value1 := random() // e.g., 12345
|
||||
// value2 := random() // e.g., 67890 (different)
|
||||
//
|
||||
// // With memoization - caches the first result
|
||||
// memoized := lazy.Memoize(rand.Int)
|
||||
// value1 := memoized() // e.g., 12345
|
||||
// value2 := memoized() // 12345 (same as value1)
|
||||
//
|
||||
// # Chaining Computations
|
||||
//
|
||||
// Use Chain to compose dependent computations:
|
||||
//
|
||||
// getUserId := lazy.Of(123)
|
||||
//
|
||||
// getUser := F.Pipe1(
|
||||
// getUserId,
|
||||
// lazy.Chain(func(id int) lazy.Lazy[User] {
|
||||
// return lazy.Of(fetchUser(id))
|
||||
// }),
|
||||
// )
|
||||
//
|
||||
// user := getUser()
|
||||
//
|
||||
// # Do-Notation Style
|
||||
//
|
||||
// The package supports do-notation style composition using Bind and ApS:
|
||||
//
|
||||
// type Config struct {
|
||||
// Host string
|
||||
// Port int
|
||||
// }
|
||||
//
|
||||
// result := F.Pipe2(
|
||||
// lazy.Do(Config{}),
|
||||
// lazy.Bind(
|
||||
// func(host string) func(Config) Config {
|
||||
// return func(c Config) Config { c.Host = host; return c }
|
||||
// },
|
||||
// func(c Config) lazy.Lazy[string] {
|
||||
// return lazy.Of("localhost")
|
||||
// },
|
||||
// ),
|
||||
// lazy.Bind(
|
||||
// func(port int) func(Config) Config {
|
||||
// return func(c Config) Config { c.Port = port; return c }
|
||||
// },
|
||||
// func(c Config) lazy.Lazy[int] {
|
||||
// return lazy.Of(8080)
|
||||
// },
|
||||
// ),
|
||||
// )
|
||||
//
|
||||
// config := result() // Config{Host: "localhost", Port: 8080}
|
||||
//
|
||||
// # Traverse and Sequence
|
||||
//
|
||||
// Transform collections of values into lazy computations:
|
||||
//
|
||||
// // Transform array elements
|
||||
// numbers := []int{1, 2, 3}
|
||||
// doubled := F.Pipe1(
|
||||
// numbers,
|
||||
// lazy.TraverseArray(func(x int) lazy.Lazy[int] {
|
||||
// return lazy.Of(x * 2)
|
||||
// }),
|
||||
// )
|
||||
// result := doubled() // []int{2, 4, 6}
|
||||
//
|
||||
// // Sequence array of lazy computations
|
||||
// computations := []lazy.Lazy[int]{
|
||||
// lazy.Of(1),
|
||||
// lazy.Of(2),
|
||||
// lazy.Of(3),
|
||||
// }
|
||||
// result := lazy.SequenceArray(computations)() // []int{1, 2, 3}
|
||||
//
|
||||
// # Retry Logic
|
||||
//
|
||||
// The package includes retry functionality for computations that may fail:
|
||||
//
|
||||
// import (
|
||||
// R "github.com/IBM/fp-go/v2/retry"
|
||||
// "time"
|
||||
// )
|
||||
//
|
||||
// policy := R.CapDelay(
|
||||
// 2*time.Second,
|
||||
// R.Monoid.Concat(
|
||||
// R.ExponentialBackoff(10),
|
||||
// R.LimitRetries(5),
|
||||
// ),
|
||||
// )
|
||||
//
|
||||
// action := func(status R.RetryStatus) lazy.Lazy[string] {
|
||||
// return lazy.Of(fetchData())
|
||||
// }
|
||||
//
|
||||
// check := func(value string) bool {
|
||||
// return value == "" // retry if empty
|
||||
// }
|
||||
//
|
||||
// result := lazy.Retrying(policy, action, check)()
|
||||
//
|
||||
// # Algebraic Structures
|
||||
//
|
||||
// The package provides algebraic structures for combining lazy computations:
|
||||
//
|
||||
// **Semigroup**: Combine two lazy values using a semigroup operation
|
||||
//
|
||||
// import M "github.com/IBM/fp-go/v2/monoid"
|
||||
//
|
||||
// intAddSemigroup := lazy.ApplySemigroup(M.MonoidSum[int]())
|
||||
// result := intAddSemigroup.Concat(lazy.Of(5), lazy.Of(10))() // 15
|
||||
//
|
||||
// **Monoid**: Combine lazy values with an identity element
|
||||
//
|
||||
// intAddMonoid := lazy.ApplicativeMonoid(M.MonoidSum[int]())
|
||||
// empty := intAddMonoid.Empty()() // 0
|
||||
// result := intAddMonoid.Concat(lazy.Of(5), lazy.Of(10))() // 15
|
||||
//
|
||||
// # Comparison
|
||||
//
|
||||
// Compare lazy computations by evaluating and comparing their results:
|
||||
//
|
||||
// import EQ "github.com/IBM/fp-go/v2/eq"
|
||||
//
|
||||
// eq := lazy.Eq(EQ.FromEquals[int]())
|
||||
// result := eq.Equals(lazy.Of(42), lazy.Of(42)) // true
|
||||
//
|
||||
// # Key Functions
|
||||
//
|
||||
// **Creation**:
|
||||
// - Of: Create a lazy computation from a value
|
||||
// - FromLazy: Create a lazy computation from another lazy computation
|
||||
// - FromImpure: Convert a side effect into a lazy computation
|
||||
// - Defer: Create a lazy computation from a generator function
|
||||
//
|
||||
// **Transformation**:
|
||||
// - Map: Transform the value inside a lazy computation
|
||||
// - MapTo: Replace the value with a constant
|
||||
// - Chain: Chain dependent computations
|
||||
// - ChainFirst: Chain computations but keep the first result
|
||||
// - Flatten: Flatten nested lazy computations
|
||||
//
|
||||
// **Combination**:
|
||||
// - Ap: Apply a lazy function to a lazy value
|
||||
// - ApFirst: Combine two computations, keeping the first result
|
||||
// - ApSecond: Combine two computations, keeping the second result
|
||||
//
|
||||
// **Memoization**:
|
||||
// - Memoize: Cache the result of a computation
|
||||
//
|
||||
// **Do-Notation**:
|
||||
// - Do: Start a do-notation context
|
||||
// - Bind: Bind a computation result to a context
|
||||
// - Let: Attach a pure value to a context
|
||||
// - LetTo: Attach a constant to a context
|
||||
// - BindTo: Initialize a context from a value
|
||||
// - ApS: Attach a value using applicative style
|
||||
//
|
||||
// **Lens-Based Operations**:
|
||||
// - BindL: Bind using a lens
|
||||
// - LetL: Let using a lens
|
||||
// - LetToL: LetTo using a lens
|
||||
// - ApSL: ApS using a lens
|
||||
//
|
||||
// **Collections**:
|
||||
// - TraverseArray: Transform array elements into lazy computations
|
||||
// - SequenceArray: Convert array of lazy computations to lazy array
|
||||
// - TraverseRecord: Transform record values into lazy computations
|
||||
// - SequenceRecord: Convert record of lazy computations to lazy record
|
||||
//
|
||||
// **Tuples**:
|
||||
// - SequenceT1, SequenceT2, SequenceT3, SequenceT4: Combine lazy computations into tuples
|
||||
//
|
||||
// **Retry**:
|
||||
// - Retrying: Retry a computation according to a policy
|
||||
//
|
||||
// **Algebraic**:
|
||||
// - ApplySemigroup: Create a semigroup for lazy values
|
||||
// - ApplicativeMonoid: Create a monoid for lazy values
|
||||
// - Eq: Create an equality predicate for lazy values
|
||||
//
|
||||
// # Relationship to IO
|
||||
//
|
||||
// The lazy package is built on top of the io package and shares the same underlying
|
||||
// implementation. The key difference is conceptual:
|
||||
// - lazy.Lazy[A] represents a pure, synchronous computation without side effects
|
||||
// - io.IO[A] represents a computation that may have side effects
|
||||
//
|
||||
// In practice, they are the same type, but the lazy package provides a more focused
|
||||
// API for pure computations.
|
||||
package lazy
|
||||
101
v2/lazy/lazy.go
101
v2/lazy/lazy.go
@@ -21,10 +21,28 @@ import (
|
||||
"github.com/IBM/fp-go/v2/io"
|
||||
)
|
||||
|
||||
// Of creates a lazy computation that returns the given value.
|
||||
// This is the most basic way to lift a value into the Lazy context.
|
||||
//
|
||||
// The computation is pure and will always return the same value when evaluated.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// computation := lazy.Of(42)
|
||||
// result := computation() // 42
|
||||
func Of[A any](a A) Lazy[A] {
|
||||
return io.Of(a)
|
||||
}
|
||||
|
||||
// FromLazy creates a lazy computation from another lazy computation.
|
||||
// This is an identity function that can be useful for type conversions or
|
||||
// making the intent explicit in code.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// original := func() int { return 42 }
|
||||
// wrapped := lazy.FromLazy(original)
|
||||
// result := wrapped() // 42
|
||||
func FromLazy[A any](a Lazy[A]) Lazy[A] {
|
||||
return io.FromIO(a)
|
||||
}
|
||||
@@ -34,22 +52,73 @@ func FromImpure(f func()) Lazy[any] {
|
||||
return io.FromImpure(f)
|
||||
}
|
||||
|
||||
// MonadOf creates a lazy computation that returns the given value.
|
||||
// This is an alias for Of, provided for consistency with monadic naming conventions.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// computation := lazy.MonadOf(42)
|
||||
// result := computation() // 42
|
||||
func MonadOf[A any](a A) Lazy[A] {
|
||||
return io.MonadOf(a)
|
||||
}
|
||||
|
||||
// MonadMap transforms the value inside a lazy computation using the provided function.
|
||||
// The transformation is not applied until the lazy computation is evaluated.
|
||||
//
|
||||
// This is the monadic version of Map, taking the lazy computation as the first parameter.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// computation := lazy.Of(5)
|
||||
// doubled := lazy.MonadMap(computation, func(x int) int { return x * 2 })
|
||||
// result := doubled() // 10
|
||||
func MonadMap[A, B any](fa Lazy[A], f func(A) B) Lazy[B] {
|
||||
return io.MonadMap(fa, f)
|
||||
}
|
||||
|
||||
// Map transforms the value inside a lazy computation using the provided function.
|
||||
// Returns a function that can be applied to a lazy computation.
|
||||
//
|
||||
// This is the curried version of MonadMap, useful for function composition.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// double := lazy.Map(func(x int) int { return x * 2 })
|
||||
// computation := lazy.Of(5)
|
||||
// result := double(computation)() // 10
|
||||
//
|
||||
// // Or with pipe:
|
||||
// result := F.Pipe1(lazy.Of(5), double)() // 10
|
||||
func Map[A, B any](f func(A) B) func(fa Lazy[A]) Lazy[B] {
|
||||
return io.Map(f)
|
||||
}
|
||||
|
||||
// MonadMapTo replaces the value inside a lazy computation with a constant value.
|
||||
// The original computation is still evaluated, but its result is discarded.
|
||||
//
|
||||
// This is useful when you want to sequence computations but only care about
|
||||
// the side effects (though Lazy should represent pure computations).
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// computation := lazy.Of("ignored")
|
||||
// replaced := lazy.MonadMapTo(computation, 42)
|
||||
// result := replaced() // 42
|
||||
func MonadMapTo[A, B any](fa Lazy[A], b B) Lazy[B] {
|
||||
return io.MonadMapTo(fa, b)
|
||||
}
|
||||
|
||||
// MapTo replaces the value inside a lazy computation with a constant value.
|
||||
// Returns a function that can be applied to a lazy computation.
|
||||
//
|
||||
// This is the curried version of MonadMapTo.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// replaceWith42 := lazy.MapTo[string](42)
|
||||
// computation := lazy.Of("ignored")
|
||||
// result := replaceWith42(computation)() // 42
|
||||
func MapTo[A, B any](b B) Kleisli[Lazy[A], B] {
|
||||
return io.MapTo[A](b)
|
||||
}
|
||||
@@ -64,10 +133,32 @@ func Chain[A, B any](f Kleisli[A, B]) Kleisli[Lazy[A], B] {
|
||||
return io.Chain(f)
|
||||
}
|
||||
|
||||
// MonadAp applies a lazy function to a lazy value.
|
||||
// Both the function and the value are evaluated when the result is evaluated.
|
||||
//
|
||||
// This is the applicative functor operation, allowing you to apply functions
|
||||
// that are themselves wrapped in a lazy context.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// lazyFunc := lazy.Of(func(x int) int { return x * 2 })
|
||||
// lazyValue := lazy.Of(5)
|
||||
// result := lazy.MonadAp(lazyFunc, lazyValue)() // 10
|
||||
func MonadAp[B, A any](mab Lazy[func(A) B], ma Lazy[A]) Lazy[B] {
|
||||
return io.MonadApSeq(mab, ma)
|
||||
}
|
||||
|
||||
// Ap applies a lazy function to a lazy value.
|
||||
// Returns a function that takes a lazy function and returns a lazy result.
|
||||
//
|
||||
// This is the curried version of MonadAp, useful for function composition.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// lazyValue := lazy.Of(5)
|
||||
// applyTo5 := lazy.Ap[int](lazyValue)
|
||||
// lazyFunc := lazy.Of(func(x int) int { return x * 2 })
|
||||
// result := applyTo5(lazyFunc)() // 10
|
||||
func Ap[B, A any](ma Lazy[A]) func(Lazy[func(A) B]) Lazy[B] {
|
||||
return io.ApSeq[B](ma)
|
||||
}
|
||||
@@ -123,7 +214,15 @@ func ChainTo[A, B any](fb Lazy[B]) Kleisli[Lazy[A], B] {
|
||||
return io.ChainTo[A](fb)
|
||||
}
|
||||
|
||||
// Now returns the current timestamp
|
||||
// Now is a lazy computation that returns the current timestamp when evaluated.
|
||||
// Each evaluation will return the current time at the moment of evaluation.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// time1 := lazy.Now()
|
||||
// // ... some time passes ...
|
||||
// time2 := lazy.Now()
|
||||
// // time1 and time2 will be different
|
||||
var Now Lazy[time.Time] = io.Now
|
||||
|
||||
// Defer creates an IO by creating a brand new IO via a generator function, each time
|
||||
|
||||
503
v2/lazy/lazy_extended_test.go
Normal file
503
v2/lazy/lazy_extended_test.go
Normal file
@@ -0,0 +1,503 @@
|
||||
// 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 lazy
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
EQ "github.com/IBM/fp-go/v2/eq"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
"github.com/IBM/fp-go/v2/internal/utils"
|
||||
M "github.com/IBM/fp-go/v2/monoid"
|
||||
L "github.com/IBM/fp-go/v2/optics/lens"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestOf(t *testing.T) {
|
||||
result := Of(42)
|
||||
assert.Equal(t, 42, result())
|
||||
}
|
||||
|
||||
func TestFromLazy(t *testing.T) {
|
||||
original := func() int { return 42 }
|
||||
wrapped := FromLazy(original)
|
||||
assert.Equal(t, 42, wrapped())
|
||||
}
|
||||
|
||||
func TestFromImpure(t *testing.T) {
|
||||
counter := 0
|
||||
impure := func() {
|
||||
counter++
|
||||
}
|
||||
lazy := FromImpure(impure)
|
||||
lazy()
|
||||
assert.Equal(t, 1, counter)
|
||||
}
|
||||
|
||||
func TestMonadOf(t *testing.T) {
|
||||
result := MonadOf(42)
|
||||
assert.Equal(t, 42, result())
|
||||
}
|
||||
|
||||
func TestMonadMap(t *testing.T) {
|
||||
result := MonadMap(Of(5), func(x int) int { return x * 2 })
|
||||
assert.Equal(t, 10, result())
|
||||
}
|
||||
|
||||
func TestMonadMapTo(t *testing.T) {
|
||||
result := MonadMapTo(Of("ignored"), 42)
|
||||
assert.Equal(t, 42, result())
|
||||
}
|
||||
|
||||
func TestMapTo(t *testing.T) {
|
||||
mapper := MapTo[string](42)
|
||||
result := mapper(Of("ignored"))
|
||||
assert.Equal(t, 42, result())
|
||||
}
|
||||
|
||||
func TestMonadChain(t *testing.T) {
|
||||
result := MonadChain(Of(5), func(x int) Lazy[int] {
|
||||
return Of(x * 2)
|
||||
})
|
||||
assert.Equal(t, 10, result())
|
||||
}
|
||||
|
||||
func TestMonadChainFirst(t *testing.T) {
|
||||
result := MonadChainFirst(Of(5), func(x int) Lazy[string] {
|
||||
return Of("ignored")
|
||||
})
|
||||
assert.Equal(t, 5, result())
|
||||
}
|
||||
|
||||
func TestChainFirst(t *testing.T) {
|
||||
chainer := ChainFirst(func(x int) Lazy[string] {
|
||||
return Of("ignored")
|
||||
})
|
||||
result := chainer(Of(5))
|
||||
assert.Equal(t, 5, result())
|
||||
}
|
||||
|
||||
func TestMonadChainTo(t *testing.T) {
|
||||
result := MonadChainTo(Of(5), Of(10))
|
||||
assert.Equal(t, 10, result())
|
||||
}
|
||||
|
||||
func TestChainTo(t *testing.T) {
|
||||
chainer := ChainTo[int](Of(10))
|
||||
result := chainer(Of(5))
|
||||
assert.Equal(t, 10, result())
|
||||
}
|
||||
|
||||
func TestMonadAp(t *testing.T) {
|
||||
lazyFunc := Of(func(x int) int { return x * 2 })
|
||||
lazyValue := Of(5)
|
||||
result := MonadAp(lazyFunc, lazyValue)
|
||||
assert.Equal(t, 10, result())
|
||||
}
|
||||
|
||||
func TestMonadApFirst(t *testing.T) {
|
||||
result := MonadApFirst(Of(5), Of(10))
|
||||
assert.Equal(t, 5, result())
|
||||
}
|
||||
|
||||
func TestMonadApSecond(t *testing.T) {
|
||||
result := MonadApSecond(Of(5), Of(10))
|
||||
assert.Equal(t, 10, result())
|
||||
}
|
||||
|
||||
func TestNow(t *testing.T) {
|
||||
before := time.Now()
|
||||
result := Now()
|
||||
after := time.Now()
|
||||
|
||||
assert.True(t, result.After(before) || result.Equal(before))
|
||||
assert.True(t, result.Before(after) || result.Equal(after))
|
||||
}
|
||||
|
||||
func TestDefer(t *testing.T) {
|
||||
counter := 0
|
||||
deferred := Defer(func() Lazy[int] {
|
||||
counter++
|
||||
return Of(counter)
|
||||
})
|
||||
|
||||
// First execution
|
||||
result1 := deferred()
|
||||
assert.Equal(t, 1, result1)
|
||||
|
||||
// Second execution should generate a new computation
|
||||
result2 := deferred()
|
||||
assert.Equal(t, 2, result2)
|
||||
}
|
||||
|
||||
func TestDo(t *testing.T) {
|
||||
type State struct {
|
||||
Value int
|
||||
}
|
||||
result := Do(State{Value: 42})
|
||||
assert.Equal(t, State{Value: 42}, result())
|
||||
}
|
||||
|
||||
func TestLet(t *testing.T) {
|
||||
type State struct {
|
||||
Value int
|
||||
}
|
||||
|
||||
result := F.Pipe2(
|
||||
Do(State{}),
|
||||
Let(
|
||||
func(v int) func(State) State {
|
||||
return func(s State) State { s.Value = v; return s }
|
||||
},
|
||||
func(s State) int { return 42 },
|
||||
),
|
||||
Map(func(s State) int { return s.Value }),
|
||||
)
|
||||
|
||||
assert.Equal(t, 42, result())
|
||||
}
|
||||
|
||||
func TestLetTo(t *testing.T) {
|
||||
type State struct {
|
||||
Value int
|
||||
}
|
||||
|
||||
result := F.Pipe2(
|
||||
Do(State{}),
|
||||
LetTo(
|
||||
func(v int) func(State) State {
|
||||
return func(s State) State { s.Value = v; return s }
|
||||
},
|
||||
42,
|
||||
),
|
||||
Map(func(s State) int { return s.Value }),
|
||||
)
|
||||
|
||||
assert.Equal(t, 42, result())
|
||||
}
|
||||
|
||||
func TestBindTo(t *testing.T) {
|
||||
type State struct {
|
||||
Value int
|
||||
}
|
||||
|
||||
result := F.Pipe2(
|
||||
Of(42),
|
||||
BindTo(func(v int) State { return State{Value: v} }),
|
||||
Map(func(s State) int { return s.Value }),
|
||||
)
|
||||
|
||||
assert.Equal(t, 42, result())
|
||||
}
|
||||
|
||||
func TestBindL(t *testing.T) {
|
||||
type Config struct {
|
||||
Port int
|
||||
}
|
||||
type State struct {
|
||||
Config Config
|
||||
}
|
||||
|
||||
// Create a lens manually
|
||||
configLens := L.MakeLens(
|
||||
func(s State) Config { return s.Config },
|
||||
func(s State, cfg Config) State { s.Config = cfg; return s },
|
||||
)
|
||||
|
||||
result := F.Pipe2(
|
||||
Do(State{Config: Config{Port: 8080}}),
|
||||
BindL(configLens, func(cfg Config) Lazy[Config] {
|
||||
return Of(Config{Port: cfg.Port + 1})
|
||||
}),
|
||||
Map(func(s State) int { return s.Config.Port }),
|
||||
)
|
||||
|
||||
assert.Equal(t, 8081, result())
|
||||
}
|
||||
|
||||
func TestLetL(t *testing.T) {
|
||||
type Config struct {
|
||||
Port int
|
||||
}
|
||||
type State struct {
|
||||
Config Config
|
||||
}
|
||||
|
||||
// Create a lens manually
|
||||
configLens := L.MakeLens(
|
||||
func(s State) Config { return s.Config },
|
||||
func(s State, cfg Config) State { s.Config = cfg; return s },
|
||||
)
|
||||
|
||||
result := F.Pipe2(
|
||||
Do(State{Config: Config{Port: 8080}}),
|
||||
LetL(configLens, func(cfg Config) Config {
|
||||
return Config{Port: cfg.Port + 1}
|
||||
}),
|
||||
Map(func(s State) int { return s.Config.Port }),
|
||||
)
|
||||
|
||||
assert.Equal(t, 8081, result())
|
||||
}
|
||||
|
||||
func TestLetToL(t *testing.T) {
|
||||
type Config struct {
|
||||
Port int
|
||||
}
|
||||
type State struct {
|
||||
Config Config
|
||||
}
|
||||
|
||||
// Create a lens manually
|
||||
configLens := L.MakeLens(
|
||||
func(s State) Config { return s.Config },
|
||||
func(s State, cfg Config) State { s.Config = cfg; return s },
|
||||
)
|
||||
|
||||
result := F.Pipe2(
|
||||
Do(State{}),
|
||||
LetToL(configLens, Config{Port: 8080}),
|
||||
Map(func(s State) int { return s.Config.Port }),
|
||||
)
|
||||
|
||||
assert.Equal(t, 8080, result())
|
||||
}
|
||||
|
||||
func TestApSL(t *testing.T) {
|
||||
type Config struct {
|
||||
Port int
|
||||
}
|
||||
type State struct {
|
||||
Config Config
|
||||
}
|
||||
|
||||
// Create a lens manually
|
||||
configLens := L.MakeLens(
|
||||
func(s State) Config { return s.Config },
|
||||
func(s State, cfg Config) State { s.Config = cfg; return s },
|
||||
)
|
||||
|
||||
result := F.Pipe2(
|
||||
Do(State{}),
|
||||
ApSL(configLens, Of(Config{Port: 8080})),
|
||||
Map(func(s State) int { return s.Config.Port }),
|
||||
)
|
||||
|
||||
assert.Equal(t, 8080, result())
|
||||
}
|
||||
|
||||
func TestSequenceT1(t *testing.T) {
|
||||
result := SequenceT1(Of(42))
|
||||
tuple := result()
|
||||
assert.Equal(t, 42, tuple.F1)
|
||||
}
|
||||
|
||||
func TestSequenceT2(t *testing.T) {
|
||||
result := SequenceT2(Of(42), Of("hello"))
|
||||
tuple := result()
|
||||
assert.Equal(t, 42, tuple.F1)
|
||||
assert.Equal(t, "hello", tuple.F2)
|
||||
}
|
||||
|
||||
func TestSequenceT3(t *testing.T) {
|
||||
result := SequenceT3(Of(42), Of("hello"), Of(true))
|
||||
tuple := result()
|
||||
assert.Equal(t, 42, tuple.F1)
|
||||
assert.Equal(t, "hello", tuple.F2)
|
||||
assert.Equal(t, true, tuple.F3)
|
||||
}
|
||||
|
||||
func TestSequenceT4(t *testing.T) {
|
||||
result := SequenceT4(Of(42), Of("hello"), Of(true), Of(3.14))
|
||||
tuple := result()
|
||||
assert.Equal(t, 42, tuple.F1)
|
||||
assert.Equal(t, "hello", tuple.F2)
|
||||
assert.Equal(t, true, tuple.F3)
|
||||
assert.Equal(t, 3.14, tuple.F4)
|
||||
}
|
||||
|
||||
func TestTraverseArray(t *testing.T) {
|
||||
numbers := []int{1, 2, 3}
|
||||
result := F.Pipe1(
|
||||
numbers,
|
||||
TraverseArray(func(x int) Lazy[int] {
|
||||
return Of(x * 2)
|
||||
}),
|
||||
)
|
||||
assert.Equal(t, []int{2, 4, 6}, result())
|
||||
}
|
||||
|
||||
func TestTraverseArrayWithIndex(t *testing.T) {
|
||||
numbers := []int{10, 20, 30}
|
||||
result := F.Pipe1(
|
||||
numbers,
|
||||
TraverseArrayWithIndex(func(i int, x int) Lazy[int] {
|
||||
return Of(x + i)
|
||||
}),
|
||||
)
|
||||
assert.Equal(t, []int{10, 21, 32}, result())
|
||||
}
|
||||
|
||||
func TestSequenceArray(t *testing.T) {
|
||||
lazies := []Lazy[int]{Of(1), Of(2), Of(3)}
|
||||
result := SequenceArray(lazies)
|
||||
assert.Equal(t, []int{1, 2, 3}, result())
|
||||
}
|
||||
|
||||
func TestMonadTraverseArray(t *testing.T) {
|
||||
numbers := []int{1, 2, 3}
|
||||
result := MonadTraverseArray(numbers, func(x int) Lazy[int] {
|
||||
return Of(x * 2)
|
||||
})
|
||||
assert.Equal(t, []int{2, 4, 6}, result())
|
||||
}
|
||||
|
||||
func TestTraverseRecord(t *testing.T) {
|
||||
record := map[string]int{"a": 1, "b": 2}
|
||||
result := F.Pipe1(
|
||||
record,
|
||||
TraverseRecord[string](func(x int) Lazy[int] {
|
||||
return Of(x * 2)
|
||||
}),
|
||||
)
|
||||
resultMap := result()
|
||||
assert.Equal(t, 2, resultMap["a"])
|
||||
assert.Equal(t, 4, resultMap["b"])
|
||||
}
|
||||
|
||||
func TestTraverseRecordWithIndex(t *testing.T) {
|
||||
record := map[string]int{"a": 10, "b": 20}
|
||||
result := F.Pipe1(
|
||||
record,
|
||||
TraverseRecordWithIndex(func(k string, x int) Lazy[int] {
|
||||
if k == "a" {
|
||||
return Of(x + 1)
|
||||
}
|
||||
return Of(x + 2)
|
||||
}),
|
||||
)
|
||||
resultMap := result()
|
||||
assert.Equal(t, 11, resultMap["a"])
|
||||
assert.Equal(t, 22, resultMap["b"])
|
||||
}
|
||||
|
||||
func TestSequenceRecord(t *testing.T) {
|
||||
record := map[string]Lazy[int]{
|
||||
"a": Of(1),
|
||||
"b": Of(2),
|
||||
}
|
||||
result := SequenceRecord(record)
|
||||
resultMap := result()
|
||||
assert.Equal(t, 1, resultMap["a"])
|
||||
assert.Equal(t, 2, resultMap["b"])
|
||||
}
|
||||
|
||||
func TestMonadTraverseRecord(t *testing.T) {
|
||||
record := map[string]int{"a": 1, "b": 2}
|
||||
result := MonadTraverseRecord(record, func(x int) Lazy[int] {
|
||||
return Of(x * 2)
|
||||
})
|
||||
resultMap := result()
|
||||
assert.Equal(t, 2, resultMap["a"])
|
||||
assert.Equal(t, 4, resultMap["b"])
|
||||
}
|
||||
|
||||
func TestApplySemigroup(t *testing.T) {
|
||||
sg := ApplySemigroup(M.MakeMonoid(
|
||||
func(a, b int) int { return a + b },
|
||||
0,
|
||||
))
|
||||
|
||||
result := sg.Concat(Of(5), Of(10))
|
||||
assert.Equal(t, 15, result())
|
||||
}
|
||||
|
||||
func TestApplicativeMonoid(t *testing.T) {
|
||||
mon := ApplicativeMonoid(M.MakeMonoid(
|
||||
func(a, b int) int { return a + b },
|
||||
0,
|
||||
))
|
||||
|
||||
// Test Empty
|
||||
empty := mon.Empty()
|
||||
assert.Equal(t, 0, empty())
|
||||
|
||||
// Test Concat
|
||||
result := mon.Concat(Of(5), Of(10))
|
||||
assert.Equal(t, 15, result())
|
||||
|
||||
// Test identity laws
|
||||
left := mon.Concat(mon.Empty(), Of(5))
|
||||
assert.Equal(t, 5, left())
|
||||
|
||||
right := mon.Concat(Of(5), mon.Empty())
|
||||
assert.Equal(t, 5, right())
|
||||
}
|
||||
|
||||
func TestEq(t *testing.T) {
|
||||
eq := Eq(EQ.FromEquals(func(a, b int) bool { return a == b }))
|
||||
|
||||
assert.True(t, eq.Equals(Of(42), Of(42)))
|
||||
assert.False(t, eq.Equals(Of(42), Of(43)))
|
||||
}
|
||||
|
||||
func TestComplexDoNotation(t *testing.T) {
|
||||
// Test a more complex do-notation scenario
|
||||
result := F.Pipe3(
|
||||
Do(utils.Empty),
|
||||
Bind(utils.SetLastName, func(s utils.Initial) Lazy[string] {
|
||||
return Of("Doe")
|
||||
}),
|
||||
Bind(utils.SetGivenName, func(s utils.WithLastName) Lazy[string] {
|
||||
return Of("John")
|
||||
}),
|
||||
Map(utils.GetFullName),
|
||||
)
|
||||
|
||||
assert.Equal(t, "John Doe", result())
|
||||
}
|
||||
|
||||
func TestChainComposition(t *testing.T) {
|
||||
// Test chaining multiple operations
|
||||
double := func(x int) Lazy[int] {
|
||||
return Of(x * 2)
|
||||
}
|
||||
|
||||
addTen := func(x int) Lazy[int] {
|
||||
return Of(x + 10)
|
||||
}
|
||||
|
||||
result := F.Pipe2(
|
||||
Of(5),
|
||||
Chain(double),
|
||||
Chain(addTen),
|
||||
)
|
||||
|
||||
assert.Equal(t, 20, result())
|
||||
}
|
||||
|
||||
func TestMapComposition(t *testing.T) {
|
||||
// Test mapping multiple transformations
|
||||
result := F.Pipe3(
|
||||
Of(5),
|
||||
Map(func(x int) int { return x * 2 }),
|
||||
Map(func(x int) int { return x + 10 }),
|
||||
Map(func(x int) int { return x }),
|
||||
)
|
||||
|
||||
assert.Equal(t, 20, result())
|
||||
}
|
||||
@@ -22,18 +22,56 @@ import (
|
||||
|
||||
// SequenceT converts n inputs of higher kinded types into a higher kinded types of n strongly typed values, represented as a tuple
|
||||
|
||||
// SequenceT1 combines a single lazy computation into a lazy tuple.
|
||||
// This is mainly useful for consistency with the other SequenceT functions.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// lazy1 := lazy.Of(42)
|
||||
// result := lazy.SequenceT1(lazy1)()
|
||||
// // result is tuple.Tuple1[int]{F1: 42}
|
||||
func SequenceT1[A any](a Lazy[A]) Lazy[tuple.Tuple1[A]] {
|
||||
return io.SequenceT1(a)
|
||||
}
|
||||
|
||||
// SequenceT2 combines two lazy computations into a lazy tuple of two elements.
|
||||
// Both computations are evaluated when the result is evaluated.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// lazy1 := lazy.Of(42)
|
||||
// lazy2 := lazy.Of("hello")
|
||||
// result := lazy.SequenceT2(lazy1, lazy2)()
|
||||
// // result is tuple.Tuple2[int, string]{F1: 42, F2: "hello"}
|
||||
func SequenceT2[A, B any](a Lazy[A], b Lazy[B]) Lazy[tuple.Tuple2[A, B]] {
|
||||
return io.SequenceT2(a, b)
|
||||
}
|
||||
|
||||
// SequenceT3 combines three lazy computations into a lazy tuple of three elements.
|
||||
// All computations are evaluated when the result is evaluated.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// lazy1 := lazy.Of(42)
|
||||
// lazy2 := lazy.Of("hello")
|
||||
// lazy3 := lazy.Of(true)
|
||||
// result := lazy.SequenceT3(lazy1, lazy2, lazy3)()
|
||||
// // result is tuple.Tuple3[int, string, bool]{F1: 42, F2: "hello", F3: true}
|
||||
func SequenceT3[A, B, C any](a Lazy[A], b Lazy[B], c Lazy[C]) Lazy[tuple.Tuple3[A, B, C]] {
|
||||
return io.SequenceT3(a, b, c)
|
||||
}
|
||||
|
||||
// SequenceT4 combines four lazy computations into a lazy tuple of four elements.
|
||||
// All computations are evaluated when the result is evaluated.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// lazy1 := lazy.Of(42)
|
||||
// lazy2 := lazy.Of("hello")
|
||||
// lazy3 := lazy.Of(true)
|
||||
// lazy4 := lazy.Of(3.14)
|
||||
// result := lazy.SequenceT4(lazy1, lazy2, lazy3, lazy4)()
|
||||
// // result is tuple.Tuple4[int, string, bool, float64]{F1: 42, F2: "hello", F3: true, F4: 3.14}
|
||||
func SequenceT4[A, B, C, D any](a Lazy[A], b Lazy[B], c Lazy[C], d Lazy[D]) Lazy[tuple.Tuple4[A, B, C, D]] {
|
||||
return io.SequenceT4(a, b, c, d)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,18 @@ package lazy
|
||||
|
||||
import "github.com/IBM/fp-go/v2/io"
|
||||
|
||||
// MonadTraverseArray applies a function returning a lazy computation to all elements
|
||||
// in an array and transforms this into a lazy computation of that array.
|
||||
//
|
||||
// This is the monadic version of TraverseArray, taking the array as the first parameter.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// numbers := []int{1, 2, 3}
|
||||
// result := lazy.MonadTraverseArray(numbers, func(x int) lazy.Lazy[int] {
|
||||
// return lazy.Of(x * 2)
|
||||
// })()
|
||||
// // result is []int{2, 4, 6}
|
||||
func MonadTraverseArray[A, B any](tas []A, f Kleisli[A, B]) Lazy[[]B] {
|
||||
return io.MonadTraverseArray(tas, f)
|
||||
}
|
||||
@@ -38,6 +50,18 @@ func SequenceArray[A any](tas []Lazy[A]) Lazy[[]A] {
|
||||
return io.SequenceArray(tas)
|
||||
}
|
||||
|
||||
// MonadTraverseRecord applies a function returning a lazy computation to all values
|
||||
// in a record (map) and transforms this into a lazy computation of that record.
|
||||
//
|
||||
// This is the monadic version of TraverseRecord, taking the record as the first parameter.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// record := map[string]int{"a": 1, "b": 2}
|
||||
// result := lazy.MonadTraverseRecord(record, func(x int) lazy.Lazy[int] {
|
||||
// return lazy.Of(x * 2)
|
||||
// })()
|
||||
// // result is map[string]int{"a": 2, "b": 4}
|
||||
func MonadTraverseRecord[K comparable, A, B any](tas map[K]A, f Kleisli[A, B]) Lazy[map[K]B] {
|
||||
return io.MonadTraverseRecord(tas, f)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,60 @@
|
||||
package lazy
|
||||
|
||||
type (
|
||||
// Lazy represents a synchronous computation without side effects
|
||||
// Lazy represents a synchronous computation without side effects.
|
||||
// It is a function that takes no arguments and returns a value of type A.
|
||||
//
|
||||
// Lazy computations are evaluated only when their result is needed (lazy evaluation).
|
||||
// This allows for:
|
||||
// - Deferring expensive computations until they're actually required
|
||||
// - Creating infinite data structures
|
||||
// - Implementing memoization patterns
|
||||
// - Composing pure computations in a functional style
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// // Create a lazy computation
|
||||
// computation := lazy.Of(42)
|
||||
//
|
||||
// // Transform it (not evaluated yet)
|
||||
// doubled := lazy.Map(func(x int) int { return x * 2 })(computation)
|
||||
//
|
||||
// // Evaluate when needed
|
||||
// result := doubled() // 84
|
||||
//
|
||||
// Note: Lazy is an alias for io.IO[A] but represents pure computations
|
||||
// without side effects, whereas IO represents computations that may have side effects.
|
||||
Lazy[A any] = func() A
|
||||
|
||||
Kleisli[A, B any] = func(A) Lazy[B]
|
||||
// Kleisli represents a function that takes a value of type A and returns
|
||||
// a lazy computation producing a value of type B.
|
||||
//
|
||||
// Kleisli arrows are used for composing monadic computations. They allow
|
||||
// you to chain operations where each step depends on the result of the previous step.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// // A Kleisli arrow that doubles a number lazily
|
||||
// double := func(x int) lazy.Lazy[int] {
|
||||
// return lazy.Of(x * 2)
|
||||
// }
|
||||
//
|
||||
// // Chain it with another operation
|
||||
// result := lazy.Chain(double)(lazy.Of(5))() // 10
|
||||
Kleisli[A, B any] = func(A) Lazy[B]
|
||||
|
||||
// Operator represents a function that takes a lazy computation of type A
|
||||
// and returns a lazy computation of type B.
|
||||
//
|
||||
// Operators are used to transform lazy computations. They are essentially
|
||||
// Kleisli arrows where the input is already wrapped in a Lazy context.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// // An operator that doubles the value in a lazy computation
|
||||
// doubleOp := lazy.Map(func(x int) int { return x * 2 })
|
||||
//
|
||||
// // Apply it to a lazy computation
|
||||
// result := doubleOp(lazy.Of(5))() // 10
|
||||
Operator[A, B any] = Kleisli[Lazy[A], B]
|
||||
)
|
||||
|
||||
@@ -352,8 +352,11 @@ func FromEither[E, T any]() Prism[Either[E, T], T] {
|
||||
// - Working with optional fields that use zero as "not set"
|
||||
// - Replacing zero values with defaults
|
||||
func FromZero[T comparable]() Prism[T, T] {
|
||||
var zero T
|
||||
return MakePrism(option.FromPredicate(func(t T) bool { return t == zero }), F.Identity[T])
|
||||
return MakePrism(option.FromZero[T](), F.Identity[T])
|
||||
}
|
||||
|
||||
func FromNonZero[T comparable]() Prism[T, T] {
|
||||
return MakePrism(option.FromNonZero[T](), F.Identity[T])
|
||||
}
|
||||
|
||||
// Match represents a regex match result with full reconstruction capability.
|
||||
|
||||
@@ -18,6 +18,7 @@ package prism
|
||||
import (
|
||||
"github.com/IBM/fp-go/v2/either"
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
"github.com/IBM/fp-go/v2/reader"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -93,4 +94,6 @@ type (
|
||||
// - FromEither for creating prisms that work with Either types
|
||||
// - Prism composition for building complex error-handling pipelines
|
||||
Either[E, T any] = either.Either[E, T]
|
||||
|
||||
Reader[R, T any] = reader.Reader[R, T]
|
||||
)
|
||||
|
||||
@@ -39,8 +39,8 @@ var (
|
||||
// var opt Option[int] = Some(42) // Contains a value
|
||||
// var opt Option[int] = None[int]() // Contains no value
|
||||
type Option[A any] struct {
|
||||
isSome bool
|
||||
value A
|
||||
isSome bool
|
||||
}
|
||||
|
||||
type (
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
package option
|
||||
|
||||
import (
|
||||
"github.com/IBM/fp-go/v2/eq"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
C "github.com/IBM/fp-go/v2/internal/chain"
|
||||
FC "github.com/IBM/fp-go/v2/internal/functor"
|
||||
P "github.com/IBM/fp-go/v2/predicate"
|
||||
)
|
||||
|
||||
// fromPredicate creates an Option based on a predicate function.
|
||||
@@ -43,6 +45,21 @@ func FromPredicate[A any](pred func(A) bool) Kleisli[A, A] {
|
||||
return F.Bind2nd(fromPredicate[A], pred)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func FromZero[A comparable]() Kleisli[A, A] {
|
||||
return FromPredicate(P.IsZero[A]())
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func FromNonZero[A comparable]() Kleisli[A, A] {
|
||||
return FromPredicate(P.IsNonZero[A]())
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func FromEq[A any](pred eq.Eq[A]) func(A) Kleisli[A, A] {
|
||||
return F.Flow2(P.IsEqual(pred), FromPredicate[A])
|
||||
}
|
||||
|
||||
// FromNillable converts a pointer to an Option.
|
||||
// Returns Some if the pointer is non-nil, None otherwise.
|
||||
//
|
||||
|
||||
@@ -18,6 +18,7 @@ package predicate
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/IBM/fp-go/v2/eq"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -408,3 +409,272 @@ func TestComplexScenarios(t *testing.T) {
|
||||
assert.False(t, canBuy(Item{Price: 150, Stock: 0}))
|
||||
})
|
||||
}
|
||||
|
||||
// TestIsEqual tests the IsEqual function
|
||||
func TestIsEqual(t *testing.T) {
|
||||
t.Run("works with custom equality", func(t *testing.T) {
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
// Custom equality that only compares names
|
||||
nameEq := eq.FromEquals(func(a, b Person) bool {
|
||||
return a.Name == b.Name
|
||||
})
|
||||
|
||||
isEqualToPerson := IsEqual(nameEq)
|
||||
alice := Person{Name: "Alice", Age: 30}
|
||||
isAlice := isEqualToPerson(alice)
|
||||
|
||||
assert.True(t, isAlice(Person{Name: "Alice", Age: 30}))
|
||||
assert.True(t, isAlice(Person{Name: "Alice", Age: 25})) // Different age, same name
|
||||
assert.False(t, isAlice(Person{Name: "Bob", Age: 30}))
|
||||
})
|
||||
|
||||
t.Run("works with struct equality", func(t *testing.T) {
|
||||
type Point struct {
|
||||
X, Y int
|
||||
}
|
||||
|
||||
pointEq := eq.FromStrictEquals[Point]()
|
||||
isEqualToPoint := IsEqual(pointEq)
|
||||
origin := Point{X: 0, Y: 0}
|
||||
isOrigin := isEqualToPoint(origin)
|
||||
|
||||
assert.True(t, isOrigin(Point{X: 0, Y: 0}))
|
||||
assert.False(t, isOrigin(Point{X: 1, Y: 0}))
|
||||
assert.False(t, isOrigin(Point{X: 0, Y: 1}))
|
||||
})
|
||||
|
||||
t.Run("can be used with And/Or", func(t *testing.T) {
|
||||
intEq := eq.FromStrictEquals[int]()
|
||||
isEqualTo5 := IsEqual(intEq)(5)
|
||||
isEqualTo10 := IsEqual(intEq)(10)
|
||||
|
||||
is5Or10 := F.Pipe1(isEqualTo5, Or(isEqualTo10))
|
||||
assert.True(t, is5Or10(5))
|
||||
assert.True(t, is5Or10(10))
|
||||
assert.False(t, is5Or10(7))
|
||||
})
|
||||
}
|
||||
|
||||
// TestIsStrictEqual tests the IsStrictEqual function
|
||||
func TestIsStrictEqual(t *testing.T) {
|
||||
t.Run("works with integers", func(t *testing.T) {
|
||||
isEqualTo42 := IsStrictEqual[int]()(42)
|
||||
assert.True(t, isEqualTo42(42))
|
||||
assert.False(t, isEqualTo42(0))
|
||||
assert.False(t, isEqualTo42(-42))
|
||||
})
|
||||
|
||||
t.Run("works with strings", func(t *testing.T) {
|
||||
isEqualToHello := IsStrictEqual[string]()("hello")
|
||||
assert.True(t, isEqualToHello("hello"))
|
||||
assert.False(t, isEqualToHello("Hello"))
|
||||
assert.False(t, isEqualToHello("world"))
|
||||
assert.False(t, isEqualToHello(""))
|
||||
})
|
||||
|
||||
t.Run("works with booleans", func(t *testing.T) {
|
||||
isEqualToTrue := IsStrictEqual[bool]()(true)
|
||||
assert.True(t, isEqualToTrue(true))
|
||||
assert.False(t, isEqualToTrue(false))
|
||||
|
||||
isEqualToFalse := IsStrictEqual[bool]()(false)
|
||||
assert.True(t, isEqualToFalse(false))
|
||||
assert.False(t, isEqualToFalse(true))
|
||||
})
|
||||
|
||||
t.Run("works with floats", func(t *testing.T) {
|
||||
isEqualTo3Point14 := IsStrictEqual[float64]()(3.14)
|
||||
assert.True(t, isEqualTo3Point14(3.14))
|
||||
assert.False(t, isEqualTo3Point14(3.15))
|
||||
assert.False(t, isEqualTo3Point14(0.0))
|
||||
})
|
||||
|
||||
t.Run("can be combined with other predicates", func(t *testing.T) {
|
||||
isEqualTo5 := IsStrictEqual[int]()(5)
|
||||
isNotEqualTo5 := Not(isEqualTo5)
|
||||
|
||||
assert.False(t, isNotEqualTo5(5))
|
||||
assert.True(t, isNotEqualTo5(10))
|
||||
assert.True(t, isNotEqualTo5(0))
|
||||
})
|
||||
}
|
||||
|
||||
// TestIsZero tests the IsZero function
|
||||
func TestIsZero(t *testing.T) {
|
||||
t.Run("works with integers", func(t *testing.T) {
|
||||
isZeroInt := IsZero[int]()
|
||||
assert.True(t, isZeroInt(0))
|
||||
assert.False(t, isZeroInt(1))
|
||||
assert.False(t, isZeroInt(-1))
|
||||
assert.False(t, isZeroInt(100))
|
||||
})
|
||||
|
||||
t.Run("works with strings", func(t *testing.T) {
|
||||
isZeroString := IsZero[string]()
|
||||
assert.True(t, isZeroString(""))
|
||||
assert.False(t, isZeroString("hello"))
|
||||
assert.False(t, isZeroString(" "))
|
||||
assert.False(t, isZeroString("0"))
|
||||
})
|
||||
|
||||
t.Run("works with booleans", func(t *testing.T) {
|
||||
isZeroBool := IsZero[bool]()
|
||||
assert.True(t, isZeroBool(false))
|
||||
assert.False(t, isZeroBool(true))
|
||||
})
|
||||
|
||||
t.Run("works with floats", func(t *testing.T) {
|
||||
isZeroFloat := IsZero[float64]()
|
||||
assert.True(t, isZeroFloat(0.0))
|
||||
assert.False(t, isZeroFloat(0.1))
|
||||
assert.False(t, isZeroFloat(-0.1))
|
||||
})
|
||||
|
||||
t.Run("works with pointers", func(t *testing.T) {
|
||||
isZeroPtr := IsZero[*int]()
|
||||
assert.True(t, isZeroPtr(nil))
|
||||
|
||||
x := 42
|
||||
assert.False(t, isZeroPtr(&x))
|
||||
})
|
||||
|
||||
t.Run("works with structs", func(t *testing.T) {
|
||||
type Point struct {
|
||||
X, Y int
|
||||
}
|
||||
isZeroPoint := IsZero[Point]()
|
||||
assert.True(t, isZeroPoint(Point{X: 0, Y: 0}))
|
||||
assert.False(t, isZeroPoint(Point{X: 1, Y: 0}))
|
||||
assert.False(t, isZeroPoint(Point{X: 0, Y: 1}))
|
||||
})
|
||||
|
||||
t.Run("can be combined with other predicates", func(t *testing.T) {
|
||||
isZeroInt := IsZero[int]()
|
||||
isPositiveOrZero := F.Pipe1(isPositive, Or(isZeroInt))
|
||||
|
||||
assert.True(t, isPositiveOrZero(5))
|
||||
assert.True(t, isPositiveOrZero(0))
|
||||
assert.False(t, isPositiveOrZero(-5))
|
||||
})
|
||||
}
|
||||
|
||||
// TestIsNonZero tests the IsNonZero function
|
||||
func TestIsNonZero(t *testing.T) {
|
||||
t.Run("works with integers", func(t *testing.T) {
|
||||
isNonZeroInt := IsNonZero[int]()
|
||||
assert.False(t, isNonZeroInt(0))
|
||||
assert.True(t, isNonZeroInt(1))
|
||||
assert.True(t, isNonZeroInt(-1))
|
||||
assert.True(t, isNonZeroInt(100))
|
||||
})
|
||||
|
||||
t.Run("works with strings", func(t *testing.T) {
|
||||
isNonZeroString := IsNonZero[string]()
|
||||
assert.False(t, isNonZeroString(""))
|
||||
assert.True(t, isNonZeroString("hello"))
|
||||
assert.True(t, isNonZeroString(" "))
|
||||
assert.True(t, isNonZeroString("0"))
|
||||
})
|
||||
|
||||
t.Run("works with booleans", func(t *testing.T) {
|
||||
isNonZeroBool := IsNonZero[bool]()
|
||||
assert.False(t, isNonZeroBool(false))
|
||||
assert.True(t, isNonZeroBool(true))
|
||||
})
|
||||
|
||||
t.Run("works with floats", func(t *testing.T) {
|
||||
isNonZeroFloat := IsNonZero[float64]()
|
||||
assert.False(t, isNonZeroFloat(0.0))
|
||||
assert.True(t, isNonZeroFloat(0.1))
|
||||
assert.True(t, isNonZeroFloat(-0.1))
|
||||
})
|
||||
|
||||
t.Run("works with pointers", func(t *testing.T) {
|
||||
isNonZeroPtr := IsNonZero[*int]()
|
||||
assert.False(t, isNonZeroPtr(nil))
|
||||
|
||||
x := 42
|
||||
assert.True(t, isNonZeroPtr(&x))
|
||||
|
||||
y := 0
|
||||
assert.True(t, isNonZeroPtr(&y)) // Pointer itself is non-nil
|
||||
})
|
||||
|
||||
t.Run("is opposite of IsZero", func(t *testing.T) {
|
||||
isZeroInt := IsZero[int]()
|
||||
isNonZeroInt := IsNonZero[int]()
|
||||
|
||||
testValues := []int{-100, -1, 0, 1, 100}
|
||||
for _, v := range testValues {
|
||||
assert.Equal(t, !isZeroInt(v), isNonZeroInt(v), "IsNonZero should be opposite of IsZero for value %d", v)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("can be combined with other predicates", func(t *testing.T) {
|
||||
isNonZeroInt := IsNonZero[int]()
|
||||
isNonZeroAndPositive := F.Pipe1(isNonZeroInt, And(isPositive))
|
||||
|
||||
assert.True(t, isNonZeroAndPositive(5))
|
||||
assert.False(t, isNonZeroAndPositive(0))
|
||||
assert.False(t, isNonZeroAndPositive(-5))
|
||||
})
|
||||
}
|
||||
|
||||
// TestPredicatesIntegration tests integration of predicates.go functions with other predicate operations
|
||||
func TestPredicatesIntegration(t *testing.T) {
|
||||
t.Run("filter with IsZero", func(t *testing.T) {
|
||||
numbers := []int{0, 1, 0, 2, 0, 3}
|
||||
isZeroInt := IsZero[int]()
|
||||
|
||||
var nonZeros []int
|
||||
for _, n := range numbers {
|
||||
if !isZeroInt(n) {
|
||||
nonZeros = append(nonZeros, n)
|
||||
}
|
||||
}
|
||||
|
||||
assert.Equal(t, []int{1, 2, 3}, nonZeros)
|
||||
})
|
||||
|
||||
t.Run("validation with IsNonZero", func(t *testing.T) {
|
||||
type Config struct {
|
||||
Host string
|
||||
Port int
|
||||
}
|
||||
|
||||
isNonZeroString := IsNonZero[string]()
|
||||
isNonZeroInt := IsNonZero[int]()
|
||||
|
||||
getHost := func(c Config) string { return c.Host }
|
||||
getPort := func(c Config) int { return c.Port }
|
||||
|
||||
hasHost := F.Pipe1(isNonZeroString, ContraMap(getHost))
|
||||
hasPort := F.Pipe1(isNonZeroInt, ContraMap(getPort))
|
||||
isValid := F.Pipe1(hasHost, And(hasPort))
|
||||
|
||||
assert.True(t, isValid(Config{Host: "localhost", Port: 8080}))
|
||||
assert.False(t, isValid(Config{Host: "", Port: 8080}))
|
||||
assert.False(t, isValid(Config{Host: "localhost", Port: 0}))
|
||||
assert.False(t, isValid(Config{Host: "", Port: 0}))
|
||||
})
|
||||
|
||||
t.Run("equality with monoid", func(t *testing.T) {
|
||||
m := MonoidAny[int]()
|
||||
|
||||
isEqualTo1 := IsStrictEqual[int]()(1)
|
||||
isEqualTo2 := IsStrictEqual[int]()(2)
|
||||
isEqualTo3 := IsStrictEqual[int]()(3)
|
||||
|
||||
is1Or2Or3 := m.Concat(m.Concat(isEqualTo1, isEqualTo2), isEqualTo3)
|
||||
|
||||
assert.True(t, is1Or2Or3(1))
|
||||
assert.True(t, is1Or2Or3(2))
|
||||
assert.True(t, is1Or2Or3(3))
|
||||
assert.False(t, is1Or2Or3(4))
|
||||
assert.False(t, is1Or2Or3(0))
|
||||
})
|
||||
}
|
||||
|
||||
120
v2/predicate/predicates.go
Normal file
120
v2/predicate/predicates.go
Normal file
@@ -0,0 +1,120 @@
|
||||
// Copyright (c) 2025 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package predicate
|
||||
|
||||
import (
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
|
||||
"github.com/IBM/fp-go/v2/eq"
|
||||
)
|
||||
|
||||
// IsEqual creates a Kleisli arrow that tests if two values are equal using a custom equality function.
|
||||
//
|
||||
// This function takes an Eq instance (which defines how to compare values of type A) and returns
|
||||
// a curried function that can be used to create predicates for equality testing.
|
||||
//
|
||||
// Parameters:
|
||||
// - pred: An Eq[A] instance that defines equality for type A
|
||||
//
|
||||
// Returns:
|
||||
// - A Kleisli[A, A] that takes a value and returns a predicate testing equality with that value
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type Person struct { Name string; Age int }
|
||||
// personEq := eq.MakeEq(func(a, b Person) bool {
|
||||
// return a.Name == b.Name && a.Age == b.Age
|
||||
// })
|
||||
// isEqualToPerson := IsEqual(personEq)
|
||||
// alice := Person{Name: "Alice", Age: 30}
|
||||
// isAlice := isEqualToPerson(alice)
|
||||
// isAlice(Person{Name: "Alice", Age: 30}) // true
|
||||
// isAlice(Person{Name: "Bob", Age: 30}) // false
|
||||
func IsEqual[A any](pred eq.Eq[A]) Kleisli[A, A] {
|
||||
return F.Curry2(pred.Equals)
|
||||
}
|
||||
|
||||
// IsStrictEqual creates a Kleisli arrow that tests if two values are equal using Go's == operator.
|
||||
//
|
||||
// This is a convenience function for comparable types that uses strict equality (==) for comparison.
|
||||
// It's equivalent to IsEqual with an Eq instance based on ==.
|
||||
//
|
||||
// Returns:
|
||||
// - A Kleisli[A, A] that takes a value and returns a predicate testing strict equality
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// isEqualTo5 := IsStrictEqual[int]()(5)
|
||||
// isEqualTo5(5) // true
|
||||
// isEqualTo5(10) // false
|
||||
//
|
||||
// isEqualToHello := IsStrictEqual[string]()("hello")
|
||||
// isEqualToHello("hello") // true
|
||||
// isEqualToHello("world") // false
|
||||
func IsStrictEqual[A comparable]() Kleisli[A, A] {
|
||||
return IsEqual(eq.FromStrictEquals[A]())
|
||||
}
|
||||
|
||||
// IsZero creates a predicate that tests if a value equals the zero value for its type.
|
||||
//
|
||||
// The zero value is the default value for a type in Go (e.g., 0 for int, "" for string,
|
||||
// false for bool, nil for pointers, etc.).
|
||||
//
|
||||
// Returns:
|
||||
// - A Predicate[A] that returns true if the value is the zero value for type A
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// isZeroInt := IsZero[int]()
|
||||
// isZeroInt(0) // true
|
||||
// isZeroInt(5) // false
|
||||
//
|
||||
// isZeroString := IsZero[string]()
|
||||
// isZeroString("") // true
|
||||
// isZeroString("hello") // false
|
||||
//
|
||||
// isZeroBool := IsZero[bool]()
|
||||
// isZeroBool(false) // true
|
||||
// isZeroBool(true) // false
|
||||
func IsZero[A comparable]() Predicate[A] {
|
||||
var zero A
|
||||
return IsStrictEqual[A]()(zero)
|
||||
}
|
||||
|
||||
// IsNonZero creates a predicate that tests if a value is not equal to the zero value for its type.
|
||||
//
|
||||
// This is the negation of IsZero, returning true for any non-zero value.
|
||||
//
|
||||
// Returns:
|
||||
// - A Predicate[A] that returns true if the value is not the zero value for type A
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// isNonZeroInt := IsNonZero[int]()
|
||||
// isNonZeroInt(0) // false
|
||||
// isNonZeroInt(5) // true
|
||||
// isNonZeroInt(-3) // true
|
||||
//
|
||||
// isNonZeroString := IsNonZero[string]()
|
||||
// isNonZeroString("") // false
|
||||
// isNonZeroString("hello") // true
|
||||
//
|
||||
// isNonZeroPtr := IsNonZero[*int]()
|
||||
// isNonZeroPtr(nil) // false
|
||||
// isNonZeroPtr(new(int)) // true
|
||||
func IsNonZero[A comparable]() Predicate[A] {
|
||||
return Not(IsZero[A]())
|
||||
}
|
||||
@@ -45,7 +45,9 @@ type (
|
||||
// It is commonly used for filtering, validation, and conditional logic.
|
||||
Predicate[A any] = func(A) bool
|
||||
|
||||
Kleisli[A, B any] = func(A) Predicate[B]
|
||||
|
||||
// Operator represents a function that transforms a Predicate[A] into a Predicate[B].
|
||||
// This is useful for composing and transforming predicates.
|
||||
Operator[A, B any] = func(Predicate[A]) Predicate[B]
|
||||
Operator[A, B any] = Kleisli[Predicate[A], B]
|
||||
)
|
||||
|
||||
@@ -278,7 +278,7 @@ func Local[A, R2, R1 any](f func(R2) R1) func(Reader[R1, A]) Reader[R2, A] {
|
||||
// getPort := reader.Asks(func(c Config) int { return c.Port })
|
||||
// run := reader.Read(Config{Port: 8080})
|
||||
// port := run(getPort) // 8080
|
||||
func Read[E, A any](e E) func(Reader[E, A]) A {
|
||||
func Read[A, E any](e E) func(Reader[E, A]) A {
|
||||
return I.Ap[A](e)
|
||||
}
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@ func TestLocal(t *testing.T) {
|
||||
func TestRead(t *testing.T) {
|
||||
config := Config{Port: 8080}
|
||||
getPort := Asks(func(c Config) int { return c.Port })
|
||||
run := Read[Config, int](config)
|
||||
run := Read[int](config)
|
||||
port := run(getPort)
|
||||
assert.Equal(t, 8080, port)
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ func Local[E, A, R2, R1 any](f func(R2) R1) func(ReaderEither[R1, E, A]) ReaderE
|
||||
|
||||
// Read applies a context to a reader to obtain its value
|
||||
func Read[E1, A, E any](e E) func(ReaderEither[E, E1, A]) Either[E1, A] {
|
||||
return reader.Read[E, Either[E1, A]](e)
|
||||
return reader.Read[Either[E1, A]](e)
|
||||
}
|
||||
|
||||
func MonadFlap[L, E, A, B any](fab ReaderEither[L, E, func(A) B], a A) ReaderEither[L, E, B] {
|
||||
|
||||
@@ -22,8 +22,12 @@ import (
|
||||
)
|
||||
|
||||
type (
|
||||
Option[A any] = option.Option[A]
|
||||
Either[E, A any] = either.Either[E, A]
|
||||
Reader[R, A any] = reader.Reader[R, A]
|
||||
Option[A any] = option.Option[A]
|
||||
Either[E, A any] = either.Either[E, A]
|
||||
Reader[R, A any] = reader.Reader[R, A]
|
||||
|
||||
ReaderEither[R, E, A any] = Reader[R, Either[E, A]]
|
||||
|
||||
Kleisli[R, E, A, B any] = Reader[A, ReaderEither[R, E, B]]
|
||||
Operator[R, E, A, B any] = Kleisli[R, E, ReaderEither[R, E, A], B]
|
||||
)
|
||||
|
||||
@@ -161,7 +161,7 @@ func MonadChainReaderK[GEA ~func(R) GIOA, GEB ~func(R) GIOB, GIOA ~func() either
|
||||
// Deprecated:
|
||||
func ChainReaderK[GEA ~func(R) GIOA, GEB ~func(R) GIOB, GIOA ~func() either.Either[E, A], GIOB ~func() either.Either[E, B], GB ~func(R) B, R, E, A, B any](f func(A) GB) func(GEA) GEB {
|
||||
return FR.ChainReaderK(
|
||||
MonadChain[GEA, GEB, GIOA, GIOB, R, E, A, B],
|
||||
Chain[GEA, GEB, GIOA, GIOB, R, E, A, B],
|
||||
FromReader[GB, GEB, GIOB, R, E, B],
|
||||
f,
|
||||
)
|
||||
@@ -180,7 +180,7 @@ func MonadChainReaderIOK[GEA ~func(R) GIOEA, GEB ~func(R) GIOEB, GIOEA ~func() e
|
||||
// Deprecated:
|
||||
func ChainReaderIOK[GEA ~func(R) GIOEA, GEB ~func(R) GIOEB, GIOEA ~func() either.Either[E, A], GIOEB ~func() either.Either[E, B], GIOB ~func() B, GB ~func(R) GIOB, R, E, A, B any](f func(A) GB) func(GEA) GEB {
|
||||
return FR.ChainReaderK(
|
||||
MonadChain[GEA, GEB, GIOEA, GIOEB, R, E, A, B],
|
||||
Chain[GEA, GEB, GIOEA, GIOEB, R, E, A, B],
|
||||
RightReaderIO[GEB, GIOEB, GB, GIOB, R, E, B],
|
||||
f,
|
||||
)
|
||||
|
||||
@@ -28,12 +28,18 @@ import (
|
||||
"github.com/IBM/fp-go/v2/io"
|
||||
IOE "github.com/IBM/fp-go/v2/ioeither"
|
||||
L "github.com/IBM/fp-go/v2/lazy"
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
"github.com/IBM/fp-go/v2/reader"
|
||||
RE "github.com/IBM/fp-go/v2/readereither"
|
||||
"github.com/IBM/fp-go/v2/readerio"
|
||||
"github.com/IBM/fp-go/v2/readeroption"
|
||||
)
|
||||
|
||||
//go:inline
|
||||
func FromReaderOption[R, A, E any](onNone func() E) Kleisli[R, E, ReaderOption[R, A], A] {
|
||||
return function.Bind2nd(function.Flow2[ReaderOption[R, A], IOE.Kleisli[E, Option[A], A]], IOE.FromOption[A](onNone))
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func FromReaderIO[E, R, A any](ma ReaderIO[R, A]) ReaderIOEither[R, E, A] {
|
||||
return RightReaderIO[E](ma)
|
||||
}
|
||||
@@ -116,7 +122,7 @@ func MonadChainFirst[R, E, A, B any](fa ReaderIOEither[R, E, A], f func(A) Reade
|
||||
// The Either is automatically lifted into the ReaderIOEither context.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) either.Either[E, B]) ReaderIOEither[R, E, B] {
|
||||
func MonadChainEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f either.Kleisli[E, A, B]) ReaderIOEither[R, E, B] {
|
||||
return fromeither.MonadChainEitherK(
|
||||
MonadChain[R, E, A, B],
|
||||
FromEither[R, E, B],
|
||||
@@ -129,7 +135,7 @@ func MonadChainEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) eit
|
||||
// This is the curried version of MonadChainEitherK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainEitherK[R, E, A, B any](f func(A) either.Either[E, B]) Operator[R, E, A, B] {
|
||||
func ChainEitherK[R, E, A, B any](f either.Kleisli[E, A, B]) Operator[R, E, A, B] {
|
||||
return fromeither.ChainEitherK(
|
||||
Chain[R, E, A, B],
|
||||
FromEither[R, E, B],
|
||||
@@ -141,7 +147,7 @@ func ChainEitherK[R, E, A, B any](f func(A) either.Either[E, B]) Operator[R, E,
|
||||
// Useful for validation or side effects that return Either.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainFirstEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) either.Either[E, B]) ReaderIOEither[R, E, A] {
|
||||
func MonadChainFirstEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f either.Kleisli[E, A, B]) ReaderIOEither[R, E, A] {
|
||||
return fromeither.MonadChainFirstEitherK(
|
||||
MonadChain[R, E, A, A],
|
||||
MonadMap[R, E, B, A],
|
||||
@@ -155,7 +161,7 @@ func MonadChainFirstEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A
|
||||
// This is the curried version of MonadChainFirstEitherK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainFirstEitherK[R, E, A, B any](f func(A) either.Either[E, B]) Operator[R, E, A, A] {
|
||||
func ChainFirstEitherK[R, E, A, B any](f either.Kleisli[E, A, B]) Operator[R, E, A, A] {
|
||||
return fromeither.ChainFirstEitherK(
|
||||
Chain[R, E, A, A],
|
||||
Map[R, E, B, A],
|
||||
@@ -168,7 +174,7 @@ func ChainFirstEitherK[R, E, A, B any](f func(A) either.Either[E, B]) Operator[R
|
||||
// The Reader is automatically lifted into the ReaderIOEither context.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainReaderK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) Reader[R, B]) ReaderIOEither[R, E, B] {
|
||||
func MonadChainReaderK[E, R, A, B any](ma ReaderIOEither[R, E, A], f reader.Kleisli[R, A, B]) ReaderIOEither[R, E, B] {
|
||||
return fromreader.MonadChainReaderK(
|
||||
MonadChain[R, E, A, B],
|
||||
FromReader[E, R, B],
|
||||
@@ -181,19 +187,147 @@ func MonadChainReaderK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) Rea
|
||||
// This is the curried version of MonadChainReaderK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainReaderK[E, R, A, B any](f func(A) Reader[R, B]) Operator[R, E, A, B] {
|
||||
func ChainReaderK[E, R, A, B any](f reader.Kleisli[R, A, B]) Operator[R, E, A, B] {
|
||||
return fromreader.ChainReaderK(
|
||||
MonadChain[R, E, A, B],
|
||||
Chain[R, E, A, B],
|
||||
FromReader[E, R, B],
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func MonadChainFirstReaderK[E, R, A, B any](ma ReaderIOEither[R, E, A], f reader.Kleisli[R, A, B]) ReaderIOEither[R, E, A] {
|
||||
return fromreader.MonadChainFirstReaderK(
|
||||
MonadChainFirst[R, E, A, B],
|
||||
FromReader[E, R, B],
|
||||
ma,
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
// ChainReaderK returns a function that chains a Reader-returning function into ReaderIOEither.
|
||||
// This is the curried version of MonadChainReaderK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainFirstReaderK[E, R, A, B any](f reader.Kleisli[R, A, B]) Operator[R, E, A, A] {
|
||||
return fromreader.ChainFirstReaderK(
|
||||
ChainFirst[R, E, A, B],
|
||||
FromReader[E, R, B],
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func MonadChainReaderIOK[E, R, A, B any](ma ReaderIOEither[R, E, A], f readerio.Kleisli[R, A, B]) ReaderIOEither[R, E, B] {
|
||||
return fromreader.MonadChainReaderK(
|
||||
MonadChain[R, E, A, B],
|
||||
FromReaderIO[E, R, B],
|
||||
ma,
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func ChainReaderIOK[E, R, A, B any](f readerio.Kleisli[R, A, B]) Operator[R, E, A, B] {
|
||||
return fromreader.ChainReaderK(
|
||||
Chain[R, E, A, B],
|
||||
FromReaderIO[E, R, B],
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func MonadChainFirstReaderIOK[E, R, A, B any](ma ReaderIOEither[R, E, A], f readerio.Kleisli[R, A, B]) ReaderIOEither[R, E, A] {
|
||||
return fromreader.MonadChainFirstReaderK(
|
||||
MonadChainFirst[R, E, A, B],
|
||||
FromReaderIO[E, R, B],
|
||||
ma,
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func ChainFirstReaderIOK[E, R, A, B any](f readerio.Kleisli[R, A, B]) Operator[R, E, A, A] {
|
||||
return fromreader.ChainFirstReaderK(
|
||||
ChainFirst[R, E, A, B],
|
||||
FromReaderIO[E, R, B],
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func MonadChainReaderEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f RE.Kleisli[R, E, A, B]) ReaderIOEither[R, E, B] {
|
||||
return fromreader.MonadChainReaderK(
|
||||
MonadChain[R, E, A, B],
|
||||
FromReaderEither[R, E, B],
|
||||
ma,
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
// ChainReaderK returns a function that chains a Reader-returning function into ReaderIOEither.
|
||||
// This is the curried version of MonadChainReaderK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainReaderEitherK[E, R, A, B any](f RE.Kleisli[R, E, A, B]) Operator[R, E, A, B] {
|
||||
return fromreader.ChainReaderK(
|
||||
Chain[R, E, A, B],
|
||||
FromReaderEither[R, E, B],
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func MonadChainFirstReaderEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f RE.Kleisli[R, E, A, B]) ReaderIOEither[R, E, A] {
|
||||
return fromreader.MonadChainFirstReaderK(
|
||||
MonadChainFirst[R, E, A, B],
|
||||
FromReaderEither[R, E, B],
|
||||
ma,
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
// ChainReaderK returns a function that chains a Reader-returning function into ReaderIOEither.
|
||||
// This is the curried version of MonadChainReaderK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainFirstReaderEitherK[E, R, A, B any](f RE.Kleisli[R, E, A, B]) Operator[R, E, A, A] {
|
||||
return fromreader.ChainFirstReaderK(
|
||||
ChainFirst[R, E, A, B],
|
||||
FromReaderEither[R, E, B],
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func ChainReaderOptionK[R, A, B, E any](onNone func() E) func(readeroption.Kleisli[R, A, B]) Operator[R, E, A, B] {
|
||||
fro := FromReaderOption[R, B](onNone)
|
||||
return func(f readeroption.Kleisli[R, A, B]) Operator[R, E, A, B] {
|
||||
return fromreader.ChainReaderK(
|
||||
Chain[R, E, A, B],
|
||||
fro,
|
||||
f,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func ChainFirstReaderOptionK[R, A, B, E any](onNone func() E) func(readeroption.Kleisli[R, A, B]) Operator[R, E, A, A] {
|
||||
fro := FromReaderOption[R, B](onNone)
|
||||
return func(f readeroption.Kleisli[R, A, B]) Operator[R, E, A, A] {
|
||||
return fromreader.ChainFirstReaderK(
|
||||
ChainFirst[R, E, A, B],
|
||||
fro,
|
||||
f,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MonadChainIOEitherK chains an IOEither-returning computation into a ReaderIOEither.
|
||||
// The IOEither is automatically lifted into the ReaderIOEither context.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainIOEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) IOE.IOEither[E, B]) ReaderIOEither[R, E, B] {
|
||||
func MonadChainIOEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f IOE.Kleisli[E, A, B]) ReaderIOEither[R, E, B] {
|
||||
return fromioeither.MonadChainIOEitherK(
|
||||
MonadChain[R, E, A, B],
|
||||
FromIOEither[R, E, B],
|
||||
@@ -206,7 +340,7 @@ func MonadChainIOEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) I
|
||||
// This is the curried version of MonadChainIOEitherK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainIOEitherK[R, E, A, B any](f func(A) IOE.IOEither[E, B]) Operator[R, E, A, B] {
|
||||
func ChainIOEitherK[R, E, A, B any](f IOE.Kleisli[E, A, B]) Operator[R, E, A, B] {
|
||||
return fromioeither.ChainIOEitherK(
|
||||
Chain[R, E, A, B],
|
||||
FromIOEither[R, E, B],
|
||||
@@ -218,7 +352,7 @@ func ChainIOEitherK[R, E, A, B any](f func(A) IOE.IOEither[E, B]) Operator[R, E,
|
||||
// The IO is automatically lifted into the ReaderIOEither context (always succeeds).
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainIOK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) io.IO[B]) ReaderIOEither[R, E, B] {
|
||||
func MonadChainIOK[R, E, A, B any](ma ReaderIOEither[R, E, A], f io.Kleisli[A, B]) ReaderIOEither[R, E, B] {
|
||||
return fromio.MonadChainIOK(
|
||||
MonadChain[R, E, A, B],
|
||||
FromIO[R, E, B],
|
||||
@@ -231,7 +365,7 @@ func MonadChainIOK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) io.IO[B
|
||||
// This is the curried version of MonadChainIOK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainIOK[R, E, A, B any](f func(A) io.IO[B]) Operator[R, E, A, B] {
|
||||
func ChainIOK[R, E, A, B any](f io.Kleisli[A, B]) Operator[R, E, A, B] {
|
||||
return fromio.ChainIOK(
|
||||
Chain[R, E, A, B],
|
||||
FromIO[R, E, B],
|
||||
@@ -243,7 +377,7 @@ func ChainIOK[R, E, A, B any](f func(A) io.IO[B]) Operator[R, E, A, B] {
|
||||
// Useful for performing IO side effects while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainFirstIOK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) io.IO[B]) ReaderIOEither[R, E, A] {
|
||||
func MonadChainFirstIOK[R, E, A, B any](ma ReaderIOEither[R, E, A], f io.Kleisli[A, B]) ReaderIOEither[R, E, A] {
|
||||
return fromio.MonadChainFirstIOK(
|
||||
MonadChain[R, E, A, A],
|
||||
MonadMap[R, E, B, A],
|
||||
@@ -257,7 +391,7 @@ func MonadChainFirstIOK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) io
|
||||
// This is the curried version of MonadChainFirstIOK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainFirstIOK[R, E, A, B any](f func(A) io.IO[B]) Operator[R, E, A, A] {
|
||||
func ChainFirstIOK[R, E, A, B any](f io.Kleisli[A, B]) Operator[R, E, A, A] {
|
||||
return fromio.ChainFirstIOK(
|
||||
Chain[R, E, A, A],
|
||||
Map[R, E, B, A],
|
||||
@@ -270,7 +404,7 @@ func ChainFirstIOK[R, E, A, B any](f func(A) io.IO[B]) Operator[R, E, A, A] {
|
||||
// If the Option is None, the provided error function is called to produce the error value.
|
||||
//
|
||||
//go:inline
|
||||
func ChainOptionK[R, A, B, E any](onNone func() E) func(func(A) O.Option[B]) Operator[R, E, A, B] {
|
||||
func ChainOptionK[R, A, B, E any](onNone func() E) func(func(A) Option[B]) Operator[R, E, A, B] {
|
||||
return fromeither.ChainOptionK(
|
||||
MonadChain[R, E, A, B],
|
||||
FromEither[R, E, B],
|
||||
@@ -400,18 +534,18 @@ func FromReader[E, R, A any](ma Reader[R, A]) ReaderIOEither[R, E, A] {
|
||||
}
|
||||
|
||||
// RightIO lifts an IO into a ReaderIOEither, placing the result in the Right side.
|
||||
func RightIO[R, E, A any](ma io.IO[A]) ReaderIOEither[R, E, A] {
|
||||
func RightIO[R, E, A any](ma IO[A]) ReaderIOEither[R, E, A] {
|
||||
return function.Pipe2(ma, IOE.RightIO[E, A], FromIOEither[R, E, A])
|
||||
}
|
||||
|
||||
// LeftIO lifts an IO into a ReaderIOEither, placing the result in the Left (error) side.
|
||||
func LeftIO[R, A, E any](ma io.IO[E]) ReaderIOEither[R, E, A] {
|
||||
func LeftIO[R, A, E any](ma IO[E]) ReaderIOEither[R, E, A] {
|
||||
return function.Pipe2(ma, IOE.LeftIO[A, E], FromIOEither[R, E, A])
|
||||
}
|
||||
|
||||
// FromIO lifts an IO into a ReaderIOEither context.
|
||||
// The IO result is placed in the Right side (success).
|
||||
func FromIO[R, E, A any](ma io.IO[A]) ReaderIOEither[R, E, A] {
|
||||
func FromIO[R, E, A any](ma IO[A]) ReaderIOEither[R, E, A] {
|
||||
return RightIO[R, E](ma)
|
||||
}
|
||||
|
||||
@@ -419,7 +553,7 @@ func FromIO[R, E, A any](ma io.IO[A]) ReaderIOEither[R, E, A] {
|
||||
// The computation becomes independent of any reader context.
|
||||
//
|
||||
//go:inline
|
||||
func FromIOEither[R, E, A any](ma IOE.IOEither[E, A]) ReaderIOEither[R, E, A] {
|
||||
func FromIOEither[R, E, A any](ma IOEither[E, A]) ReaderIOEither[R, E, A] {
|
||||
return reader.Of[R](ma)
|
||||
}
|
||||
|
||||
@@ -449,7 +583,7 @@ func Asks[E, R, A any](r Reader[R, A]) ReaderIOEither[R, E, A] {
|
||||
// If the Option is None, the provided function is called to produce the error.
|
||||
//
|
||||
//go:inline
|
||||
func FromOption[R, A, E any](onNone func() E) func(O.Option[A]) ReaderIOEither[R, E, A] {
|
||||
func FromOption[R, A, E any](onNone func() E) func(Option[A]) ReaderIOEither[R, E, A] {
|
||||
return fromeither.FromOption(FromEither[R, E, A], onNone)
|
||||
}
|
||||
|
||||
@@ -616,3 +750,8 @@ func MapLeft[R, A, E1, E2 any](f func(E1) E2) func(ReaderIOEither[R, E1, A]) Rea
|
||||
func Local[E, A, R1, R2 any](f func(R2) R1) func(ReaderIOEither[R1, E, A]) ReaderIOEither[R2, E, A] {
|
||||
return reader.Local[IOEither[E, A]](f)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func Read[E, A, R any](r R) func(ReaderIOEither[R, E, A]) IOEither[E, A] {
|
||||
return reader.Read[IOEither[E, A]](r)
|
||||
}
|
||||
|
||||
@@ -19,8 +19,10 @@ import (
|
||||
"github.com/IBM/fp-go/v2/either"
|
||||
"github.com/IBM/fp-go/v2/io"
|
||||
"github.com/IBM/fp-go/v2/ioeither"
|
||||
"github.com/IBM/fp-go/v2/optics/lens/option"
|
||||
"github.com/IBM/fp-go/v2/reader"
|
||||
"github.com/IBM/fp-go/v2/readerio"
|
||||
"github.com/IBM/fp-go/v2/readeroption"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -84,4 +86,7 @@ type (
|
||||
// Example:
|
||||
// var doubleOp Operator[Config, error, int, int] = Map(func(x int) int { return x * 2 })
|
||||
Operator[R, E, A, B any] = Kleisli[R, E, ReaderIOEither[R, E, A], B]
|
||||
|
||||
ReaderOption[R, A any] = readeroption.ReaderOption[R, A]
|
||||
Option[A any] = option.Option[A]
|
||||
)
|
||||
|
||||
@@ -56,5 +56,3 @@ func TestApS(t *testing.T) {
|
||||
|
||||
assert.Equal(t, res(context.Background())(), result.Of("John Doe"))
|
||||
}
|
||||
|
||||
// Made with Bob
|
||||
|
||||
@@ -16,12 +16,22 @@
|
||||
package readerioresult
|
||||
|
||||
import (
|
||||
"github.com/IBM/fp-go/v2/reader"
|
||||
RE "github.com/IBM/fp-go/v2/readereither"
|
||||
"github.com/IBM/fp-go/v2/readerio"
|
||||
RIOE "github.com/IBM/fp-go/v2/readerioeither"
|
||||
"github.com/IBM/fp-go/v2/readeroption"
|
||||
)
|
||||
|
||||
//go:inline
|
||||
func FromReaderOption[R, A any](onNone func() error) Kleisli[R, ReaderOption[R, A], A] {
|
||||
return RIOE.FromReaderOption[R, A](onNone)
|
||||
}
|
||||
|
||||
// FromReaderIO creates a function that lifts a ReaderIO-producing function into ReaderIOResult.
|
||||
// The ReaderIO result is placed in the Right side of the Either.
|
||||
//
|
||||
//go:inline
|
||||
func FromReaderIO[R, A any](ma ReaderIO[R, A]) ReaderIOResult[R, A] {
|
||||
return RIOE.FromReaderIO[error](ma)
|
||||
}
|
||||
@@ -158,7 +168,7 @@ func ChainFirstResultK[R, A, B any](f func(A) Result[B]) Operator[R, A, A] {
|
||||
// The Reader is automatically lifted into the ReaderIOResult context.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainReaderK[R, A, B any](ma ReaderIOResult[R, A], f func(A) Reader[R, B]) ReaderIOResult[R, B] {
|
||||
func MonadChainReaderK[R, A, B any](ma ReaderIOResult[R, A], f reader.Kleisli[R, A, B]) ReaderIOResult[R, B] {
|
||||
return RIOE.MonadChainReaderK(ma, f)
|
||||
}
|
||||
|
||||
@@ -166,10 +176,99 @@ func MonadChainReaderK[R, A, B any](ma ReaderIOResult[R, A], f func(A) Reader[R,
|
||||
// This is the curried version of MonadChainReaderK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainReaderK[R, A, B any](f func(A) Reader[R, B]) Operator[R, A, B] {
|
||||
func ChainReaderK[R, A, B any](f reader.Kleisli[R, A, B]) Operator[R, A, B] {
|
||||
return RIOE.ChainReaderK[error](f)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func MonadChainFirstReaderK[R, A, B any](ma ReaderIOResult[R, A], f reader.Kleisli[R, A, B]) ReaderIOResult[R, A] {
|
||||
return RIOE.MonadChainFirstReaderK(ma, f)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func ChainFirstReaderK[R, A, B any](f reader.Kleisli[R, A, B]) Operator[R, A, A] {
|
||||
return RIOE.ChainFirstReaderK[error](f)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func ChainReaderOptionK[R, A, B any](onNone func() error) func(readeroption.Kleisli[R, A, B]) Operator[R, A, B] {
|
||||
return RIOE.ChainReaderOptionK[R, A, B](onNone)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func ChainFirstReaderOptionK[R, A, B any](onNone func() error) func(readeroption.Kleisli[R, A, B]) Operator[R, A, A] {
|
||||
return RIOE.ChainFirstReaderOptionK[R, A, B](onNone)
|
||||
}
|
||||
|
||||
// MonadChainReaderK chains a Reader-returning computation into a ReaderIOResult.
|
||||
// The Reader is automatically lifted into the ReaderIOResult context.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainReaderEitherK[R, A, B any](ma ReaderIOResult[R, A], f RE.Kleisli[R, error, A, B]) ReaderIOResult[R, B] {
|
||||
return RIOE.MonadChainReaderEitherK(ma, f)
|
||||
}
|
||||
|
||||
// ChainReaderK returns a function that chains a Reader-returning function into ReaderIOResult.
|
||||
// This is the curried version of MonadChainReaderK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainReaderEitherK[R, A, B any](f RE.Kleisli[R, error, A, B]) Operator[R, A, B] {
|
||||
return RIOE.ChainReaderEitherK(f)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func MonadChainFirstReaderEitherK[R, A, B any](ma ReaderIOResult[R, A], f RE.Kleisli[R, error, A, B]) ReaderIOResult[R, A] {
|
||||
return RIOE.MonadChainFirstReaderEitherK(ma, f)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func ChainFirstReaderEitherK[R, A, B any](f RE.Kleisli[R, error, A, B]) Operator[R, A, A] {
|
||||
return RIOE.ChainFirstReaderEitherK(f)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func MonadChainReaderResultK[R, A, B any](ma ReaderIOResult[R, A], f RE.Kleisli[R, error, A, B]) ReaderIOResult[R, B] {
|
||||
return RIOE.MonadChainReaderEitherK(ma, f)
|
||||
}
|
||||
|
||||
// ChainReaderK returns a function that chains a Reader-returning function into ReaderIOResult.
|
||||
// This is the curried version of MonadChainReaderK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainReaderResultK[R, A, B any](f RE.Kleisli[R, error, A, B]) Operator[R, A, B] {
|
||||
return RIOE.ChainReaderEitherK(f)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func MonadChainFirstReaderResultK[R, A, B any](ma ReaderIOResult[R, A], f RE.Kleisli[R, error, A, B]) ReaderIOResult[R, A] {
|
||||
return RIOE.MonadChainFirstReaderEitherK(ma, f)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func ChainFirstReaderResultK[R, A, B any](f RE.Kleisli[R, error, A, B]) Operator[R, A, A] {
|
||||
return RIOE.ChainFirstReaderEitherK(f)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func MonadChainReaderIOK[R, A, B any](ma ReaderIOResult[R, A], f readerio.Kleisli[R, A, B]) ReaderIOResult[R, B] {
|
||||
return RIOE.MonadChainReaderIOK(ma, f)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func ChainReaderIOK[R, A, B any](f readerio.Kleisli[R, A, B]) Operator[R, A, B] {
|
||||
return RIOE.ChainReaderIOK[error](f)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func MonadChainFirstReaderIOK[R, A, B any](ma ReaderIOResult[R, A], f readerio.Kleisli[R, A, B]) ReaderIOResult[R, A] {
|
||||
return RIOE.MonadChainFirstReaderIOK(ma, f)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func ChainFirstReaderIOK[R, A, B any](f readerio.Kleisli[R, A, B]) Operator[R, A, A] {
|
||||
return RIOE.ChainFirstReaderIOK[error](f)
|
||||
}
|
||||
|
||||
// MonadChainIOEitherK chains an IOEither-returning computation into a ReaderIOResult.
|
||||
// The IOEither is automatically lifted into the ReaderIOResult context.
|
||||
//
|
||||
@@ -578,3 +677,8 @@ func MapLeft[R, A, E any](f func(error) E) func(ReaderIOResult[R, A]) RIOE.Reade
|
||||
func Local[A, R1, R2 any](f func(R2) R1) func(ReaderIOResult[R1, A]) ReaderIOResult[R2, A] {
|
||||
return RIOE.Local[error, A](f)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func Read[A, R any](r R) func(ReaderIOResult[R, A]) IOResult[A] {
|
||||
return RIOE.Read[error, A](r)
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/IBM/fp-go/v2/option"
|
||||
"github.com/IBM/fp-go/v2/reader"
|
||||
"github.com/IBM/fp-go/v2/readerio"
|
||||
"github.com/IBM/fp-go/v2/readeroption"
|
||||
"github.com/IBM/fp-go/v2/result"
|
||||
)
|
||||
|
||||
@@ -42,6 +43,8 @@ type (
|
||||
// side effects to produce a value of type A.
|
||||
ReaderIO[R, A any] = readerio.ReaderIO[R, A]
|
||||
|
||||
ReaderOption[R, A any] = readeroption.ReaderOption[R, A]
|
||||
|
||||
// IOEither represents a computation that performs side effects and can either
|
||||
// fail with an error of type E or succeed with a value of type A.
|
||||
IOEither[E, A any] = ioeither.IOEither[E, A]
|
||||
|
||||
88
v2/readeroption/array.go
Normal file
88
v2/readeroption/array.go
Normal file
@@ -0,0 +1,88 @@
|
||||
// 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 readeroption
|
||||
|
||||
import (
|
||||
G "github.com/IBM/fp-go/v2/readeroption/generic"
|
||||
)
|
||||
|
||||
// TraverseArray transforms an array by applying a function that returns a ReaderOption to each element.
|
||||
// If any element results in None, the entire result is None.
|
||||
// Otherwise, returns Some containing an array of all the unwrapped values.
|
||||
//
|
||||
// This is useful for performing a sequence of operations that may fail on each element of an array,
|
||||
// where you want all operations to succeed or the entire computation to fail.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type DB struct { ... }
|
||||
//
|
||||
// findUser := func(id int) readeroption.ReaderOption[DB, User] { ... }
|
||||
//
|
||||
// userIDs := []int{1, 2, 3}
|
||||
// result := F.Pipe1(
|
||||
// readeroption.Of[DB](userIDs),
|
||||
// readeroption.Chain(readeroption.TraverseArray[DB](findUser)),
|
||||
// )
|
||||
// // result will be Some([]User) if all users are found, None otherwise
|
||||
func TraverseArray[E, A, B any](f func(A) ReaderOption[E, B]) Kleisli[E, []A, []B] {
|
||||
return G.TraverseArray[ReaderOption[E, B], ReaderOption[E, []B], []A](f)
|
||||
}
|
||||
|
||||
// TraverseArrayWithIndex is like TraverseArray but the function also receives the index of each element.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type DB struct { ... }
|
||||
//
|
||||
// processWithIndex := func(idx int, value string) readeroption.ReaderOption[DB, Result] {
|
||||
// // Use idx in processing
|
||||
// return readeroption.Asks(func(db DB) option.Option[Result] { ... })
|
||||
// }
|
||||
//
|
||||
// values := []string{"a", "b", "c"}
|
||||
// result := readeroption.TraverseArrayWithIndex[DB](processWithIndex)(values)
|
||||
func TraverseArrayWithIndex[E, A, B any](f func(int, A) ReaderOption[E, B]) func([]A) ReaderOption[E, []B] {
|
||||
return G.TraverseArrayWithIndex[ReaderOption[E, B], ReaderOption[E, []B], []A](f)
|
||||
}
|
||||
|
||||
// SequenceArray converts an array of ReaderOption values into a ReaderOption of an array.
|
||||
// If any element is None, the entire result is None.
|
||||
// Otherwise, returns Some containing an array of all the unwrapped values.
|
||||
//
|
||||
// This is useful when you have multiple independent ReaderOption computations and want to
|
||||
// combine their results into a single array.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type Config struct { ... }
|
||||
//
|
||||
// user1 := readeroption.Of[Config](User{ID: 1, Name: "Alice"})
|
||||
// user2 := readeroption.Of[Config](User{ID: 2, Name: "Bob"})
|
||||
// user3 := readeroption.None[Config, User]()
|
||||
//
|
||||
// result := readeroption.SequenceArray([]readeroption.ReaderOption[Config, User]{
|
||||
// user1, user2, user3,
|
||||
// })
|
||||
// // result(config) will be option.None[[]User]() because user3 is None
|
||||
//
|
||||
// result2 := readeroption.SequenceArray([]readeroption.ReaderOption[Config, User]{
|
||||
// user1, user2,
|
||||
// })
|
||||
// // result2(config) will be option.Some([]User{{ID: 1, Name: "Alice"}, {ID: 2, Name: "Bob"}})
|
||||
func SequenceArray[E, A any](ma []ReaderOption[E, A]) ReaderOption[E, []A] {
|
||||
return G.SequenceArray[ReaderOption[E, A], ReaderOption[E, []A]](ma)
|
||||
}
|
||||
107
v2/readeroption/array_test.go
Normal file
107
v2/readeroption/array_test.go
Normal file
@@ -0,0 +1,107 @@
|
||||
// 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 readeroption
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
A "github.com/IBM/fp-go/v2/array"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSequenceArray(t *testing.T) {
|
||||
|
||||
n := 10
|
||||
|
||||
readers := A.MakeBy(n, Of[context.Context, int])
|
||||
exp := O.Of(A.MakeBy(n, F.Identity[int]))
|
||||
|
||||
g := F.Pipe1(
|
||||
readers,
|
||||
SequenceArray[context.Context, int],
|
||||
)
|
||||
|
||||
assert.Equal(t, exp, g(context.Background()))
|
||||
}
|
||||
|
||||
func TestTraverseArray(t *testing.T) {
|
||||
// Function that doubles a number if it's positive
|
||||
doubleIfPositive := func(x int) ReaderOption[context.Context, int] {
|
||||
if x > 0 {
|
||||
return Of[context.Context](x * 2)
|
||||
}
|
||||
return None[context.Context, int]()
|
||||
}
|
||||
|
||||
// Test with all positive numbers
|
||||
input1 := []int{1, 2, 3}
|
||||
g1 := F.Pipe1(
|
||||
Of[context.Context](input1),
|
||||
Chain(TraverseArray(doubleIfPositive)),
|
||||
)
|
||||
assert.Equal(t, O.Of([]int{2, 4, 6}), g1(context.Background()))
|
||||
|
||||
// Test with a negative number (should return None)
|
||||
input2 := []int{1, -2, 3}
|
||||
g2 := F.Pipe1(
|
||||
Of[context.Context](input2),
|
||||
Chain(TraverseArray(doubleIfPositive)),
|
||||
)
|
||||
assert.Equal(t, O.None[[]int](), g2(context.Background()))
|
||||
|
||||
// Test with empty array
|
||||
input3 := []int{}
|
||||
g3 := F.Pipe1(
|
||||
Of[context.Context](input3),
|
||||
Chain(TraverseArray(doubleIfPositive)),
|
||||
)
|
||||
assert.Equal(t, O.Of([]int{}), g3(context.Background()))
|
||||
}
|
||||
|
||||
func TestTraverseArrayWithIndex(t *testing.T) {
|
||||
// Function that multiplies value by its index if index is even
|
||||
multiplyByIndexIfEven := func(idx int, x int) ReaderOption[context.Context, int] {
|
||||
if idx%2 == 0 {
|
||||
return Of[context.Context](x * idx)
|
||||
}
|
||||
return Of[context.Context](x)
|
||||
}
|
||||
|
||||
input := []int{10, 20, 30, 40}
|
||||
g := TraverseArrayWithIndex(multiplyByIndexIfEven)(input)
|
||||
|
||||
// Expected: [10*0, 20, 30*2, 40] = [0, 20, 60, 40]
|
||||
assert.Equal(t, O.Of([]int{0, 20, 60, 40}), g(context.Background()))
|
||||
}
|
||||
|
||||
func TestTraverseArrayWithIndexNone(t *testing.T) {
|
||||
// Function that returns None for odd indices
|
||||
noneForOdd := func(idx int, x int) ReaderOption[context.Context, int] {
|
||||
if idx%2 == 0 {
|
||||
return Of[context.Context](x)
|
||||
}
|
||||
return None[context.Context, int]()
|
||||
}
|
||||
|
||||
input := []int{10, 20, 30}
|
||||
g := TraverseArrayWithIndex(noneForOdd)(input)
|
||||
|
||||
// Should return None because index 1 returns None
|
||||
assert.Equal(t, O.None[[]int](), g(context.Background()))
|
||||
}
|
||||
303
v2/readeroption/bind.go
Normal file
303
v2/readeroption/bind.go
Normal file
@@ -0,0 +1,303 @@
|
||||
// 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 readeroption
|
||||
|
||||
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/readeroption/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 {
|
||||
// User User
|
||||
// Config Config
|
||||
// }
|
||||
// type Env struct {
|
||||
// UserService UserService
|
||||
// ConfigService ConfigService
|
||||
// }
|
||||
// result := readereither.Do[Env, error](State{})
|
||||
func Do[R, S any](
|
||||
empty S,
|
||||
) ReaderOption[R, S] {
|
||||
return G.Do[ReaderOption[R, 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 shared 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
|
||||
// }
|
||||
// type Env struct {
|
||||
// UserService UserService
|
||||
// ConfigService ConfigService
|
||||
// }
|
||||
//
|
||||
// result := F.Pipe2(
|
||||
// readereither.Do[Env, error](State{}),
|
||||
// readereither.Bind(
|
||||
// func(user User) func(State) State {
|
||||
// return func(s State) State { s.User = user; return s }
|
||||
// },
|
||||
// func(s State) readereither.ReaderOption[Env, error, User] {
|
||||
// return readereither.Asks(func(env Env) either.Either[error, User] {
|
||||
// return env.UserService.GetUser()
|
||||
// })
|
||||
// },
|
||||
// ),
|
||||
// readereither.Bind(
|
||||
// func(cfg Config) func(State) State {
|
||||
// return func(s State) State { s.Config = cfg; return s }
|
||||
// },
|
||||
// func(s State) readereither.ReaderOption[Env, error, Config] {
|
||||
// // This can access s.User from the previous step
|
||||
// return readereither.Asks(func(env Env) either.Either[error, Config] {
|
||||
// return env.ConfigService.GetConfigForUser(s.User.ID)
|
||||
// })
|
||||
// },
|
||||
// ),
|
||||
// )
|
||||
func Bind[R, S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
f Kleisli[R, S1, T],
|
||||
) func(ReaderOption[R, S1]) ReaderOption[R, S2] {
|
||||
return G.Bind[ReaderOption[R, S1], ReaderOption[R, S2]](setter, f)
|
||||
}
|
||||
|
||||
// Let attaches the result of a computation to a context [S1] to produce a context [S2]
|
||||
func Let[R, S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
f func(S1) T,
|
||||
) func(ReaderOption[R, S1]) ReaderOption[R, S2] {
|
||||
return G.Let[ReaderOption[R, S1], ReaderOption[R, S2]](setter, f)
|
||||
}
|
||||
|
||||
// LetTo attaches the a value to a context [S1] to produce a context [S2]
|
||||
func LetTo[R, S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
b T,
|
||||
) func(ReaderOption[R, S1]) ReaderOption[R, S2] {
|
||||
return G.LetTo[ReaderOption[R, S1], ReaderOption[R, S2]](setter, b)
|
||||
}
|
||||
|
||||
// BindTo initializes a new state [S1] from a value [T]
|
||||
func BindTo[R, S1, T any](
|
||||
setter func(T) S1,
|
||||
) func(ReaderOption[R, T]) ReaderOption[R, S1] {
|
||||
return G.BindTo[ReaderOption[R, S1], ReaderOption[R, 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 {
|
||||
// User User
|
||||
// Config Config
|
||||
// }
|
||||
// type Env struct {
|
||||
// UserService UserService
|
||||
// ConfigService ConfigService
|
||||
// }
|
||||
//
|
||||
// // These operations are independent and can be combined with ApS
|
||||
// getUser := readereither.Asks(func(env Env) either.Either[error, User] {
|
||||
// return env.UserService.GetUser()
|
||||
// })
|
||||
// getConfig := readereither.Asks(func(env Env) either.Either[error, Config] {
|
||||
// return env.ConfigService.GetConfig()
|
||||
// })
|
||||
//
|
||||
// result := F.Pipe2(
|
||||
// readereither.Do[Env, error](State{}),
|
||||
// readereither.ApS(
|
||||
// func(user User) func(State) State {
|
||||
// return func(s State) State { s.User = user; return s }
|
||||
// },
|
||||
// getUser,
|
||||
// ),
|
||||
// readereither.ApS(
|
||||
// func(cfg Config) func(State) State {
|
||||
// return func(s State) State { s.Config = cfg; return s }
|
||||
// },
|
||||
// getConfig,
|
||||
// ),
|
||||
// )
|
||||
func ApS[R, S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
fa ReaderOption[R, T],
|
||||
) func(ReaderOption[R, S1]) ReaderOption[R, S2] {
|
||||
return G.ApS[ReaderOption[R, S1], ReaderOption[R, S2]](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
|
||||
// }
|
||||
// type Env struct {
|
||||
// UserService UserService
|
||||
// ConfigService ConfigService
|
||||
// }
|
||||
//
|
||||
// configLens := lens.MakeLens(
|
||||
// func(s State) Config { return s.Config },
|
||||
// func(s State, c Config) State { s.Config = c; return s },
|
||||
// )
|
||||
//
|
||||
// getConfig := readereither.Asks(func(env Env) either.Either[error, Config] {
|
||||
// return env.ConfigService.GetConfig()
|
||||
// })
|
||||
// result := F.Pipe2(
|
||||
// readereither.Of[Env, error](State{}),
|
||||
// readereither.ApSL(configLens, getConfig),
|
||||
// )
|
||||
func ApSL[R, S, T any](
|
||||
lens L.Lens[S, T],
|
||||
fa ReaderOption[R, T],
|
||||
) func(ReaderOption[R, S]) ReaderOption[R, 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 ReaderOption computation that produces an updated value.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type State struct {
|
||||
// User User
|
||||
// Config Config
|
||||
// }
|
||||
// type Env struct {
|
||||
// UserService UserService
|
||||
// ConfigService ConfigService
|
||||
// }
|
||||
//
|
||||
// userLens := lens.MakeLens(
|
||||
// func(s State) User { return s.User },
|
||||
// func(s State, u User) State { s.User = u; return s },
|
||||
// )
|
||||
//
|
||||
// result := F.Pipe2(
|
||||
// readereither.Do[Env, error](State{}),
|
||||
// readereither.BindL(userLens, func(user User) readereither.ReaderOption[Env, error, User] {
|
||||
// return readereither.Asks(func(env Env) either.Either[error, User] {
|
||||
// return env.UserService.GetUser()
|
||||
// })
|
||||
// }),
|
||||
// )
|
||||
func BindL[R, S, T any](
|
||||
lens L.Lens[S, T],
|
||||
f Kleisli[R, T, T],
|
||||
) func(ReaderOption[R, S]) ReaderOption[R, 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 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 ReaderOption).
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type State struct {
|
||||
// User User
|
||||
// Config Config
|
||||
// }
|
||||
//
|
||||
// configLens := lens.MakeLens(
|
||||
// func(s State) Config { return s.Config },
|
||||
// func(s State, c Config) State { s.Config = c; return s },
|
||||
// )
|
||||
//
|
||||
// result := F.Pipe2(
|
||||
// readereither.Do[any, error](State{Config: Config{Host: "localhost"}}),
|
||||
// readereither.LetL(configLens, func(cfg Config) Config {
|
||||
// cfg.Port = 8080
|
||||
// return cfg
|
||||
// }),
|
||||
// )
|
||||
func LetL[R, S, T any](
|
||||
lens L.Lens[S, T],
|
||||
f func(T) T,
|
||||
) func(ReaderOption[R, S]) ReaderOption[R, S] {
|
||||
return Let[R](lens.Set, F.Flow2(lens.Get, 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
|
||||
// }
|
||||
//
|
||||
// configLens := lens.MakeLens(
|
||||
// func(s State) Config { return s.Config },
|
||||
// func(s State, c Config) State { s.Config = c; return s },
|
||||
// )
|
||||
//
|
||||
// newConfig := Config{Host: "localhost", Port: 8080}
|
||||
// result := F.Pipe2(
|
||||
// readereither.Do[any, error](State{}),
|
||||
// readereither.LetToL(configLens, newConfig),
|
||||
// )
|
||||
func LetToL[R, S, T any](
|
||||
lens L.Lens[S, T],
|
||||
b T,
|
||||
) func(ReaderOption[R, S]) ReaderOption[R, S] {
|
||||
return LetTo[R](lens.Set, b)
|
||||
}
|
||||
99
v2/readeroption/bind_test.go
Normal file
99
v2/readeroption/bind_test.go
Normal file
@@ -0,0 +1,99 @@
|
||||
// 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 readeroption
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
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) ReaderOption[context.Context, string] {
|
||||
return Of[context.Context]("Doe")
|
||||
}
|
||||
|
||||
func getGivenName(s utils.WithLastName) ReaderOption[context.Context, string] {
|
||||
return Of[context.Context]("John")
|
||||
}
|
||||
|
||||
func TestBind(t *testing.T) {
|
||||
|
||||
res := F.Pipe3(
|
||||
Do[context.Context](utils.Empty),
|
||||
Bind(utils.SetLastName, getLastName),
|
||||
Bind(utils.SetGivenName, getGivenName),
|
||||
Map[context.Context](utils.GetFullName),
|
||||
)
|
||||
|
||||
assert.Equal(t, res(context.Background()), O.Of("John Doe"))
|
||||
}
|
||||
|
||||
func TestApS(t *testing.T) {
|
||||
|
||||
res := F.Pipe3(
|
||||
Do[context.Context](utils.Empty),
|
||||
ApS(utils.SetLastName, Of[context.Context]("Doe")),
|
||||
ApS(utils.SetGivenName, Of[context.Context]("John")),
|
||||
Map[context.Context](utils.GetFullName),
|
||||
)
|
||||
|
||||
assert.Equal(t, res(context.Background()), O.Of("John Doe"))
|
||||
}
|
||||
|
||||
func TestLet(t *testing.T) {
|
||||
res := F.Pipe3(
|
||||
Do[context.Context](utils.Empty),
|
||||
Let[context.Context](utils.SetLastName, func(s utils.Initial) string {
|
||||
return "Doe"
|
||||
}),
|
||||
Let[context.Context](utils.SetGivenName, func(s utils.WithLastName) string {
|
||||
return "John"
|
||||
}),
|
||||
Map[context.Context](utils.GetFullName),
|
||||
)
|
||||
|
||||
assert.Equal(t, res(context.Background()), O.Of("John Doe"))
|
||||
}
|
||||
|
||||
func TestLetTo(t *testing.T) {
|
||||
res := F.Pipe3(
|
||||
Do[context.Context](utils.Empty),
|
||||
LetTo[context.Context](utils.SetLastName, "Doe"),
|
||||
LetTo[context.Context](utils.SetGivenName, "John"),
|
||||
Map[context.Context](utils.GetFullName),
|
||||
)
|
||||
|
||||
assert.Equal(t, res(context.Background()), O.Of("John Doe"))
|
||||
}
|
||||
|
||||
func TestBindTo(t *testing.T) {
|
||||
type State struct {
|
||||
Value int
|
||||
}
|
||||
|
||||
res := F.Pipe1(
|
||||
Of[context.Context](42),
|
||||
BindTo[context.Context](func(v int) State {
|
||||
return State{Value: v}
|
||||
}),
|
||||
)
|
||||
|
||||
assert.Equal(t, res(context.Background()), O.Of(State{Value: 42}))
|
||||
}
|
||||
113
v2/readeroption/curry.go
Normal file
113
v2/readeroption/curry.go
Normal file
@@ -0,0 +1,113 @@
|
||||
// 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 readeroption
|
||||
|
||||
import (
|
||||
G "github.com/IBM/fp-go/v2/readeroption/generic"
|
||||
)
|
||||
|
||||
// Curry functions convert Go functions that take a context as the first parameter
|
||||
// and return (value, bool) into curried ReaderOption functions.
|
||||
//
|
||||
// This follows the Go convention of passing context as the first parameter
|
||||
// (see https://pkg.go.dev/context), while providing a functional programming interface.
|
||||
//
|
||||
// The bool return value indicates success (true) or failure (false), which maps to
|
||||
// Some or None in the Option monad.
|
||||
|
||||
// Curry0 converts a function that takes only a context and returns (A, bool)
|
||||
// into a ReaderOption[R, A].
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// getConfig := func(ctx context.Context) (Config, bool) {
|
||||
// cfg, ok := ctx.Value("config").(Config)
|
||||
// return cfg, ok
|
||||
// }
|
||||
// ro := readeroption.Curry0(getConfig)
|
||||
// result := ro(ctx) // Returns option.Some(config) or option.None()
|
||||
func Curry0[R, A any](f func(R) (A, bool)) ReaderOption[R, A] {
|
||||
return G.Curry0[ReaderOption[R, A]](f)
|
||||
}
|
||||
|
||||
// Curry1 converts a function that takes a context and one argument, returning (A, bool),
|
||||
// into a curried function that returns a ReaderOption.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// findUser := func(ctx context.Context, id int) (User, bool) {
|
||||
// // Query database using context
|
||||
// return user, found
|
||||
// }
|
||||
// ro := readeroption.Curry1(findUser)
|
||||
// result := ro(123)(ctx) // Returns option.Some(user) or option.None()
|
||||
func Curry1[R, T1, A any](f func(R, T1) (A, bool)) Kleisli[R, T1, A] {
|
||||
return G.Curry1[ReaderOption[R, A]](f)
|
||||
}
|
||||
|
||||
// Curry2 converts a function that takes a context and two arguments, returning (A, bool),
|
||||
// into a curried function that returns a ReaderOption.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// query := func(ctx context.Context, table string, id int) (Record, bool) {
|
||||
// // Query database using context
|
||||
// return record, found
|
||||
// }
|
||||
// ro := readeroption.Curry2(query)
|
||||
// result := ro("users")(123)(ctx) // Returns option.Some(record) or option.None()
|
||||
func Curry2[R, T1, T2, A any](f func(R, T1, T2) (A, bool)) func(T1) func(T2) ReaderOption[R, A] {
|
||||
return G.Curry2[ReaderOption[R, A]](f)
|
||||
}
|
||||
|
||||
// Curry3 converts a function that takes a context and three arguments, returning (A, bool),
|
||||
// into a curried function that returns a ReaderOption.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// complexQuery := func(ctx context.Context, db string, table string, id int) (Record, bool) {
|
||||
// // Query database using context
|
||||
// return record, found
|
||||
// }
|
||||
// ro := readeroption.Curry3(complexQuery)
|
||||
// result := ro("mydb")("users")(123)(ctx)
|
||||
func Curry3[R, T1, T2, T3, A any](f func(R, T1, T2, T3) (A, bool)) func(T1) func(T2) func(T3) ReaderOption[R, A] {
|
||||
return G.Curry3[ReaderOption[R, A]](f)
|
||||
}
|
||||
|
||||
// Uncurry1 converts a curried ReaderOption function back to a Go function
|
||||
// that takes a context and one argument, returning (A, bool).
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// ro := func(id int) readeroption.ReaderOption[context.Context, User] { ... }
|
||||
// findUser := readeroption.Uncurry1(ro)
|
||||
// user, found := findUser(ctx, 123)
|
||||
func Uncurry1[R, T1, A any](f func(T1) ReaderOption[R, A]) func(R, T1) (A, bool) {
|
||||
return G.Uncurry1(f)
|
||||
}
|
||||
|
||||
// Uncurry2 converts a curried ReaderOption function back to a Go function
|
||||
// that takes a context and two arguments, returning (A, bool).
|
||||
func Uncurry2[R, T1, T2, A any](f func(T1) func(T2) ReaderOption[R, A]) func(R, T1, T2) (A, bool) {
|
||||
return G.Uncurry2(f)
|
||||
}
|
||||
|
||||
// Uncurry3 converts a curried ReaderOption function back to a Go function
|
||||
// that takes a context and three arguments, returning (A, bool).
|
||||
func Uncurry3[R, T1, T2, T3, A any](f func(T1) func(T2) func(T3) ReaderOption[R, A]) func(R, T1, T2, T3) (A, bool) {
|
||||
return G.Uncurry3(f)
|
||||
}
|
||||
162
v2/readeroption/curry_test.go
Normal file
162
v2/readeroption/curry_test.go
Normal file
@@ -0,0 +1,162 @@
|
||||
// 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 readeroption
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCurry0(t *testing.T) {
|
||||
// Function that returns a value from context
|
||||
getConfig := func(ctx context.Context) (string, bool) {
|
||||
if val := ctx.Value("config"); val != nil {
|
||||
return val.(string), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
ro := Curry0(getConfig)
|
||||
|
||||
// Test with value in context
|
||||
ctx1 := context.WithValue(context.Background(), "config", "test-config")
|
||||
result1 := ro(ctx1)
|
||||
assert.Equal(t, O.Of("test-config"), result1)
|
||||
|
||||
// Test without value in context
|
||||
ctx2 := context.Background()
|
||||
result2 := ro(ctx2)
|
||||
assert.Equal(t, O.None[string](), result2)
|
||||
}
|
||||
|
||||
func TestCurry1(t *testing.T) {
|
||||
// Function that looks up a value by key
|
||||
lookup := func(ctx context.Context, key string) (int, bool) {
|
||||
if val := ctx.Value(key); val != nil {
|
||||
return val.(int), true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
ro := Curry1(lookup)
|
||||
|
||||
// Test with value in context
|
||||
ctx1 := context.WithValue(context.Background(), "count", 42)
|
||||
result1 := ro("count")(ctx1)
|
||||
assert.Equal(t, O.Of(42), result1)
|
||||
|
||||
// Test without value in context
|
||||
ctx2 := context.Background()
|
||||
result2 := ro("count")(ctx2)
|
||||
assert.Equal(t, O.None[int](), result2)
|
||||
}
|
||||
|
||||
func TestCurry2(t *testing.T) {
|
||||
// Function that combines two parameters with context
|
||||
combine := func(ctx context.Context, a string, b int) (string, bool) {
|
||||
if ctx.Value("enabled") == true {
|
||||
return a + ":" + string(rune('0'+b)), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
ro := Curry2(combine)
|
||||
|
||||
// Test with enabled context
|
||||
ctx1 := context.WithValue(context.Background(), "enabled", true)
|
||||
result1 := ro("test")(5)(ctx1)
|
||||
assert.Equal(t, O.Of("test:5"), result1)
|
||||
|
||||
// Test with disabled context
|
||||
ctx2 := context.Background()
|
||||
result2 := ro("test")(5)(ctx2)
|
||||
assert.Equal(t, O.None[string](), result2)
|
||||
}
|
||||
|
||||
func TestCurry3(t *testing.T) {
|
||||
// Function that combines three parameters with context
|
||||
combine := func(ctx context.Context, a string, b int, c bool) (string, bool) {
|
||||
if ctx.Value("enabled") == true && c {
|
||||
return a + ":" + string(rune('0'+b)), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
ro := Curry3(combine)
|
||||
|
||||
// Test with enabled context and true flag
|
||||
ctx1 := context.WithValue(context.Background(), "enabled", true)
|
||||
result1 := ro("test")(5)(true)(ctx1)
|
||||
assert.Equal(t, O.Of("test:5"), result1)
|
||||
|
||||
// Test with false flag
|
||||
result2 := ro("test")(5)(false)(ctx1)
|
||||
assert.Equal(t, O.None[string](), result2)
|
||||
}
|
||||
|
||||
func TestUncurry1(t *testing.T) {
|
||||
// Create a curried function
|
||||
curried := func(x int) ReaderOption[context.Context, int] {
|
||||
return Of[context.Context](x * 2)
|
||||
}
|
||||
|
||||
// Uncurry it
|
||||
uncurried := Uncurry1(curried)
|
||||
|
||||
// Test the uncurried function
|
||||
result, ok := uncurried(context.Background(), 21)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 42, result)
|
||||
}
|
||||
|
||||
func TestUncurry2(t *testing.T) {
|
||||
// Create a curried function
|
||||
curried := func(x int) func(y int) ReaderOption[context.Context, int] {
|
||||
return func(y int) ReaderOption[context.Context, int] {
|
||||
return Of[context.Context](x + y)
|
||||
}
|
||||
}
|
||||
|
||||
// Uncurry it
|
||||
uncurried := Uncurry2(curried)
|
||||
|
||||
// Test the uncurried function
|
||||
result, ok := uncurried(context.Background(), 10, 32)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 42, result)
|
||||
}
|
||||
|
||||
func TestUncurry3(t *testing.T) {
|
||||
// Create a curried function
|
||||
curried := func(x int) func(y int) func(z int) ReaderOption[context.Context, int] {
|
||||
return func(y int) func(z int) ReaderOption[context.Context, int] {
|
||||
return func(z int) ReaderOption[context.Context, int] {
|
||||
return Of[context.Context](x + y + z)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Uncurry it
|
||||
uncurried := Uncurry3(curried)
|
||||
|
||||
// Test the uncurried function
|
||||
result, ok := uncurried(context.Background(), 10, 20, 12)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 42, result)
|
||||
}
|
||||
98
v2/readeroption/from.go
Normal file
98
v2/readeroption/from.go
Normal file
@@ -0,0 +1,98 @@
|
||||
// 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 readeroption
|
||||
|
||||
import (
|
||||
G "github.com/IBM/fp-go/v2/readeroption/generic"
|
||||
)
|
||||
|
||||
// From functions convert Go functions that take a context as the first parameter
|
||||
// and return (value, bool) into ReaderOption functions with uncurried parameters.
|
||||
//
|
||||
// Unlike Curry functions which return fully curried functions, From functions
|
||||
// return functions that take multiple parameters at once (uncurried style).
|
||||
//
|
||||
// This follows the Go convention of passing context as the first parameter
|
||||
// (see https://pkg.go.dev/context), while providing a functional programming interface.
|
||||
//
|
||||
// The bool return value indicates success (true) or failure (false), which maps to
|
||||
// Some or None in the Option monad.
|
||||
|
||||
// From0 converts a function that takes only a context and returns (A, bool)
|
||||
// into a function that returns a ReaderOption[R, A].
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// getConfig := func(ctx context.Context) (Config, bool) {
|
||||
// cfg, ok := ctx.Value("config").(Config)
|
||||
// return cfg, ok
|
||||
// }
|
||||
// roFunc := readeroption.From0(getConfig)
|
||||
// ro := roFunc() // Returns a ReaderOption[context.Context, Config]
|
||||
// result := ro(ctx) // Returns option.Some(config) or option.None()
|
||||
func From0[R, A any](f func(R) (A, bool)) func() ReaderOption[R, A] {
|
||||
return G.From0[ReaderOption[R, A]](f)
|
||||
}
|
||||
|
||||
// From1 converts a function that takes a context and one argument, returning (A, bool),
|
||||
// into a function that takes one argument and returns a ReaderOption.
|
||||
//
|
||||
// This is equivalent to Curry1 but provided for consistency with the From naming convention.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// findUser := func(ctx context.Context, id int) (User, bool) {
|
||||
// // Query database using context
|
||||
// return user, found
|
||||
// }
|
||||
// roFunc := readeroption.From1(findUser)
|
||||
// ro := roFunc(123) // Returns a ReaderOption[context.Context, User]
|
||||
// result := ro(ctx) // Returns option.Some(user) or option.None()
|
||||
func From1[R, T1, A any](f func(R, T1) (A, bool)) Kleisli[R, T1, A] {
|
||||
return G.From1[ReaderOption[R, A]](f)
|
||||
}
|
||||
|
||||
// From2 converts a function that takes a context and two arguments, returning (A, bool),
|
||||
// into a function that takes two arguments (uncurried) and returns a ReaderOption.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// query := func(ctx context.Context, table string, id int) (Record, bool) {
|
||||
// // Query database using context
|
||||
// return record, found
|
||||
// }
|
||||
// roFunc := readeroption.From2(query)
|
||||
// ro := roFunc("users", 123) // Returns a ReaderOption[context.Context, Record]
|
||||
// result := ro(ctx) // Returns option.Some(record) or option.None()
|
||||
func From2[R, T1, T2, A any](f func(R, T1, T2) (A, bool)) func(T1, T2) ReaderOption[R, A] {
|
||||
return G.From2[ReaderOption[R, A]](f)
|
||||
}
|
||||
|
||||
// From3 converts a function that takes a context and three arguments, returning (A, bool),
|
||||
// into a function that takes three arguments (uncurried) and returns a ReaderOption.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// complexQuery := func(ctx context.Context, db string, table string, id int) (Record, bool) {
|
||||
// // Query database using context
|
||||
// return record, found
|
||||
// }
|
||||
// roFunc := readeroption.From3(complexQuery)
|
||||
// ro := roFunc("mydb", "users", 123) // Returns a ReaderOption[context.Context, Record]
|
||||
// result := ro(ctx) // Returns option.Some(record) or option.None()
|
||||
func From3[R, T1, T2, T3, A any](f func(R, T1, T2, T3) (A, bool)) func(T1, T2, T3) ReaderOption[R, A] {
|
||||
return G.From3[ReaderOption[R, A]](f)
|
||||
}
|
||||
112
v2/readeroption/from_test.go
Normal file
112
v2/readeroption/from_test.go
Normal file
@@ -0,0 +1,112 @@
|
||||
// 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 readeroption
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFrom0(t *testing.T) {
|
||||
// Function that returns a value from context
|
||||
getConfig := func(ctx context.Context) (string, bool) {
|
||||
if val := ctx.Value("config"); val != nil {
|
||||
return val.(string), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
roFunc := From0(getConfig)
|
||||
ro := roFunc()
|
||||
|
||||
// Test with value in context
|
||||
ctx1 := context.WithValue(context.Background(), "config", "test-config")
|
||||
result1 := ro(ctx1)
|
||||
assert.Equal(t, O.Of("test-config"), result1)
|
||||
|
||||
// Test without value in context
|
||||
ctx2 := context.Background()
|
||||
result2 := ro(ctx2)
|
||||
assert.Equal(t, O.None[string](), result2)
|
||||
}
|
||||
|
||||
func TestFrom1(t *testing.T) {
|
||||
// Function that looks up a value by key
|
||||
lookup := func(ctx context.Context, key string) (int, bool) {
|
||||
if val := ctx.Value(key); val != nil {
|
||||
return val.(int), true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
roFunc := From1(lookup)
|
||||
|
||||
// Test with value in context
|
||||
ctx1 := context.WithValue(context.Background(), "count", 42)
|
||||
result1 := roFunc("count")(ctx1)
|
||||
assert.Equal(t, O.Of(42), result1)
|
||||
|
||||
// Test without value in context
|
||||
ctx2 := context.Background()
|
||||
result2 := roFunc("count")(ctx2)
|
||||
assert.Equal(t, O.None[int](), result2)
|
||||
}
|
||||
|
||||
func TestFrom2(t *testing.T) {
|
||||
// Function that combines two parameters with context
|
||||
combine := func(ctx context.Context, a string, b int) (string, bool) {
|
||||
if ctx.Value("enabled") == true {
|
||||
return a + ":" + string(rune('0'+b)), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
roFunc := From2(combine)
|
||||
|
||||
// Test with enabled context
|
||||
ctx1 := context.WithValue(context.Background(), "enabled", true)
|
||||
result1 := roFunc("test", 5)(ctx1)
|
||||
assert.Equal(t, O.Of("test:5"), result1)
|
||||
|
||||
// Test with disabled context
|
||||
ctx2 := context.Background()
|
||||
result2 := roFunc("test", 5)(ctx2)
|
||||
assert.Equal(t, O.None[string](), result2)
|
||||
}
|
||||
|
||||
func TestFrom3(t *testing.T) {
|
||||
// Function that combines three parameters with context
|
||||
combine := func(ctx context.Context, a string, b int, c bool) (string, bool) {
|
||||
if ctx.Value("enabled") == true && c {
|
||||
return a + ":" + string(rune('0'+b)), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
roFunc := From3(combine)
|
||||
|
||||
// Test with enabled context and true flag
|
||||
ctx1 := context.WithValue(context.Background(), "enabled", true)
|
||||
result1 := roFunc("test", 5, true)(ctx1)
|
||||
assert.Equal(t, O.Of("test:5"), result1)
|
||||
|
||||
// Test with false flag
|
||||
result2 := roFunc("test", 5, false)(ctx1)
|
||||
assert.Equal(t, O.None[string](), result2)
|
||||
}
|
||||
60
v2/readeroption/generic/array.go
Normal file
60
v2/readeroption/generic/array.go
Normal file
@@ -0,0 +1,60 @@
|
||||
// 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 generic
|
||||
|
||||
import (
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
RA "github.com/IBM/fp-go/v2/internal/array"
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
)
|
||||
|
||||
// MonadTraverseArray transforms an array
|
||||
func MonadTraverseArray[GB ~func(E) O.Option[B], GBS ~func(E) O.Option[BBS], AAS ~[]A, BBS ~[]B, E, A, B any](ma AAS, f func(A) GB) GBS {
|
||||
return RA.MonadTraverse(
|
||||
Of[GBS, E, BBS],
|
||||
Map[GBS, func(E) O.Option[func(B) BBS], E, BBS, func(B) BBS],
|
||||
Ap[GB, GBS, func(E) O.Option[func(B) BBS], E, B, BBS],
|
||||
|
||||
ma, f,
|
||||
)
|
||||
}
|
||||
|
||||
// TraverseArray transforms an array
|
||||
func TraverseArray[GB ~func(E) O.Option[B], GBS ~func(E) O.Option[BBS], AAS ~[]A, BBS ~[]B, E, A, B any](f func(A) GB) func(AAS) GBS {
|
||||
return RA.Traverse[AAS](
|
||||
Of[GBS, E, BBS],
|
||||
Map[GBS, func(E) O.Option[func(B) BBS], E, BBS, func(B) BBS],
|
||||
Ap[GB, GBS, func(E) O.Option[func(B) BBS], E, B, BBS],
|
||||
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
// TraverseArrayWithIndex transforms an array
|
||||
func TraverseArrayWithIndex[GB ~func(E) O.Option[B], GBS ~func(E) O.Option[BBS], AAS ~[]A, BBS ~[]B, E, A, B any](f func(int, A) GB) func(AAS) GBS {
|
||||
return RA.TraverseWithIndex[AAS](
|
||||
Of[GBS, E, BBS],
|
||||
Map[GBS, func(E) O.Option[func(B) BBS], E, BBS, func(B) BBS],
|
||||
Ap[GB, GBS, func(E) O.Option[func(B) BBS], E, B, BBS],
|
||||
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
// SequenceArray converts a homogeneous sequence of either into an either of sequence
|
||||
func SequenceArray[GA ~func(E) O.Option[A], GAS ~func(E) O.Option[AAS], AAS ~[]A, GAAS ~[]GA, E, A any](ma GAAS) GAS {
|
||||
return MonadTraverseArray[GA, GAS](ma, F.Identity[GA])
|
||||
}
|
||||
184
v2/readeroption/generic/bind.go
Normal file
184
v2/readeroption/generic/bind.go
Normal file
@@ -0,0 +1,184 @@
|
||||
// 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 generic
|
||||
|
||||
import (
|
||||
A "github.com/IBM/fp-go/v2/internal/apply"
|
||||
C "github.com/IBM/fp-go/v2/internal/chain"
|
||||
F "github.com/IBM/fp-go/v2/internal/functor"
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
// Config Config
|
||||
// User User
|
||||
// }
|
||||
// type Env struct {
|
||||
// ConfigService ConfigService
|
||||
// UserService UserService
|
||||
// }
|
||||
// result := generic.Do[ReaderEither[Env, error, State], Env, error, State](State{})
|
||||
func Do[GS ~func(R) O.Option[S], R, S any](
|
||||
empty S,
|
||||
) GS {
|
||||
return Of[GS](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 shared 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 {
|
||||
// Config Config
|
||||
// User User
|
||||
// }
|
||||
// type Env struct {
|
||||
// ConfigService ConfigService
|
||||
// UserService UserService
|
||||
// }
|
||||
//
|
||||
// result := F.Pipe2(
|
||||
// generic.Do[ReaderEither[Env, error, State], Env, error, State](State{}),
|
||||
// generic.Bind[ReaderEither[Env, error, State], ReaderEither[Env, error, State], ReaderEither[Env, error, Config], Env, error, State, State, Config](
|
||||
// func(cfg Config) func(State) State {
|
||||
// return func(s State) State { s.Config = cfg; return s }
|
||||
// },
|
||||
// func(s State) ReaderEither[Env, error, Config] {
|
||||
// return func(env Env) either.Either[error, Config] {
|
||||
// return env.ConfigService.Load()
|
||||
// }
|
||||
// },
|
||||
// ),
|
||||
// generic.Bind[ReaderEither[Env, error, State], ReaderEither[Env, error, State], ReaderEither[Env, error, User], Env, error, State, State, User](
|
||||
// func(user User) func(State) State {
|
||||
// return func(s State) State { s.User = user; return s }
|
||||
// },
|
||||
// func(s State) ReaderEither[Env, error, User] {
|
||||
// // This can access s.Config from the previous step
|
||||
// return func(env Env) either.Either[error, User] {
|
||||
// return env.UserService.GetUserForConfig(s.Config)
|
||||
// }
|
||||
// },
|
||||
// ),
|
||||
// )
|
||||
func Bind[GS1 ~func(R) O.Option[S1], GS2 ~func(R) O.Option[S2], GT ~func(R) O.Option[T], R, S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
f func(S1) GT,
|
||||
) func(GS1) GS2 {
|
||||
return C.Bind(
|
||||
Chain[GS1, GS2, R, S1, S2],
|
||||
Map[GT, GS2, R, T, S2],
|
||||
setter,
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
// Let attaches the result of a computation to a context [S1] to produce a context [S2]
|
||||
func Let[GS1 ~func(R) O.Option[S1], GS2 ~func(R) O.Option[S2], R, S1, S2, T any](
|
||||
key func(T) func(S1) S2,
|
||||
f func(S1) T,
|
||||
) func(GS1) GS2 {
|
||||
return F.Let(
|
||||
Map[GS1, GS2, R, S1, S2],
|
||||
key,
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
// LetTo attaches the a value to a context [S1] to produce a context [S2]
|
||||
func LetTo[GS1 ~func(R) O.Option[S1], GS2 ~func(R) O.Option[S2], R, S1, S2, B any](
|
||||
key func(B) func(S1) S2,
|
||||
b B,
|
||||
) func(GS1) GS2 {
|
||||
return F.LetTo(
|
||||
Map[GS1, GS2, R, S1, S2],
|
||||
key,
|
||||
b,
|
||||
)
|
||||
}
|
||||
|
||||
// BindTo initializes a new state [S1] from a value [T]
|
||||
func BindTo[GS1 ~func(R) O.Option[S1], GT ~func(R) O.Option[T], R, S1, T any](
|
||||
setter func(T) S1,
|
||||
) func(GT) GS1 {
|
||||
return C.BindTo(
|
||||
Map[GT, GS1, R, T, S1],
|
||||
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 {
|
||||
// Config Config
|
||||
// User User
|
||||
// }
|
||||
// type Env struct {
|
||||
// ConfigService ConfigService
|
||||
// UserService UserService
|
||||
// }
|
||||
//
|
||||
// // These operations are independent and can be combined with ApS
|
||||
// getConfig := func(env Env) either.Either[error, Config] {
|
||||
// return env.ConfigService.Load()
|
||||
// }
|
||||
// getUser := func(env Env) either.Either[error, User] {
|
||||
// return env.UserService.GetCurrent()
|
||||
// }
|
||||
//
|
||||
// result := F.Pipe2(
|
||||
// generic.Do[ReaderEither[Env, error, State], Env, error, State](State{}),
|
||||
// generic.ApS[...](
|
||||
// func(cfg Config) func(State) State {
|
||||
// return func(s State) State { s.Config = cfg; return s }
|
||||
// },
|
||||
// getConfig,
|
||||
// ),
|
||||
// generic.ApS[...](
|
||||
// func(user User) func(State) State {
|
||||
// return func(s State) State { s.User = user; return s }
|
||||
// },
|
||||
// getUser,
|
||||
// ),
|
||||
// )
|
||||
func ApS[GS1 ~func(R) O.Option[S1], GS2 ~func(R) O.Option[S2], GT ~func(R) O.Option[T], R, S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
fa GT,
|
||||
) func(GS1) GS2 {
|
||||
return A.ApS(
|
||||
Ap[GT, GS2, func(R) O.Option[func(T) S2], R, T, S2],
|
||||
Map[GS1, func(R) O.Option[func(T) S2], R, S1, func(T) S2],
|
||||
setter,
|
||||
fa,
|
||||
)
|
||||
}
|
||||
52
v2/readeroption/generic/curry.go
Normal file
52
v2/readeroption/generic/curry.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// 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 generic
|
||||
|
||||
import (
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
G "github.com/IBM/fp-go/v2/reader/generic"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
func Curry0[GEA ~func(R) O.Option[A], R, A any](f func(R) (A, bool)) GEA {
|
||||
return G.Curry0[GEA](O.Optionize1(f))
|
||||
}
|
||||
|
||||
func Curry1[GEA ~func(R) O.Option[A], R, T1, A any](f func(R, T1) (A, bool)) func(T1) GEA {
|
||||
return G.Curry1[GEA](O.Optionize2(f))
|
||||
}
|
||||
|
||||
func Curry2[GEA ~func(R) O.Option[A], R, T1, T2, A any](f func(R, T1, T2) (A, bool)) func(T1) func(T2) GEA {
|
||||
return G.Curry2[GEA](O.Optionize3(f))
|
||||
}
|
||||
|
||||
func Curry3[GEA ~func(R) O.Option[A], R, T1, T2, T3, A any](f func(R, T1, T2, T3) (A, bool)) func(T1) func(T2) func(T3) GEA {
|
||||
return G.Curry3[GEA](O.Optionize4(f))
|
||||
}
|
||||
|
||||
func Uncurry1[GEA ~func(R) O.Option[A], R, T1, A any](f func(T1) GEA) func(R, T1) (A, bool) {
|
||||
return O.Unoptionize2(G.Uncurry1(f))
|
||||
}
|
||||
|
||||
func Uncurry2[GEA ~func(R) O.Option[A], R, T1, T2, A any](f func(T1) func(T2) GEA) func(R, T1, T2) (A, bool) {
|
||||
return O.Unoptionize3(G.Uncurry2(f))
|
||||
}
|
||||
|
||||
func Uncurry3[GEA ~func(R) O.Option[A], R, T1, T2, T3, A any](f func(T1) func(T2) func(T3) GEA) func(R, T1, T2, T3) (A, bool) {
|
||||
return O.Unoptionize4(G.Uncurry3(f))
|
||||
}
|
||||
40
v2/readeroption/generic/from.go
Normal file
40
v2/readeroption/generic/from.go
Normal file
@@ -0,0 +1,40 @@
|
||||
// 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 generic
|
||||
|
||||
import (
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
G "github.com/IBM/fp-go/v2/reader/generic"
|
||||
)
|
||||
|
||||
// these functions From 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
|
||||
|
||||
func From0[GEA ~func(R) O.Option[A], R, A any](f func(R) (A, bool)) func() GEA {
|
||||
return G.From0[GEA](O.Optionize1(f))
|
||||
}
|
||||
|
||||
func From1[GEA ~func(R) O.Option[A], R, T1, A any](f func(R, T1) (A, bool)) func(T1) GEA {
|
||||
return G.From1[GEA](O.Optionize2(f))
|
||||
}
|
||||
|
||||
func From2[GEA ~func(R) O.Option[A], R, T1, T2, A any](f func(R, T1, T2) (A, bool)) func(T1, T2) GEA {
|
||||
return G.From2[GEA](O.Optionize3(f))
|
||||
}
|
||||
|
||||
func From3[GEA ~func(R) O.Option[A], R, T1, T2, T3, A any](f func(R, T1, T2, T3) (A, bool)) func(T1, T2, T3) GEA {
|
||||
return G.From3[GEA](O.Optionize4(f))
|
||||
}
|
||||
129
v2/readeroption/generic/reader.go
Normal file
129
v2/readeroption/generic/reader.go
Normal file
@@ -0,0 +1,129 @@
|
||||
// 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 generic
|
||||
|
||||
import (
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
FO "github.com/IBM/fp-go/v2/internal/fromoption"
|
||||
FR "github.com/IBM/fp-go/v2/internal/fromreader"
|
||||
FC "github.com/IBM/fp-go/v2/internal/functor"
|
||||
"github.com/IBM/fp-go/v2/internal/optiont"
|
||||
"github.com/IBM/fp-go/v2/internal/readert"
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
R "github.com/IBM/fp-go/v2/reader/generic"
|
||||
)
|
||||
|
||||
//go:inline
|
||||
func MakeReaderOption[GEA ~func(E) O.Option[A], E, A any](f func(E) O.Option[A]) GEA {
|
||||
return f
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func FromOption[GEA ~func(E) O.Option[A], E, A any](e O.Option[A]) GEA {
|
||||
return R.Of[GEA](e)
|
||||
}
|
||||
|
||||
func SomeReader[GA ~func(E) A, GEA ~func(E) O.Option[A], E, A any](r GA) GEA {
|
||||
return optiont.SomeF(R.MonadMap[GA, GEA, E, A, O.Option[A]], r)
|
||||
}
|
||||
|
||||
func Some[GEA ~func(E) O.Option[A], E, A any](r A) GEA {
|
||||
return optiont.Of(R.Of[GEA, E, O.Option[A]], r)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func FromReader[GA ~func(E) A, GEA ~func(E) O.Option[A], E, A any](r GA) GEA {
|
||||
return SomeReader[GA, GEA](r)
|
||||
}
|
||||
|
||||
func MonadMap[GEA ~func(E) O.Option[A], GEB ~func(E) O.Option[B], E, A, B any](fa GEA, f func(A) B) GEB {
|
||||
return readert.MonadMap[GEA, GEB](O.MonadMap[A, B], fa, f)
|
||||
}
|
||||
|
||||
func Map[GEA ~func(E) O.Option[A], GEB ~func(E) O.Option[B], E, A, B any](f func(A) B) func(GEA) GEB {
|
||||
return readert.Map[GEA, GEB](O.Map[A, B], f)
|
||||
}
|
||||
|
||||
func MonadChain[GEA ~func(E) O.Option[A], GEB ~func(E) O.Option[B], E, A, B any](ma GEA, f func(A) GEB) GEB {
|
||||
return readert.MonadChain(O.MonadChain[A, B], ma, f)
|
||||
}
|
||||
|
||||
func Chain[GEA ~func(E) O.Option[A], GEB ~func(E) O.Option[B], E, A, B any](f func(A) GEB) func(GEA) GEB {
|
||||
return F.Bind2nd(MonadChain[GEA, GEB, E, A, B], f)
|
||||
}
|
||||
|
||||
func Of[GEA ~func(E) O.Option[A], E, A any](a A) GEA {
|
||||
return readert.MonadOf[GEA](O.Of[A], a)
|
||||
}
|
||||
|
||||
func MonadAp[GEA ~func(E) O.Option[A], GEB ~func(E) O.Option[B], GEFAB ~func(E) O.Option[func(A) B], E, A, B any](fab GEFAB, fa GEA) GEB {
|
||||
return readert.MonadAp[GEA, GEB, GEFAB, E, A](O.MonadAp[B, A], fab, fa)
|
||||
}
|
||||
|
||||
func Ap[GEA ~func(E) O.Option[A], GEB ~func(E) O.Option[B], GEFAB ~func(E) O.Option[func(A) B], E, A, B any](fa GEA) func(GEFAB) GEB {
|
||||
return F.Bind2nd(MonadAp[GEA, GEB, GEFAB, E, A, B], fa)
|
||||
}
|
||||
|
||||
func FromPredicate[GEA ~func(E) O.Option[A], E, A any](pred func(A) bool) func(A) GEA {
|
||||
return FO.FromPredicate(FromOption[GEA, E, A], pred)
|
||||
}
|
||||
|
||||
func Fold[GEA ~func(E) O.Option[A], GB ~func(E) B, E, A, B any](onNone func() GB, onRight func(A) GB) func(GEA) GB {
|
||||
return optiont.MatchE(R.Chain[GEA, GB, E, O.Option[A], B], onNone, onRight)
|
||||
}
|
||||
|
||||
func GetOrElse[GEA ~func(E) O.Option[A], GA ~func(E) A, E, A any](onNone func() GA) func(GEA) GA {
|
||||
return optiont.GetOrElse(R.Chain[GEA, GA, E, O.Option[A], A], onNone, R.Of[GA, E, A])
|
||||
}
|
||||
|
||||
func Ask[GEE ~func(E) O.Option[E], E, L any]() GEE {
|
||||
return FR.Ask(FromReader[func(E) E, GEE, E, E])()
|
||||
}
|
||||
|
||||
func Asks[GA ~func(E) A, GEA ~func(E) O.Option[A], E, A any](r GA) GEA {
|
||||
return FR.Asks(FromReader[GA, GEA, E, A])(r)
|
||||
}
|
||||
|
||||
func MonadChainOptionK[GEA ~func(E) O.Option[A], GEB ~func(E) O.Option[B], E, A, B any](ma GEA, f func(A) O.Option[B]) GEB {
|
||||
return FO.MonadChainOptionK(
|
||||
MonadChain[GEA, GEB, E, A, B],
|
||||
FromOption[GEB, E, B],
|
||||
ma,
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
func ChainOptionK[GEA ~func(E) O.Option[A], GEB ~func(E) O.Option[B], E, A, B any](f func(A) O.Option[B]) func(ma GEA) GEB {
|
||||
return F.Bind2nd(MonadChainOptionK[GEA, GEB, E, A, B], f)
|
||||
}
|
||||
|
||||
func Flatten[GEA ~func(E) O.Option[A], GGA ~func(E) O.Option[GEA], E, A any](mma GGA) GEA {
|
||||
return MonadChain(mma, F.Identity[GEA])
|
||||
}
|
||||
|
||||
// Local changes the value of the local context during the execution of the action `ma` (similar to `Contravariant`'s
|
||||
// `contramap`).
|
||||
func Local[GA1 ~func(R1) O.Option[A], GA2 ~func(R2) O.Option[A], R2, R1, E, A any](f func(R2) R1) func(GA1) GA2 {
|
||||
return R.Local[GA1, GA2](f)
|
||||
}
|
||||
|
||||
func MonadFlap[GEFAB ~func(E) O.Option[func(A) B], GEB ~func(E) O.Option[B], E, A, B any](fab GEFAB, a A) GEB {
|
||||
return FC.MonadFlap(MonadMap[GEFAB, GEB], fab, a)
|
||||
}
|
||||
|
||||
func Flap[GEFAB ~func(E) O.Option[func(A) B], GEB ~func(E) O.Option[B], E, A, B any](a A) func(GEFAB) GEB {
|
||||
return FC.Flap(Map[GEFAB, GEB], a)
|
||||
}
|
||||
80
v2/readeroption/generic/sequence.go
Normal file
80
v2/readeroption/generic/sequence.go
Normal file
@@ -0,0 +1,80 @@
|
||||
// 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 generic
|
||||
|
||||
import (
|
||||
"github.com/IBM/fp-go/v2/internal/apply"
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
T "github.com/IBM/fp-go/v2/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[
|
||||
GA ~func(E) O.Option[A],
|
||||
GTA ~func(E) O.Option[T.Tuple1[A]],
|
||||
E, A any](a GA) GTA {
|
||||
return apply.SequenceT1(
|
||||
Map[GA, GTA, E, A, T.Tuple1[A]],
|
||||
|
||||
a,
|
||||
)
|
||||
}
|
||||
|
||||
func SequenceT2[
|
||||
GA ~func(E) O.Option[A],
|
||||
GB ~func(E) O.Option[B],
|
||||
GTAB ~func(E) O.Option[T.Tuple2[A, B]],
|
||||
E, A, B any](a GA, b GB) GTAB {
|
||||
return apply.SequenceT2(
|
||||
Map[GA, func(E) O.Option[func(B) T.Tuple2[A, B]], E, A, func(B) T.Tuple2[A, B]],
|
||||
Ap[GB, GTAB, func(E) O.Option[func(B) T.Tuple2[A, B]], E, B, T.Tuple2[A, B]],
|
||||
|
||||
a, b,
|
||||
)
|
||||
}
|
||||
|
||||
func SequenceT3[
|
||||
GA ~func(E) O.Option[A],
|
||||
GB ~func(E) O.Option[B],
|
||||
GC ~func(E) O.Option[C],
|
||||
GTABC ~func(E) O.Option[T.Tuple3[A, B, C]],
|
||||
E, A, B, C any](a GA, b GB, c GC) GTABC {
|
||||
return apply.SequenceT3(
|
||||
Map[GA, func(E) O.Option[func(B) func(C) T.Tuple3[A, B, C]], E, A, func(B) func(C) T.Tuple3[A, B, C]],
|
||||
Ap[GB, func(E) O.Option[func(C) T.Tuple3[A, B, C]], func(E) O.Option[func(B) func(C) T.Tuple3[A, B, C]], E, B, func(C) T.Tuple3[A, B, C]],
|
||||
Ap[GC, GTABC, func(E) O.Option[func(C) T.Tuple3[A, B, C]], E, C, T.Tuple3[A, B, C]],
|
||||
|
||||
a, b, c,
|
||||
)
|
||||
}
|
||||
|
||||
func SequenceT4[
|
||||
GA ~func(E) O.Option[A],
|
||||
GB ~func(E) O.Option[B],
|
||||
GC ~func(E) O.Option[C],
|
||||
GD ~func(E) O.Option[D],
|
||||
GTABCD ~func(E) O.Option[T.Tuple4[A, B, C, D]],
|
||||
E, A, B, C, D any](a GA, b GB, c GC, d GD) GTABCD {
|
||||
return apply.SequenceT4(
|
||||
Map[GA, func(E) O.Option[func(B) func(C) func(D) T.Tuple4[A, B, C, D]], E, A, func(B) func(C) func(D) T.Tuple4[A, B, C, D]],
|
||||
Ap[GB, func(E) O.Option[func(C) func(D) T.Tuple4[A, B, C, D]], func(E) O.Option[func(B) func(C) func(D) T.Tuple4[A, B, C, D]], E, B, func(C) func(D) T.Tuple4[A, B, C, D]],
|
||||
Ap[GC, func(E) O.Option[func(D) T.Tuple4[A, B, C, D]], func(E) O.Option[func(C) func(D) T.Tuple4[A, B, C, D]], E, C, func(D) T.Tuple4[A, B, C, D]],
|
||||
Ap[GD, GTABCD, func(E) O.Option[func(D) T.Tuple4[A, B, C, D]], E, D, T.Tuple4[A, B, C, D]],
|
||||
|
||||
a, b, c, d,
|
||||
)
|
||||
}
|
||||
350
v2/readeroption/reader.go
Normal file
350
v2/readeroption/reader.go
Normal file
@@ -0,0 +1,350 @@
|
||||
// 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 readeroption
|
||||
|
||||
import (
|
||||
"github.com/IBM/fp-go/v2/function"
|
||||
"github.com/IBM/fp-go/v2/internal/fromoption"
|
||||
"github.com/IBM/fp-go/v2/internal/fromreader"
|
||||
"github.com/IBM/fp-go/v2/internal/functor"
|
||||
"github.com/IBM/fp-go/v2/internal/optiont"
|
||||
"github.com/IBM/fp-go/v2/internal/readert"
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
"github.com/IBM/fp-go/v2/reader"
|
||||
)
|
||||
|
||||
// FromOption lifts an Option[A] into a ReaderOption[E, A].
|
||||
// The resulting computation ignores the environment and returns the given option.
|
||||
//
|
||||
//go:inline
|
||||
func FromOption[E, A any](e Option[A]) ReaderOption[E, A] {
|
||||
return reader.Of[E](e)
|
||||
}
|
||||
|
||||
// Some wraps a value in a ReaderOption, representing a successful computation.
|
||||
// This is equivalent to Of but more explicit about the Option semantics.
|
||||
//
|
||||
//go:inline
|
||||
func Some[E, A any](r A) ReaderOption[E, A] {
|
||||
return optiont.Of(reader.Of[E, Option[A]], r)
|
||||
}
|
||||
|
||||
// FromReader lifts a Reader[E, A] into a ReaderOption[E, A].
|
||||
// The resulting computation always succeeds (returns Some).
|
||||
//
|
||||
//go:inline
|
||||
func FromReader[E, A any](r Reader[E, A]) ReaderOption[E, A] {
|
||||
return SomeReader(r)
|
||||
}
|
||||
|
||||
// SomeReader lifts a Reader[E, A] into a ReaderOption[E, A].
|
||||
// The resulting computation always succeeds (returns Some).
|
||||
//
|
||||
//go:inline
|
||||
func SomeReader[E, A any](r Reader[E, A]) ReaderOption[E, A] {
|
||||
return optiont.SomeF(reader.MonadMap[E, A, Option[A]], r)
|
||||
}
|
||||
|
||||
// MonadMap applies a function to the value inside a ReaderOption.
|
||||
// If the ReaderOption contains None, the function is not applied.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// ro := readeroption.Of[Config](42)
|
||||
// doubled := readeroption.MonadMap(ro, func(x int) int { return x * 2 })
|
||||
//
|
||||
//go:inline
|
||||
func MonadMap[E, A, B any](fa ReaderOption[E, A], f func(A) B) ReaderOption[E, B] {
|
||||
return readert.MonadMap[ReaderOption[E, A], ReaderOption[E, B]](O.MonadMap[A, B], fa, f)
|
||||
}
|
||||
|
||||
// Map returns a function that applies a transformation to the value inside a ReaderOption.
|
||||
// This is the curried version of MonadMap, useful for composition with F.Pipe.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// doubled := F.Pipe1(
|
||||
// readeroption.Of[Config](42),
|
||||
// readeroption.Map[Config](func(x int) int { return x * 2 }),
|
||||
// )
|
||||
//
|
||||
//go:inline
|
||||
func Map[E, A, B any](f func(A) B) Operator[E, A, B] {
|
||||
return readert.Map[ReaderOption[E, A], ReaderOption[E, B]](O.Map[A, B], f)
|
||||
}
|
||||
|
||||
// MonadChain sequences two ReaderOption computations, where the second depends on the result of the first.
|
||||
// If the first computation returns None, the second is not executed.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// findUser := func(id int) readeroption.ReaderOption[DB, User] { ... }
|
||||
// loadProfile := func(user User) readeroption.ReaderOption[DB, Profile] { ... }
|
||||
// result := readeroption.MonadChain(findUser(123), loadProfile)
|
||||
//
|
||||
//go:inline
|
||||
func MonadChain[E, A, B any](ma ReaderOption[E, A], f Kleisli[E, A, B]) ReaderOption[E, B] {
|
||||
return readert.MonadChain(O.MonadChain[A, B], ma, f)
|
||||
}
|
||||
|
||||
// Chain returns a function that sequences ReaderOption computations.
|
||||
// This is the curried version of MonadChain, useful for composition with F.Pipe.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// result := F.Pipe1(
|
||||
// findUser(123),
|
||||
// readeroption.Chain(loadProfile),
|
||||
// )
|
||||
//
|
||||
//go:inline
|
||||
func Chain[E, A, B any](f Kleisli[E, A, B]) Operator[E, A, B] {
|
||||
return readert.Chain[ReaderOption[E, A]](O.Chain[A, B], f)
|
||||
}
|
||||
|
||||
// Of wraps a value in a ReaderOption, representing a successful computation.
|
||||
// The resulting computation ignores the environment and returns Some(a).
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// ro := readeroption.Of[Config](42)
|
||||
// result := ro(config) // Returns option.Some(42)
|
||||
//
|
||||
//go:inline
|
||||
func Of[E, A any](a A) ReaderOption[E, A] {
|
||||
return readert.MonadOf[ReaderOption[E, A]](O.Of[A], a)
|
||||
}
|
||||
|
||||
// None creates a ReaderOption representing a failed computation.
|
||||
// The resulting computation ignores the environment and returns None.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// ro := readeroption.None[Config, int]()
|
||||
// result := ro(config) // Returns option.None[int]()
|
||||
//
|
||||
//go:inline
|
||||
func None[E, A any]() ReaderOption[E, A] {
|
||||
return reader.Of[E](O.None[A]())
|
||||
}
|
||||
|
||||
// MonadAp applies a function wrapped in a ReaderOption to a value wrapped in a ReaderOption.
|
||||
// Both computations are executed with the same environment.
|
||||
// If either computation returns None, the result is None.
|
||||
//
|
||||
//go:inline
|
||||
func MonadAp[E, A, B any](fab ReaderOption[E, func(A) B], fa ReaderOption[E, A]) ReaderOption[E, B] {
|
||||
return readert.MonadAp[ReaderOption[E, A], ReaderOption[E, B], ReaderOption[E, func(A) B], E, A](O.MonadAp[B, A], fab, fa)
|
||||
}
|
||||
|
||||
// Ap returns a function that applies a function wrapped in a ReaderOption to a value.
|
||||
// This is the curried version of MonadAp.
|
||||
//
|
||||
//go:inline
|
||||
func Ap[B, E, A any](fa ReaderOption[E, A]) Operator[E, func(A) B, B] {
|
||||
return readert.Ap[ReaderOption[E, A], ReaderOption[E, B], ReaderOption[E, func(A) B], E, A](O.Ap[B, A], fa)
|
||||
}
|
||||
|
||||
// FromPredicate creates a Kleisli arrow that filters a value based on a predicate.
|
||||
// If the predicate returns true, the value is wrapped in Some; otherwise, None is returned.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// isPositive := readeroption.FromPredicate[Config](func(x int) bool { return x > 0 })
|
||||
// result := F.Pipe1(
|
||||
// readeroption.Of[Config](42),
|
||||
// readeroption.Chain(isPositive),
|
||||
// )
|
||||
//
|
||||
//go:inline
|
||||
func FromPredicate[E, A any](pred func(A) bool) Kleisli[E, A, A] {
|
||||
return fromoption.FromPredicate(FromOption[E, A], pred)
|
||||
}
|
||||
|
||||
// Fold extracts the value from a ReaderOption by providing handlers for both cases.
|
||||
// The onNone handler is called if the computation returns None.
|
||||
// The onRight handler is called if the computation returns Some(a).
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// result := readeroption.Fold(
|
||||
// func() reader.Reader[Config, string] { return reader.Of[Config]("not found") },
|
||||
// func(user User) reader.Reader[Config, string] { return reader.Of[Config](user.Name) },
|
||||
// )(findUser(123))
|
||||
//
|
||||
//go:inline
|
||||
func Fold[E, A, B any](onNone Reader[E, B], onRight func(A) Reader[E, B]) func(ReaderOption[E, A]) Reader[E, B] {
|
||||
return optiont.MatchE(reader.Chain[E, Option[A], B], function.Constant(onNone), onRight)
|
||||
}
|
||||
|
||||
func MonadFold[E, A, B any](fa ReaderOption[E, A], onNone Reader[E, B], onRight func(A) Reader[E, B]) Reader[E, B] {
|
||||
return optiont.MonadMatchE(fa, reader.MonadChain[E, Option[A], B], function.Constant(onNone), onRight)
|
||||
}
|
||||
|
||||
// GetOrElse returns the value from a ReaderOption, or a default value if it's None.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// result := readeroption.GetOrElse(
|
||||
// func() reader.Reader[Config, User] { return reader.Of[Config](defaultUser) },
|
||||
// )(findUser(123))
|
||||
//
|
||||
//go:inline
|
||||
func GetOrElse[E, A any](onNone Reader[E, A]) func(ReaderOption[E, A]) Reader[E, A] {
|
||||
return optiont.GetOrElse(reader.Chain[E, Option[A], A], function.Constant(onNone), reader.Of[E, A])
|
||||
}
|
||||
|
||||
// Ask retrieves the current environment as a ReaderOption.
|
||||
// This always succeeds and returns Some(environment).
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// getConfig := readeroption.Ask[Config, any]()
|
||||
// result := getConfig(myConfig) // Returns option.Some(myConfig)
|
||||
//
|
||||
//go:inline
|
||||
func Ask[E, L any]() ReaderOption[E, E] {
|
||||
return fromreader.Ask(FromReader[E, E])()
|
||||
}
|
||||
|
||||
// Asks creates a ReaderOption that applies a function to the environment.
|
||||
// This always succeeds and returns Some(f(environment)).
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// getTimeout := readeroption.Asks(func(cfg Config) int { return cfg.Timeout })
|
||||
// result := getTimeout(myConfig) // Returns option.Some(myConfig.Timeout)
|
||||
//
|
||||
//go:inline
|
||||
func Asks[E, A any](r Reader[E, A]) ReaderOption[E, A] {
|
||||
return fromreader.Asks(FromReader[E, A])(r)
|
||||
}
|
||||
|
||||
// MonadChainOptionK chains a ReaderOption with a function that returns an Option.
|
||||
// This is useful for integrating functions that return Option directly.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// parseAge := func(s string) option.Option[int] { ... }
|
||||
// result := readeroption.MonadChainOptionK(
|
||||
// readeroption.Of[Config]("25"),
|
||||
// parseAge,
|
||||
// )
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainOptionK[E, A, B any](ma ReaderOption[E, A], f func(A) Option[B]) ReaderOption[E, B] {
|
||||
return fromoption.MonadChainOptionK(
|
||||
MonadChain[E, A, B],
|
||||
FromOption[E, B],
|
||||
ma,
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
// ChainOptionK returns a function that chains a ReaderOption with a function returning an Option.
|
||||
// This is the curried version of MonadChainOptionK.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// parseAge := func(s string) option.Option[int] { ... }
|
||||
// result := F.Pipe1(
|
||||
// readeroption.Of[Config]("25"),
|
||||
// readeroption.ChainOptionK[Config](parseAge),
|
||||
// )
|
||||
//
|
||||
//go:inline
|
||||
func ChainOptionK[E, A, B any](f func(A) Option[B]) Operator[E, A, B] {
|
||||
return fromoption.ChainOptionK(
|
||||
Chain[E, A, B],
|
||||
FromOption[E, B],
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
// Flatten removes one level of nesting from a ReaderOption.
|
||||
// Converts ReaderOption[E, ReaderOption[E, A]] to ReaderOption[E, A].
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// nested := readeroption.Of[Config](readeroption.Of[Config](42))
|
||||
// flattened := readeroption.Flatten(nested)
|
||||
//
|
||||
//go:inline
|
||||
func Flatten[E, A any](mma ReaderOption[E, ReaderOption[E, A]]) ReaderOption[E, A] {
|
||||
return MonadChain(mma, function.Identity[ReaderOption[E, A]])
|
||||
}
|
||||
|
||||
// Local changes the value of the local context during the execution of the action `ma` (similar to `Contravariant`'s
|
||||
// `contramap`).
|
||||
//
|
||||
// This allows you to transform the environment before passing it to a computation.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type GlobalConfig struct { DB DBConfig }
|
||||
// type DBConfig struct { Host string }
|
||||
//
|
||||
// // A computation that needs DBConfig
|
||||
// query := func(cfg DBConfig) option.Option[User] { ... }
|
||||
//
|
||||
// // Transform GlobalConfig to DBConfig
|
||||
// result := readeroption.Local(func(g GlobalConfig) DBConfig { return g.DB })(
|
||||
// readeroption.Asks(query),
|
||||
// )
|
||||
//
|
||||
//go:inline
|
||||
func Local[A, R2, R1 any](f func(R2) R1) func(ReaderOption[R1, A]) ReaderOption[R2, A] {
|
||||
return reader.Local[Option[A]](f)
|
||||
}
|
||||
|
||||
// Read applies a context to a reader to obtain its value.
|
||||
// This executes the ReaderOption computation with the given environment.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// ro := readeroption.Of[Config](42)
|
||||
// result := readeroption.Read[int](myConfig)(ro) // Returns option.Some(42)
|
||||
//
|
||||
//go:inline
|
||||
func Read[A, E any](e E) func(ReaderOption[E, A]) Option[A] {
|
||||
return reader.Read[Option[A]](e)
|
||||
}
|
||||
|
||||
// MonadFlap applies a value to a function wrapped in a ReaderOption.
|
||||
// This is the reverse of MonadAp.
|
||||
//
|
||||
//go:inline
|
||||
func MonadFlap[E, A, B any](fab ReaderOption[E, func(A) B], a A) ReaderOption[E, B] {
|
||||
return functor.MonadFlap(MonadMap[E, func(A) B, B], fab, a)
|
||||
}
|
||||
|
||||
// Flap returns a function that applies a value to a function wrapped in a ReaderOption.
|
||||
// This is the curried version of MonadFlap.
|
||||
//
|
||||
//go:inline
|
||||
func Flap[E, B, A any](a A) Operator[E, func(A) B, B] {
|
||||
return functor.Flap(Map[E, func(A) B, B], a)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func MonadAlt[E, A any](fa ReaderOption[E, A], that ReaderOption[E, A]) ReaderOption[E, A] {
|
||||
return MonadFold(fa, that, Of[E, A])
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func Alt[E, A any](that ReaderOption[E, A]) Operator[E, A, A] {
|
||||
return Fold(that, Of[E, A])
|
||||
}
|
||||
239
v2/readeroption/reader_test.go
Normal file
239
v2/readeroption/reader_test.go
Normal file
@@ -0,0 +1,239 @@
|
||||
// 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 readeroption
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
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/IBM/fp-go/v2/reader"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type MyContext string
|
||||
|
||||
const defaultContext MyContext = "default"
|
||||
|
||||
func TestMap(t *testing.T) {
|
||||
|
||||
g := F.Pipe1(
|
||||
Of[MyContext](1),
|
||||
Map[MyContext](utils.Double),
|
||||
)
|
||||
|
||||
assert.Equal(t, O.Of(2), g(defaultContext))
|
||||
|
||||
}
|
||||
|
||||
func TestAp(t *testing.T) {
|
||||
g := F.Pipe1(
|
||||
Of[MyContext](utils.Double),
|
||||
Ap[int](Of[MyContext](1)),
|
||||
)
|
||||
assert.Equal(t, O.Of(2), g(defaultContext))
|
||||
|
||||
}
|
||||
|
||||
func TestFlatten(t *testing.T) {
|
||||
|
||||
g := F.Pipe1(
|
||||
Of[MyContext](Of[MyContext]("a")),
|
||||
Flatten[MyContext, string],
|
||||
)
|
||||
|
||||
assert.Equal(t, O.Of("a"), g(defaultContext))
|
||||
}
|
||||
|
||||
func TestFromOption(t *testing.T) {
|
||||
// Test with Some
|
||||
opt1 := O.Of(42)
|
||||
ro1 := FromOption[MyContext](opt1)
|
||||
assert.Equal(t, O.Of(42), ro1(defaultContext))
|
||||
|
||||
// Test with None
|
||||
opt2 := O.None[int]()
|
||||
ro2 := FromOption[MyContext](opt2)
|
||||
assert.Equal(t, O.None[int](), ro2(defaultContext))
|
||||
}
|
||||
|
||||
func TestSome(t *testing.T) {
|
||||
ro := Some[MyContext](42)
|
||||
assert.Equal(t, O.Of(42), ro(defaultContext))
|
||||
}
|
||||
|
||||
func TestFromReader(t *testing.T) {
|
||||
reader := func(ctx MyContext) int {
|
||||
return 42
|
||||
}
|
||||
ro := FromReader(reader)
|
||||
assert.Equal(t, O.Of(42), ro(defaultContext))
|
||||
}
|
||||
|
||||
func TestOf(t *testing.T) {
|
||||
ro := Of[MyContext](42)
|
||||
assert.Equal(t, O.Of(42), ro(defaultContext))
|
||||
}
|
||||
|
||||
func TestNone(t *testing.T) {
|
||||
ro := None[MyContext, int]()
|
||||
assert.Equal(t, O.None[int](), ro(defaultContext))
|
||||
}
|
||||
|
||||
func TestChain(t *testing.T) {
|
||||
double := func(x int) ReaderOption[MyContext, int] {
|
||||
return Of[MyContext](x * 2)
|
||||
}
|
||||
|
||||
g := F.Pipe1(
|
||||
Of[MyContext](21),
|
||||
Chain(double),
|
||||
)
|
||||
|
||||
assert.Equal(t, O.Of(42), g(defaultContext))
|
||||
|
||||
// Test with None
|
||||
g2 := F.Pipe1(
|
||||
None[MyContext, int](),
|
||||
Chain(double),
|
||||
)
|
||||
assert.Equal(t, O.None[int](), g2(defaultContext))
|
||||
}
|
||||
|
||||
func TestFromPredicate(t *testing.T) {
|
||||
isPositive := FromPredicate[MyContext](func(x int) bool {
|
||||
return x > 0
|
||||
})
|
||||
|
||||
// Test with positive number
|
||||
g1 := F.Pipe1(
|
||||
Of[MyContext](42),
|
||||
Chain(isPositive),
|
||||
)
|
||||
assert.Equal(t, O.Of(42), g1(defaultContext))
|
||||
|
||||
// Test with negative number
|
||||
g2 := F.Pipe1(
|
||||
Of[MyContext](-5),
|
||||
Chain(isPositive),
|
||||
)
|
||||
assert.Equal(t, O.None[int](), g2(defaultContext))
|
||||
}
|
||||
|
||||
func TestFold(t *testing.T) {
|
||||
onNone := reader.Of[MyContext]("none")
|
||||
onSome := func(x int) Reader[MyContext, string] {
|
||||
return reader.Of[MyContext](fmt.Sprintf("%d", x))
|
||||
}
|
||||
|
||||
// Test with Some
|
||||
g1 := Fold(onNone, onSome)(Of[MyContext](42))
|
||||
assert.Equal(t, "42", g1(defaultContext))
|
||||
|
||||
// Test with None
|
||||
g2 := Fold(onNone, onSome)(None[MyContext, int]())
|
||||
assert.Equal(t, "none", g2(defaultContext))
|
||||
}
|
||||
|
||||
func TestGetOrElse(t *testing.T) {
|
||||
defaultValue := reader.Of[MyContext](0)
|
||||
|
||||
// Test with Some
|
||||
g1 := GetOrElse(defaultValue)(Of[MyContext](42))
|
||||
assert.Equal(t, 42, g1(defaultContext))
|
||||
|
||||
// Test with None
|
||||
g2 := GetOrElse(defaultValue)(None[MyContext, int]())
|
||||
assert.Equal(t, 0, g2(defaultContext))
|
||||
}
|
||||
|
||||
func TestAsk(t *testing.T) {
|
||||
ro := Ask[MyContext, any]()
|
||||
result := ro(defaultContext)
|
||||
assert.Equal(t, O.Of(defaultContext), result)
|
||||
}
|
||||
|
||||
func TestAsks(t *testing.T) {
|
||||
reader := func(ctx MyContext) string {
|
||||
return string(ctx)
|
||||
}
|
||||
ro := Asks(reader)
|
||||
result := ro(defaultContext)
|
||||
assert.Equal(t, O.Of("default"), result)
|
||||
}
|
||||
|
||||
func TestChainOptionK(t *testing.T) {
|
||||
parsePositive := func(x int) O.Option[int] {
|
||||
if x > 0 {
|
||||
return O.Of(x)
|
||||
}
|
||||
return O.None[int]()
|
||||
}
|
||||
|
||||
// Test with positive number
|
||||
g1 := F.Pipe1(
|
||||
Of[MyContext](42),
|
||||
ChainOptionK[MyContext](parsePositive),
|
||||
)
|
||||
assert.Equal(t, O.Of(42), g1(defaultContext))
|
||||
|
||||
// Test with negative number
|
||||
g2 := F.Pipe1(
|
||||
Of[MyContext](-5),
|
||||
ChainOptionK[MyContext](parsePositive),
|
||||
)
|
||||
assert.Equal(t, O.None[int](), g2(defaultContext))
|
||||
}
|
||||
|
||||
func TestLocal(t *testing.T) {
|
||||
type GlobalContext struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
// A computation that needs a string context
|
||||
ro := Asks(func(s string) string {
|
||||
return "Hello, " + s
|
||||
})
|
||||
|
||||
// Transform GlobalContext to string
|
||||
transformed := Local[string](func(g GlobalContext) string {
|
||||
return g.Value
|
||||
})(ro)
|
||||
|
||||
result := transformed(GlobalContext{Value: "World"})
|
||||
assert.Equal(t, O.Of("Hello, World"), result)
|
||||
}
|
||||
|
||||
func TestRead(t *testing.T) {
|
||||
ro := Of[MyContext](42)
|
||||
result := Read[int](defaultContext)(ro)
|
||||
assert.Equal(t, O.Of(42), result)
|
||||
}
|
||||
|
||||
func TestFlap(t *testing.T) {
|
||||
addFunc := func(x int) int {
|
||||
return x + 10
|
||||
}
|
||||
|
||||
g := F.Pipe1(
|
||||
Of[MyContext](addFunc),
|
||||
Flap[MyContext, int](32),
|
||||
)
|
||||
|
||||
assert.Equal(t, O.Of(42), g(defaultContext))
|
||||
}
|
||||
127
v2/readeroption/sequence.go
Normal file
127
v2/readeroption/sequence.go
Normal file
@@ -0,0 +1,127 @@
|
||||
// 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 readeroption
|
||||
|
||||
import (
|
||||
G "github.com/IBM/fp-go/v2/readeroption/generic"
|
||||
T "github.com/IBM/fp-go/v2/tuple"
|
||||
)
|
||||
|
||||
// SequenceT functions convert multiple ReaderOption values into a single ReaderOption containing a tuple.
|
||||
// If any input is None, the entire result is None.
|
||||
// Otherwise, returns Some containing a tuple of all the unwrapped values.
|
||||
//
|
||||
// These functions are useful for combining multiple independent ReaderOption computations
|
||||
// where you need to preserve the individual types of each result.
|
||||
|
||||
// SequenceT1 converts a single ReaderOption into a ReaderOption of a 1-tuple.
|
||||
// This is mainly useful for consistency with the other SequenceT functions.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type Config struct { ... }
|
||||
//
|
||||
// user := readeroption.Of[Config](User{Name: "Alice"})
|
||||
// result := readeroption.SequenceT1(user)
|
||||
// // result(config) returns option.Some(tuple.MakeTuple1(User{Name: "Alice"}))
|
||||
func SequenceT1[E, A any](a ReaderOption[E, A]) ReaderOption[E, T.Tuple1[A]] {
|
||||
return G.SequenceT1[
|
||||
ReaderOption[E, A],
|
||||
ReaderOption[E, T.Tuple1[A]],
|
||||
](a)
|
||||
}
|
||||
|
||||
// SequenceT2 combines two ReaderOption values into a ReaderOption of a 2-tuple.
|
||||
// If either input is None, the result is None.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type Config struct { ... }
|
||||
//
|
||||
// user := readeroption.Of[Config](User{Name: "Alice"})
|
||||
// count := readeroption.Of[Config](42)
|
||||
//
|
||||
// result := readeroption.SequenceT2(user, count)
|
||||
// // result(config) returns option.Some(tuple.MakeTuple2(User{Name: "Alice"}, 42))
|
||||
//
|
||||
// noneUser := readeroption.None[Config, User]()
|
||||
// result2 := readeroption.SequenceT2(noneUser, count)
|
||||
// // result2(config) returns option.None[tuple.Tuple2[User, int]]()
|
||||
func SequenceT2[E, A, B any](
|
||||
a ReaderOption[E, A],
|
||||
b ReaderOption[E, B],
|
||||
) ReaderOption[E, T.Tuple2[A, B]] {
|
||||
return G.SequenceT2[
|
||||
ReaderOption[E, A],
|
||||
ReaderOption[E, B],
|
||||
ReaderOption[E, T.Tuple2[A, B]],
|
||||
](a, b)
|
||||
}
|
||||
|
||||
// SequenceT3 combines three ReaderOption values into a ReaderOption of a 3-tuple.
|
||||
// If any input is None, the result is None.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type Config struct { ... }
|
||||
//
|
||||
// user := readeroption.Of[Config](User{Name: "Alice"})
|
||||
// count := readeroption.Of[Config](42)
|
||||
// active := readeroption.Of[Config](true)
|
||||
//
|
||||
// result := readeroption.SequenceT3(user, count, active)
|
||||
// // result(config) returns option.Some(tuple.MakeTuple3(User{Name: "Alice"}, 42, true))
|
||||
func SequenceT3[E, A, B, C any](
|
||||
a ReaderOption[E, A],
|
||||
b ReaderOption[E, B],
|
||||
c ReaderOption[E, C],
|
||||
) ReaderOption[E, T.Tuple3[A, B, C]] {
|
||||
return G.SequenceT3[
|
||||
ReaderOption[E, A],
|
||||
ReaderOption[E, B],
|
||||
ReaderOption[E, C],
|
||||
ReaderOption[E, T.Tuple3[A, B, C]],
|
||||
](a, b, c)
|
||||
}
|
||||
|
||||
// SequenceT4 combines four ReaderOption values into a ReaderOption of a 4-tuple.
|
||||
// If any input is None, the result is None.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type Config struct { ... }
|
||||
//
|
||||
// user := readeroption.Of[Config](User{Name: "Alice"})
|
||||
// count := readeroption.Of[Config](42)
|
||||
// active := readeroption.Of[Config](true)
|
||||
// score := readeroption.Of[Config](95.5)
|
||||
//
|
||||
// result := readeroption.SequenceT4(user, count, active, score)
|
||||
// // result(config) returns option.Some(tuple.MakeTuple4(User{Name: "Alice"}, 42, true, 95.5))
|
||||
func SequenceT4[E, A, B, C, D any](
|
||||
a ReaderOption[E, A],
|
||||
b ReaderOption[E, B],
|
||||
c ReaderOption[E, C],
|
||||
d ReaderOption[E, D],
|
||||
) ReaderOption[E, T.Tuple4[A, B, C, D]] {
|
||||
return G.SequenceT4[
|
||||
ReaderOption[E, A],
|
||||
ReaderOption[E, B],
|
||||
ReaderOption[E, C],
|
||||
ReaderOption[E, D],
|
||||
ReaderOption[E, T.Tuple4[A, B, C, D]],
|
||||
](a, b, c, d)
|
||||
}
|
||||
87
v2/readeroption/sequence_test.go
Normal file
87
v2/readeroption/sequence_test.go
Normal file
@@ -0,0 +1,87 @@
|
||||
// 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 readeroption
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
T "github.com/IBM/fp-go/v2/tuple"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSequenceT1(t *testing.T) {
|
||||
|
||||
t1 := Of[MyContext]("s1")
|
||||
e1 := None[MyContext, string]()
|
||||
|
||||
res1 := SequenceT1(t1)
|
||||
assert.Equal(t, O.Of(T.MakeTuple1("s1")), res1(defaultContext))
|
||||
|
||||
res2 := SequenceT1(e1)
|
||||
assert.Equal(t, O.None[T.Tuple1[string]](), res2(defaultContext))
|
||||
}
|
||||
|
||||
func TestSequenceT2(t *testing.T) {
|
||||
|
||||
t1 := Of[MyContext]("s1")
|
||||
e1 := None[MyContext, string]()
|
||||
t2 := Of[MyContext](2)
|
||||
e2 := None[MyContext, int]()
|
||||
|
||||
res1 := SequenceT2(t1, t2)
|
||||
assert.Equal(t, O.Of(T.MakeTuple2("s1", 2)), res1(defaultContext))
|
||||
|
||||
res2 := SequenceT2(e1, t2)
|
||||
assert.Equal(t, O.None[T.Tuple2[string, int]](), res2(defaultContext))
|
||||
|
||||
res3 := SequenceT2(t1, e2)
|
||||
assert.Equal(t, O.None[T.Tuple2[string, int]](), res3(defaultContext))
|
||||
}
|
||||
|
||||
func TestSequenceT3(t *testing.T) {
|
||||
|
||||
t1 := Of[MyContext]("s1")
|
||||
e1 := None[MyContext, string]()
|
||||
t2 := Of[MyContext](2)
|
||||
e2 := None[MyContext, int]()
|
||||
t3 := Of[MyContext](true)
|
||||
e3 := None[MyContext, bool]()
|
||||
|
||||
res1 := SequenceT3(t1, t2, t3)
|
||||
assert.Equal(t, O.Of(T.MakeTuple3("s1", 2, true)), res1(defaultContext))
|
||||
|
||||
res2 := SequenceT3(e1, t2, t3)
|
||||
assert.Equal(t, O.None[T.Tuple3[string, int, bool]](), res2(defaultContext))
|
||||
|
||||
res3 := SequenceT3(t1, e2, t3)
|
||||
assert.Equal(t, O.None[T.Tuple3[string, int, bool]](), res3(defaultContext))
|
||||
|
||||
res4 := SequenceT3(t1, t2, e3)
|
||||
assert.Equal(t, O.None[T.Tuple3[string, int, bool]](), res4(defaultContext))
|
||||
}
|
||||
|
||||
func TestSequenceT4(t *testing.T) {
|
||||
|
||||
t1 := Of[MyContext]("s1")
|
||||
t2 := Of[MyContext](2)
|
||||
t3 := Of[MyContext](true)
|
||||
t4 := Of[MyContext](1.0)
|
||||
|
||||
res := SequenceT4(t1, t2, t3, t4)
|
||||
|
||||
assert.Equal(t, O.Of(T.MakeTuple4("s1", 2, true, 1.0)), res(defaultContext))
|
||||
}
|
||||
92
v2/readeroption/types.go
Normal file
92
v2/readeroption/types.go
Normal file
@@ -0,0 +1,92 @@
|
||||
// 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 readeroption provides a monad transformer that combines the Reader and Option monads.
|
||||
//
|
||||
// ReaderOption[R, A] represents a computation that:
|
||||
// - Depends on a shared environment of type R (Reader monad)
|
||||
// - May fail to produce a value of type A (Option monad)
|
||||
//
|
||||
// This is useful for computations that need access to configuration, context, or dependencies
|
||||
// while also being able to represent the absence of a value without using errors.
|
||||
//
|
||||
// The ReaderOption monad is defined as: Reader[R, Option[A]]
|
||||
//
|
||||
// Key operations:
|
||||
// - Of: Wraps a value in a ReaderOption
|
||||
// - None: Creates a ReaderOption representing no value
|
||||
// - Map: Transforms the value inside a ReaderOption
|
||||
// - Chain: Sequences ReaderOption computations
|
||||
// - Ask/Asks: Accesses the environment
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type Config struct {
|
||||
// DatabaseURL string
|
||||
// Timeout int
|
||||
// }
|
||||
//
|
||||
// // A computation that may or may not find a user
|
||||
// func findUser(id int) readeroption.ReaderOption[Config, User] {
|
||||
// return readeroption.Asks(func(cfg Config) option.Option[User] {
|
||||
// // Use cfg.DatabaseURL to query database
|
||||
// // Return Some(user) if found, None() if not found
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// // Chain multiple operations
|
||||
// result := F.Pipe2(
|
||||
// findUser(123),
|
||||
// readeroption.Chain(func(user User) readeroption.ReaderOption[Config, Profile] {
|
||||
// return loadProfile(user.ProfileID)
|
||||
// }),
|
||||
// readeroption.Map(func(profile Profile) string {
|
||||
// return profile.DisplayName
|
||||
// }),
|
||||
// )
|
||||
//
|
||||
// // Execute with config
|
||||
// config := Config{DatabaseURL: "localhost:5432", Timeout: 30}
|
||||
// displayName := result(config) // Returns Option[string]
|
||||
package readeroption
|
||||
|
||||
import (
|
||||
"github.com/IBM/fp-go/v2/lazy"
|
||||
"github.com/IBM/fp-go/v2/option"
|
||||
"github.com/IBM/fp-go/v2/reader"
|
||||
)
|
||||
|
||||
type (
|
||||
// Lazy represents a deferred computation
|
||||
Lazy[A any] = lazy.Lazy[A]
|
||||
|
||||
// Option represents an optional value
|
||||
Option[A any] = option.Option[A]
|
||||
|
||||
// Reader represents a computation that depends on an environment R and produces a value A
|
||||
Reader[R, A any] = reader.Reader[R, A]
|
||||
|
||||
// ReaderOption represents a computation that depends on an environment R and may produce a value A.
|
||||
// It combines the Reader monad (for dependency injection) with the Option monad (for optional values).
|
||||
ReaderOption[R, A any] = Reader[R, Option[A]]
|
||||
|
||||
// Kleisli represents a function that takes a value A and returns a ReaderOption[R, B].
|
||||
// This is the type of functions used with Chain/Bind operations.
|
||||
Kleisli[R, A, B any] = Reader[A, ReaderOption[R, B]]
|
||||
|
||||
// Operator represents a function that transforms one ReaderOption into another.
|
||||
// This is commonly used for lifting functions into the ReaderOption context.
|
||||
Operator[R, A, B any] = Reader[ReaderOption[R, A], ReaderOption[R, B]]
|
||||
)
|
||||
@@ -41,6 +41,9 @@ var (
|
||||
|
||||
// Includes returns a predicate that tests for the existence of the search string
|
||||
Includes = F.Curry2(F.Swap(strings.Contains))
|
||||
|
||||
// HasPrefix returns a predicate that checks if the prefis is included in the string
|
||||
HasPrefix = F.Curry2(F.Swap(strings.HasPrefix))
|
||||
)
|
||||
|
||||
func Eq(left string, right string) bool {
|
||||
|
||||
@@ -48,3 +48,9 @@ func TestIncludes(t *testing.T) {
|
||||
assert.False(t, Includes("bab")("a"))
|
||||
assert.False(t, Includes("b")("a"))
|
||||
}
|
||||
|
||||
func TestHasPrefix(t *testing.T) {
|
||||
assert.True(t, HasPrefix("prefix")("prefixbab"))
|
||||
assert.False(t, HasPrefix("bab")("a"))
|
||||
assert.False(t, HasPrefix("b")("a"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user