mirror of
https://github.com/IBM/fp-go.git
synced 2025-12-09 23:11:40 +02:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2dbce6e8b | ||
|
|
6f7ec0768d | ||
|
|
ca813b673c | ||
|
|
af271e7d10 | ||
|
|
567315a31c | ||
|
|
311ed55f06 | ||
|
|
23333ce52c | ||
|
|
eb7fc9f77b | ||
|
|
fd0550e71b | ||
|
|
13063bbd88 | ||
|
|
4f8a557072 |
240
v2/README.md
240
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).
|
||||
|
||||
@@ -70,6 +197,36 @@ pair := MakePair(1, "hello")
|
||||
result := Map(func(s string) string { return s + "!" })(pair) // Pair(1, "hello!")
|
||||
```
|
||||
|
||||
#### 4. Endomorphism Compose Semantics
|
||||
|
||||
The `Compose` function for endomorphisms now follows **mathematical function composition** (right-to-left execution), aligning with standard functional programming conventions.
|
||||
|
||||
**V1:**
|
||||
```go
|
||||
// Compose executed left-to-right
|
||||
double := func(x int) int { return x * 2 }
|
||||
increment := func(x int) int { return x + 1 }
|
||||
composed := Compose(double, increment)
|
||||
result := composed(5) // (5 * 2) + 1 = 11
|
||||
```
|
||||
|
||||
**V2:**
|
||||
```go
|
||||
// Compose executes RIGHT-TO-LEFT (mathematical composition)
|
||||
double := func(x int) int { return x * 2 }
|
||||
increment := func(x int) int { return x + 1 }
|
||||
composed := Compose(double, increment)
|
||||
result := composed(5) // (5 + 1) * 2 = 12
|
||||
|
||||
// Use MonadChain for LEFT-TO-RIGHT execution
|
||||
chained := MonadChain(double, increment)
|
||||
result2 := chained(5) // (5 * 2) + 1 = 11
|
||||
```
|
||||
|
||||
**Key Difference:**
|
||||
- `Compose(f, g)` now means `f ∘ g`, which applies `g` first, then `f` (right-to-left)
|
||||
- `MonadChain(f, g)` applies `f` first, then `g` (left-to-right)
|
||||
|
||||
## ✨ Key Improvements
|
||||
|
||||
### 1. Simplified Type Declarations
|
||||
@@ -91,16 +248,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 +387,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 +428,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 +473,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]
|
||||
)
|
||||
@@ -15,14 +15,163 @@
|
||||
|
||||
package bytes
|
||||
|
||||
// Empty returns an empty byte slice.
|
||||
//
|
||||
// This function returns the identity element for the byte slice Monoid,
|
||||
// which is an empty byte slice. It's useful as a starting point for
|
||||
// building byte slices or as a default value.
|
||||
//
|
||||
// Returns:
|
||||
// - An empty byte slice ([]byte{})
|
||||
//
|
||||
// Properties:
|
||||
// - Empty() is the identity element for Monoid.Concat
|
||||
// - Monoid.Concat(Empty(), x) == x
|
||||
// - Monoid.Concat(x, Empty()) == x
|
||||
//
|
||||
// Example - Basic usage:
|
||||
//
|
||||
// empty := Empty()
|
||||
// fmt.Println(len(empty)) // 0
|
||||
//
|
||||
// Example - As identity element:
|
||||
//
|
||||
// data := []byte("hello")
|
||||
// result1 := Monoid.Concat(Empty(), data) // []byte("hello")
|
||||
// result2 := Monoid.Concat(data, Empty()) // []byte("hello")
|
||||
//
|
||||
// Example - Building byte slices:
|
||||
//
|
||||
// // Start with empty and build up
|
||||
// buffer := Empty()
|
||||
// buffer = Monoid.Concat(buffer, []byte("Hello"))
|
||||
// buffer = Monoid.Concat(buffer, []byte(" "))
|
||||
// buffer = Monoid.Concat(buffer, []byte("World"))
|
||||
// // buffer: []byte("Hello World")
|
||||
//
|
||||
// See also:
|
||||
// - Monoid.Empty(): Alternative way to get empty byte slice
|
||||
// - ConcatAll(): For concatenating multiple byte slices
|
||||
func Empty() []byte {
|
||||
return Monoid.Empty()
|
||||
}
|
||||
|
||||
// ToString converts a byte slice to a string.
|
||||
//
|
||||
// This function performs a direct conversion from []byte to string.
|
||||
// The conversion creates a new string with a copy of the byte data.
|
||||
//
|
||||
// Parameters:
|
||||
// - a: The byte slice to convert
|
||||
//
|
||||
// Returns:
|
||||
// - A string containing the same data as the byte slice
|
||||
//
|
||||
// Performance Note:
|
||||
//
|
||||
// This conversion allocates a new string. For performance-critical code
|
||||
// that needs to avoid allocations, consider using unsafe.String (Go 1.20+)
|
||||
// or working directly with byte slices.
|
||||
//
|
||||
// Example - Basic conversion:
|
||||
//
|
||||
// bytes := []byte("hello")
|
||||
// str := ToString(bytes)
|
||||
// fmt.Println(str) // "hello"
|
||||
//
|
||||
// Example - Converting binary data:
|
||||
//
|
||||
// // ASCII codes for "Hello"
|
||||
// data := []byte{0x48, 0x65, 0x6c, 0x6c, 0x6f}
|
||||
// str := ToString(data)
|
||||
// fmt.Println(str) // "Hello"
|
||||
//
|
||||
// Example - Empty byte slice:
|
||||
//
|
||||
// empty := Empty()
|
||||
// str := ToString(empty)
|
||||
// fmt.Println(str == "") // true
|
||||
//
|
||||
// Example - UTF-8 encoded text:
|
||||
//
|
||||
// utf8Bytes := []byte("Hello, 世界")
|
||||
// str := ToString(utf8Bytes)
|
||||
// fmt.Println(str) // "Hello, 世界"
|
||||
//
|
||||
// Example - Round-trip conversion:
|
||||
//
|
||||
// original := "test string"
|
||||
// bytes := []byte(original)
|
||||
// result := ToString(bytes)
|
||||
// fmt.Println(original == result) // true
|
||||
//
|
||||
// See also:
|
||||
// - []byte(string): For converting string to byte slice
|
||||
// - Size(): For getting the length of a byte slice
|
||||
func ToString(a []byte) string {
|
||||
return string(a)
|
||||
}
|
||||
|
||||
// Size returns the number of bytes in a byte slice.
|
||||
//
|
||||
// This function returns the length of the byte slice, which is the number
|
||||
// of bytes it contains. This is equivalent to len(as) but provided as a
|
||||
// named function for use in functional composition.
|
||||
//
|
||||
// Parameters:
|
||||
// - as: The byte slice to measure
|
||||
//
|
||||
// Returns:
|
||||
// - The number of bytes in the slice
|
||||
//
|
||||
// Example - Basic usage:
|
||||
//
|
||||
// data := []byte("hello")
|
||||
// size := Size(data)
|
||||
// fmt.Println(size) // 5
|
||||
//
|
||||
// Example - Empty slice:
|
||||
//
|
||||
// empty := Empty()
|
||||
// size := Size(empty)
|
||||
// fmt.Println(size) // 0
|
||||
//
|
||||
// Example - Binary data:
|
||||
//
|
||||
// binary := []byte{0x01, 0x02, 0x03, 0x04}
|
||||
// size := Size(binary)
|
||||
// fmt.Println(size) // 4
|
||||
//
|
||||
// Example - UTF-8 encoded text:
|
||||
//
|
||||
// // Note: Size returns byte count, not character count
|
||||
// utf8 := []byte("Hello, 世界")
|
||||
// byteCount := Size(utf8)
|
||||
// fmt.Println(byteCount) // 13 (not 9 characters)
|
||||
//
|
||||
// Example - Using in functional composition:
|
||||
//
|
||||
// import "github.com/IBM/fp-go/v2/array"
|
||||
//
|
||||
// slices := [][]byte{
|
||||
// []byte("a"),
|
||||
// []byte("bb"),
|
||||
// []byte("ccc"),
|
||||
// }
|
||||
//
|
||||
// // Map to get sizes
|
||||
// sizes := array.Map(Size)(slices)
|
||||
// // sizes: []int{1, 2, 3}
|
||||
//
|
||||
// Example - Checking if slice is empty:
|
||||
//
|
||||
// data := []byte("test")
|
||||
// isEmpty := Size(data) == 0
|
||||
// fmt.Println(isEmpty) // false
|
||||
//
|
||||
// See also:
|
||||
// - len(): Built-in function for getting slice length
|
||||
// - ToString(): For converting byte slice to string
|
||||
func Size(as []byte) int {
|
||||
return len(as)
|
||||
}
|
||||
|
||||
@@ -187,6 +187,299 @@ func TestOrd(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestOrdProperties tests mathematical properties of Ord
|
||||
func TestOrdProperties(t *testing.T) {
|
||||
t.Run("reflexivity: x == x", func(t *testing.T) {
|
||||
testCases := [][]byte{
|
||||
[]byte{},
|
||||
[]byte("a"),
|
||||
[]byte("test"),
|
||||
[]byte{0x01, 0x02, 0x03},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
assert.Equal(t, 0, Ord.Compare(tc, tc),
|
||||
"Compare(%v, %v) should be 0", tc, tc)
|
||||
assert.True(t, Ord.Equals(tc, tc),
|
||||
"Equals(%v, %v) should be true", tc, tc)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("antisymmetry: if x <= y and y <= x then x == y", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
a, b []byte
|
||||
}{
|
||||
{[]byte("abc"), []byte("abc")},
|
||||
{[]byte{}, []byte{}},
|
||||
{[]byte{0x01}, []byte{0x01}},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
cmp1 := Ord.Compare(tc.a, tc.b)
|
||||
cmp2 := Ord.Compare(tc.b, tc.a)
|
||||
|
||||
if cmp1 <= 0 && cmp2 <= 0 {
|
||||
assert.True(t, Ord.Equals(tc.a, tc.b),
|
||||
"If %v <= %v and %v <= %v, they should be equal", tc.a, tc.b, tc.b, tc.a)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("transitivity: if x <= y and y <= z then x <= z", func(t *testing.T) {
|
||||
x := []byte("a")
|
||||
y := []byte("b")
|
||||
z := []byte("c")
|
||||
|
||||
cmpXY := Ord.Compare(x, y)
|
||||
cmpYZ := Ord.Compare(y, z)
|
||||
cmpXZ := Ord.Compare(x, z)
|
||||
|
||||
if cmpXY <= 0 && cmpYZ <= 0 {
|
||||
assert.True(t, cmpXZ <= 0,
|
||||
"If %v <= %v and %v <= %v, then %v <= %v", x, y, y, z, x, z)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("totality: either x <= y or y <= x", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
a, b []byte
|
||||
}{
|
||||
{[]byte("abc"), []byte("abd")},
|
||||
{[]byte("xyz"), []byte("abc")},
|
||||
{[]byte{}, []byte("a")},
|
||||
{[]byte{0x01}, []byte{0x02}},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
cmp1 := Ord.Compare(tc.a, tc.b)
|
||||
cmp2 := Ord.Compare(tc.b, tc.a)
|
||||
|
||||
assert.True(t, cmp1 <= 0 || cmp2 <= 0,
|
||||
"Either %v <= %v or %v <= %v must be true", tc.a, tc.b, tc.b, tc.a)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestEdgeCases tests edge cases and boundary conditions
|
||||
func TestEdgeCases(t *testing.T) {
|
||||
t.Run("very large byte slices", func(t *testing.T) {
|
||||
large := make([]byte, 1000000)
|
||||
for i := range large {
|
||||
large[i] = byte(i % 256)
|
||||
}
|
||||
|
||||
size := Size(large)
|
||||
assert.Equal(t, 1000000, size)
|
||||
|
||||
str := ToString(large)
|
||||
assert.Equal(t, 1000000, len(str))
|
||||
})
|
||||
|
||||
t.Run("concatenating many slices", func(t *testing.T) {
|
||||
slices := make([][]byte, 100)
|
||||
for i := range slices {
|
||||
slices[i] = []byte{byte(i)}
|
||||
}
|
||||
|
||||
result := ConcatAll(slices...)
|
||||
assert.Equal(t, 100, Size(result))
|
||||
})
|
||||
|
||||
t.Run("null bytes in slice", func(t *testing.T) {
|
||||
data := []byte{0x00, 0x01, 0x00, 0x02}
|
||||
size := Size(data)
|
||||
assert.Equal(t, 4, size)
|
||||
|
||||
str := ToString(data)
|
||||
assert.Equal(t, 4, len(str))
|
||||
})
|
||||
|
||||
t.Run("comparing slices with null bytes", func(t *testing.T) {
|
||||
a := []byte{0x00, 0x01}
|
||||
b := []byte{0x00, 0x02}
|
||||
assert.Equal(t, -1, Ord.Compare(a, b))
|
||||
})
|
||||
}
|
||||
|
||||
// TestMonoidConcatPerformance tests concatenation performance characteristics
|
||||
func TestMonoidConcatPerformance(t *testing.T) {
|
||||
t.Run("ConcatAll vs repeated Concat", func(t *testing.T) {
|
||||
slices := [][]byte{
|
||||
[]byte("a"),
|
||||
[]byte("b"),
|
||||
[]byte("c"),
|
||||
[]byte("d"),
|
||||
[]byte("e"),
|
||||
}
|
||||
|
||||
// Using ConcatAll
|
||||
result1 := ConcatAll(slices...)
|
||||
|
||||
// Using repeated Concat
|
||||
result2 := Monoid.Empty()
|
||||
for _, s := range slices {
|
||||
result2 = Monoid.Concat(result2, s)
|
||||
}
|
||||
|
||||
assert.Equal(t, result1, result2)
|
||||
assert.Equal(t, []byte("abcde"), result1)
|
||||
})
|
||||
}
|
||||
|
||||
// TestRoundTrip tests round-trip conversions
|
||||
func TestRoundTrip(t *testing.T) {
|
||||
t.Run("string to bytes to string", func(t *testing.T) {
|
||||
original := "Hello, World! 世界"
|
||||
bytes := []byte(original)
|
||||
result := ToString(bytes)
|
||||
assert.Equal(t, original, result)
|
||||
})
|
||||
|
||||
t.Run("bytes to string to bytes", func(t *testing.T) {
|
||||
original := []byte{0x48, 0x65, 0x6c, 0x6c, 0x6f}
|
||||
str := ToString(original)
|
||||
result := []byte(str)
|
||||
assert.Equal(t, original, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestConcatAllVariadic tests ConcatAll with various argument counts
|
||||
func TestConcatAllVariadic(t *testing.T) {
|
||||
t.Run("zero arguments", func(t *testing.T) {
|
||||
result := ConcatAll()
|
||||
assert.Equal(t, []byte{}, result)
|
||||
})
|
||||
|
||||
t.Run("one argument", func(t *testing.T) {
|
||||
result := ConcatAll([]byte("test"))
|
||||
assert.Equal(t, []byte("test"), result)
|
||||
})
|
||||
|
||||
t.Run("two arguments", func(t *testing.T) {
|
||||
result := ConcatAll([]byte("hello"), []byte("world"))
|
||||
assert.Equal(t, []byte("helloworld"), result)
|
||||
})
|
||||
|
||||
t.Run("many arguments", func(t *testing.T) {
|
||||
result := ConcatAll(
|
||||
[]byte("a"),
|
||||
[]byte("b"),
|
||||
[]byte("c"),
|
||||
[]byte("d"),
|
||||
[]byte("e"),
|
||||
[]byte("f"),
|
||||
[]byte("g"),
|
||||
[]byte("h"),
|
||||
[]byte("i"),
|
||||
[]byte("j"),
|
||||
)
|
||||
assert.Equal(t, []byte("abcdefghij"), result)
|
||||
})
|
||||
}
|
||||
|
||||
// Benchmark tests
|
||||
func BenchmarkToString(b *testing.B) {
|
||||
data := []byte("Hello, World!")
|
||||
|
||||
b.Run("small", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = ToString(data)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("large", func(b *testing.B) {
|
||||
large := make([]byte, 10000)
|
||||
for i := range large {
|
||||
large[i] = byte(i % 256)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = ToString(large)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkSize(b *testing.B) {
|
||||
data := []byte("Hello, World!")
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = Size(data)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMonoidConcat(b *testing.B) {
|
||||
a := []byte("Hello")
|
||||
c := []byte(" World")
|
||||
|
||||
b.Run("small slices", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = Monoid.Concat(a, c)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("large slices", func(b *testing.B) {
|
||||
large1 := make([]byte, 10000)
|
||||
large2 := make([]byte, 10000)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = Monoid.Concat(large1, large2)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkConcatAll(b *testing.B) {
|
||||
slices := [][]byte{
|
||||
[]byte("Hello"),
|
||||
[]byte(" "),
|
||||
[]byte("World"),
|
||||
[]byte("!"),
|
||||
}
|
||||
|
||||
b.Run("few slices", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = ConcatAll(slices...)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("many slices", func(b *testing.B) {
|
||||
many := make([][]byte, 100)
|
||||
for i := range many {
|
||||
many[i] = []byte{byte(i)}
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = ConcatAll(many...)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkOrdCompare(b *testing.B) {
|
||||
a := []byte("abc")
|
||||
c := []byte("abd")
|
||||
|
||||
b.Run("equal", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = Ord.Compare(a, a)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("different", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = Ord.Compare(a, c)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("large slices", func(b *testing.B) {
|
||||
large1 := make([]byte, 10000)
|
||||
large2 := make([]byte, 10000)
|
||||
large2[9999] = 1
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = Ord.Compare(large1, large2)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Example tests
|
||||
func ExampleEmpty() {
|
||||
empty := Empty()
|
||||
@@ -219,3 +512,17 @@ func ExampleConcatAll() {
|
||||
|
||||
// Output:
|
||||
}
|
||||
|
||||
func ExampleMonoid_concat() {
|
||||
result := Monoid.Concat([]byte("Hello"), []byte(" World"))
|
||||
println(string(result)) // Hello World
|
||||
|
||||
// Output:
|
||||
}
|
||||
|
||||
func ExampleOrd_compare() {
|
||||
cmp := Ord.Compare([]byte("abc"), []byte("abd"))
|
||||
println(cmp) // -1 (abc < abd)
|
||||
|
||||
// Output:
|
||||
}
|
||||
|
||||
4
v2/bytes/coverage.out
Normal file
4
v2/bytes/coverage.out
Normal file
@@ -0,0 +1,4 @@
|
||||
mode: set
|
||||
github.com/IBM/fp-go/v2/bytes/bytes.go:55.21,57.2 1 1
|
||||
github.com/IBM/fp-go/v2/bytes/bytes.go:111.32,113.2 1 1
|
||||
github.com/IBM/fp-go/v2/bytes/bytes.go:175.26,177.2 1 1
|
||||
@@ -23,12 +23,219 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
// monoid for byte arrays
|
||||
// Monoid is the Monoid instance for byte slices.
|
||||
//
|
||||
// This Monoid combines byte slices through concatenation, with an empty
|
||||
// byte slice as the identity element. It satisfies the monoid laws:
|
||||
//
|
||||
// Identity laws:
|
||||
// - Monoid.Concat(Monoid.Empty(), x) == x (left identity)
|
||||
// - Monoid.Concat(x, Monoid.Empty()) == x (right identity)
|
||||
//
|
||||
// Associativity law:
|
||||
// - Monoid.Concat(Monoid.Concat(a, b), c) == Monoid.Concat(a, Monoid.Concat(b, c))
|
||||
//
|
||||
// Operations:
|
||||
// - Empty(): Returns an empty byte slice []byte{}
|
||||
// - Concat(a, b []byte): Concatenates two byte slices
|
||||
//
|
||||
// Example - Basic concatenation:
|
||||
//
|
||||
// result := Monoid.Concat([]byte("Hello"), []byte(" World"))
|
||||
// // result: []byte("Hello World")
|
||||
//
|
||||
// Example - Identity element:
|
||||
//
|
||||
// empty := Monoid.Empty()
|
||||
// data := []byte("test")
|
||||
// result1 := Monoid.Concat(empty, data) // []byte("test")
|
||||
// result2 := Monoid.Concat(data, empty) // []byte("test")
|
||||
//
|
||||
// Example - Building byte buffers:
|
||||
//
|
||||
// buffer := Monoid.Empty()
|
||||
// buffer = Monoid.Concat(buffer, []byte("Line 1\n"))
|
||||
// buffer = Monoid.Concat(buffer, []byte("Line 2\n"))
|
||||
// buffer = Monoid.Concat(buffer, []byte("Line 3\n"))
|
||||
//
|
||||
// Example - Associativity:
|
||||
//
|
||||
// a := []byte("a")
|
||||
// b := []byte("b")
|
||||
// c := []byte("c")
|
||||
// left := Monoid.Concat(Monoid.Concat(a, b), c) // []byte("abc")
|
||||
// right := Monoid.Concat(a, Monoid.Concat(b, c)) // []byte("abc")
|
||||
// // left == right
|
||||
//
|
||||
// See also:
|
||||
// - ConcatAll: For concatenating multiple byte slices at once
|
||||
// - Empty(): Convenience function for getting empty byte slice
|
||||
Monoid = A.Monoid[byte]()
|
||||
|
||||
// ConcatAll concatenates all bytes
|
||||
// ConcatAll efficiently concatenates multiple byte slices into a single slice.
|
||||
//
|
||||
// This function takes a variadic number of byte slices and combines them
|
||||
// into a single byte slice. It pre-allocates the exact amount of memory
|
||||
// needed, making it more efficient than repeated concatenation.
|
||||
//
|
||||
// Parameters:
|
||||
// - slices: Zero or more byte slices to concatenate
|
||||
//
|
||||
// Returns:
|
||||
// - A new byte slice containing all input slices concatenated in order
|
||||
//
|
||||
// Performance:
|
||||
//
|
||||
// ConcatAll is more efficient than using Monoid.Concat repeatedly because
|
||||
// it calculates the total size upfront and allocates memory once, avoiding
|
||||
// multiple allocations and copies.
|
||||
//
|
||||
// Example - Basic usage:
|
||||
//
|
||||
// result := ConcatAll(
|
||||
// []byte("Hello"),
|
||||
// []byte(" "),
|
||||
// []byte("World"),
|
||||
// )
|
||||
// // result: []byte("Hello World")
|
||||
//
|
||||
// Example - Empty input:
|
||||
//
|
||||
// result := ConcatAll()
|
||||
// // result: []byte{}
|
||||
//
|
||||
// Example - Single slice:
|
||||
//
|
||||
// result := ConcatAll([]byte("test"))
|
||||
// // result: []byte("test")
|
||||
//
|
||||
// Example - Building protocol messages:
|
||||
//
|
||||
// import "encoding/binary"
|
||||
//
|
||||
// header := []byte{0x01, 0x02}
|
||||
// length := make([]byte, 4)
|
||||
// binary.BigEndian.PutUint32(length, 100)
|
||||
// payload := []byte("data")
|
||||
// footer := []byte{0xFF}
|
||||
//
|
||||
// message := ConcatAll(header, length, payload, footer)
|
||||
//
|
||||
// Example - With empty slices:
|
||||
//
|
||||
// result := ConcatAll(
|
||||
// []byte("a"),
|
||||
// []byte{},
|
||||
// []byte("b"),
|
||||
// []byte{},
|
||||
// []byte("c"),
|
||||
// )
|
||||
// // result: []byte("abc")
|
||||
//
|
||||
// Example - Building CSV line:
|
||||
//
|
||||
// fields := [][]byte{
|
||||
// []byte("John"),
|
||||
// []byte("Doe"),
|
||||
// []byte("30"),
|
||||
// }
|
||||
// separator := []byte(",")
|
||||
//
|
||||
// // Interleave fields with separators
|
||||
// parts := [][]byte{
|
||||
// fields[0], separator,
|
||||
// fields[1], separator,
|
||||
// fields[2],
|
||||
// }
|
||||
// line := ConcatAll(parts...)
|
||||
// // line: []byte("John,Doe,30")
|
||||
//
|
||||
// See also:
|
||||
// - Monoid.Concat: For concatenating exactly two byte slices
|
||||
// - bytes.Join: Standard library function for joining with separator
|
||||
ConcatAll = A.ArrayConcatAll[byte]
|
||||
|
||||
// Ord implements the default ordering on bytes
|
||||
// Ord is the Ord instance for byte slices providing lexicographic ordering.
|
||||
//
|
||||
// This Ord instance compares byte slices lexicographically (dictionary order),
|
||||
// comparing bytes from left to right until a difference is found or one slice
|
||||
// ends. It uses the standard library's bytes.Compare and bytes.Equal functions.
|
||||
//
|
||||
// Comparison rules:
|
||||
// - Compares byte-by-byte from left to right
|
||||
// - First differing byte determines the order
|
||||
// - Shorter slice is less than longer slice if all bytes match
|
||||
// - Empty slice is less than any non-empty slice
|
||||
//
|
||||
// Operations:
|
||||
// - Compare(a, b []byte) int: Returns -1 if a < b, 0 if a == b, 1 if a > b
|
||||
// - Equals(a, b []byte) bool: Returns true if slices are equal
|
||||
//
|
||||
// Example - Basic comparison:
|
||||
//
|
||||
// cmp := Ord.Compare([]byte("abc"), []byte("abd"))
|
||||
// // cmp: -1 (abc < abd)
|
||||
//
|
||||
// cmp = Ord.Compare([]byte("xyz"), []byte("abc"))
|
||||
// // cmp: 1 (xyz > abc)
|
||||
//
|
||||
// cmp = Ord.Compare([]byte("test"), []byte("test"))
|
||||
// // cmp: 0 (equal)
|
||||
//
|
||||
// Example - Length differences:
|
||||
//
|
||||
// cmp := Ord.Compare([]byte("ab"), []byte("abc"))
|
||||
// // cmp: -1 (shorter is less)
|
||||
//
|
||||
// cmp = Ord.Compare([]byte("abc"), []byte("ab"))
|
||||
// // cmp: 1 (longer is greater)
|
||||
//
|
||||
// Example - Empty slices:
|
||||
//
|
||||
// cmp := Ord.Compare([]byte{}, []byte("a"))
|
||||
// // cmp: -1 (empty is less)
|
||||
//
|
||||
// cmp = Ord.Compare([]byte{}, []byte{})
|
||||
// // cmp: 0 (both empty)
|
||||
//
|
||||
// Example - Equality check:
|
||||
//
|
||||
// equal := Ord.Equals([]byte("test"), []byte("test"))
|
||||
// // equal: true
|
||||
//
|
||||
// equal = Ord.Equals([]byte("test"), []byte("Test"))
|
||||
// // equal: false (case-sensitive)
|
||||
//
|
||||
// Example - Sorting byte slices:
|
||||
//
|
||||
// import "github.com/IBM/fp-go/v2/array"
|
||||
//
|
||||
// data := [][]byte{
|
||||
// []byte("zebra"),
|
||||
// []byte("apple"),
|
||||
// []byte("mango"),
|
||||
// }
|
||||
//
|
||||
// sorted := array.Sort(Ord)(data)
|
||||
// // sorted: [[]byte("apple"), []byte("mango"), []byte("zebra")]
|
||||
//
|
||||
// Example - Binary data comparison:
|
||||
//
|
||||
// cmp := Ord.Compare([]byte{0x01, 0x02}, []byte{0x01, 0x03})
|
||||
// // cmp: -1 (0x02 < 0x03)
|
||||
//
|
||||
// Example - Finding minimum:
|
||||
//
|
||||
// import O "github.com/IBM/fp-go/v2/ord"
|
||||
//
|
||||
// a := []byte("xyz")
|
||||
// b := []byte("abc")
|
||||
// min := O.Min(Ord)(a, b)
|
||||
// // min: []byte("abc")
|
||||
//
|
||||
// See also:
|
||||
// - bytes.Compare: Standard library comparison function
|
||||
// - bytes.Equal: Standard library equality function
|
||||
// - array.Sort: For sorting slices using an Ord instance
|
||||
Ord = O.MakeOrd(bytes.Compare, bytes.Equal)
|
||||
)
|
||||
|
||||
153
v2/cli/lens.go
153
v2/cli/lens.go
@@ -60,10 +60,11 @@ type structInfo struct {
|
||||
|
||||
// fieldInfo holds information about a struct field
|
||||
type fieldInfo struct {
|
||||
Name string
|
||||
TypeName string
|
||||
BaseType string // TypeName without leading * for pointer types
|
||||
IsOptional bool // true if field is a pointer or has json omitempty tag
|
||||
Name string
|
||||
TypeName string
|
||||
BaseType string // TypeName without leading * for pointer types
|
||||
IsOptional bool // true if field is a pointer or has json omitempty tag
|
||||
IsComparable bool // true if the type is comparable (can use ==)
|
||||
}
|
||||
|
||||
// templateData holds data for template rendering
|
||||
@@ -115,17 +116,25 @@ func Make{{.Name}}Lenses() {{.Name}}Lenses {
|
||||
|
||||
// Make{{.Name}}RefLenses creates a new {{.Name}}RefLenses with lenses for all fields
|
||||
func Make{{.Name}}RefLenses() {{.Name}}RefLenses {
|
||||
{{- range .Fields}}
|
||||
{{- if .IsOptional}}
|
||||
iso{{.Name}} := I.FromZero[{{.TypeName}}]()
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
return {{.Name}}RefLenses{
|
||||
{{- range .Fields}}
|
||||
{{- if .IsOptional}}
|
||||
{{.Name}}: L.MakeLensRef(
|
||||
func(s *{{$.Name}}) O.Option[{{.TypeName}}] { return iso{{.Name}}.Get(s.{{.Name}}) },
|
||||
func(s *{{$.Name}}, v O.Option[{{.TypeName}}]) *{{$.Name}} { s.{{.Name}} = iso{{.Name}}.ReverseGet(v); return s },
|
||||
{{- if .IsComparable}}
|
||||
{{.Name}}: LO.FromIso[*{{$.Name}}](I.FromZero[{{.TypeName}}]())(L.MakeLensStrict(
|
||||
func(s *{{$.Name}}) {{.TypeName}} { return s.{{.Name}} },
|
||||
func(s *{{$.Name}}, v {{.TypeName}}) *{{$.Name}} { s.{{.Name}} = v; return s },
|
||||
)),
|
||||
{{- else}}
|
||||
{{.Name}}: LO.FromIso[*{{$.Name}}](I.FromZero[{{.TypeName}}]())(L.MakeLensRef(
|
||||
func(s *{{$.Name}}) {{.TypeName}} { return s.{{.Name}} },
|
||||
func(s *{{$.Name}}, v {{.TypeName}}) *{{$.Name}} { s.{{.Name}} = v; return s },
|
||||
)),
|
||||
{{- end}}
|
||||
{{- else}}
|
||||
{{- if .IsComparable}}
|
||||
{{.Name}}: L.MakeLensStrict(
|
||||
func(s *{{$.Name}}) {{.TypeName}} { return s.{{.Name}} },
|
||||
func(s *{{$.Name}}, v {{.TypeName}}) *{{$.Name}} { s.{{.Name}} = v; return s },
|
||||
),
|
||||
{{- else}}
|
||||
{{.Name}}: L.MakeLensRef(
|
||||
@@ -133,6 +142,7 @@ func Make{{.Name}}RefLenses() {{.Name}}RefLenses {
|
||||
func(s *{{$.Name}}, v {{.TypeName}}) *{{$.Name}} { s.{{.Name}} = v; return s },
|
||||
),
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
}
|
||||
}
|
||||
@@ -257,6 +267,111 @@ func isPointerType(expr ast.Expr) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// isComparableType checks if a type expression represents a comparable type.
|
||||
// Comparable types in Go include:
|
||||
// - Basic types (bool, numeric types, string)
|
||||
// - Pointer types
|
||||
// - Channel types
|
||||
// - Interface types
|
||||
// - Structs where all fields are comparable
|
||||
// - Arrays where the element type is comparable
|
||||
//
|
||||
// Non-comparable types include:
|
||||
// - Slices
|
||||
// - Maps
|
||||
// - Functions
|
||||
func isComparableType(expr ast.Expr) bool {
|
||||
switch t := expr.(type) {
|
||||
case *ast.Ident:
|
||||
// Basic types and named types
|
||||
// We assume named types are comparable unless they're known non-comparable types
|
||||
name := t.Name
|
||||
// Known non-comparable built-in types
|
||||
if name == "error" {
|
||||
// error is an interface, which is comparable
|
||||
return true
|
||||
}
|
||||
// Most basic types and named types are comparable
|
||||
// We can't determine if a custom type is comparable without type checking,
|
||||
// so we assume it is (conservative approach)
|
||||
return true
|
||||
case *ast.StarExpr:
|
||||
// Pointer types are always comparable
|
||||
return true
|
||||
case *ast.ArrayType:
|
||||
// Arrays are comparable if their element type is comparable
|
||||
if t.Len == nil {
|
||||
// This is a slice (no length), slices are not comparable
|
||||
return false
|
||||
}
|
||||
// Fixed-size array, check element type
|
||||
return isComparableType(t.Elt)
|
||||
case *ast.MapType:
|
||||
// Maps are not comparable
|
||||
return false
|
||||
case *ast.FuncType:
|
||||
// Functions are not comparable
|
||||
return false
|
||||
case *ast.InterfaceType:
|
||||
// Interface types are comparable
|
||||
return true
|
||||
case *ast.StructType:
|
||||
// Structs are comparable if all fields are comparable
|
||||
// We can't easily determine this without full type information,
|
||||
// so we conservatively return false for struct literals
|
||||
return false
|
||||
case *ast.SelectorExpr:
|
||||
// Qualified identifier (e.g., pkg.Type)
|
||||
// We can't determine comparability without type information
|
||||
// Check for known non-comparable types from standard library
|
||||
if ident, ok := t.X.(*ast.Ident); ok {
|
||||
pkgName := ident.Name
|
||||
typeName := t.Sel.Name
|
||||
// Check for known non-comparable types
|
||||
if pkgName == "context" && typeName == "Context" {
|
||||
// context.Context is an interface, which is comparable
|
||||
return true
|
||||
}
|
||||
// For other qualified types, we assume they're comparable
|
||||
// This is a conservative approach
|
||||
}
|
||||
return true
|
||||
case *ast.IndexExpr, *ast.IndexListExpr:
|
||||
// Generic types - we can't determine comparability without type information
|
||||
// For common generic types, we can make educated guesses
|
||||
var baseExpr ast.Expr
|
||||
if idx, ok := t.(*ast.IndexExpr); ok {
|
||||
baseExpr = idx.X
|
||||
} else if idxList, ok := t.(*ast.IndexListExpr); ok {
|
||||
baseExpr = idxList.X
|
||||
}
|
||||
|
||||
if sel, ok := baseExpr.(*ast.SelectorExpr); ok {
|
||||
if ident, ok := sel.X.(*ast.Ident); ok {
|
||||
pkgName := ident.Name
|
||||
typeName := sel.Sel.Name
|
||||
// Check for known non-comparable generic types
|
||||
if pkgName == "option" && typeName == "Option" {
|
||||
// Option types are not comparable (they contain a slice internally)
|
||||
return false
|
||||
}
|
||||
if pkgName == "either" && typeName == "Either" {
|
||||
// Either types are not comparable
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
// For other generic types, conservatively assume not comparable
|
||||
return false
|
||||
case *ast.ChanType:
|
||||
// Channel types are comparable
|
||||
return true
|
||||
default:
|
||||
// Unknown type, conservatively assume not comparable
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// parseFile parses a Go file and extracts structs with lens annotations
|
||||
func parseFile(filename string) ([]structInfo, string, error) {
|
||||
fset := token.NewFileSet()
|
||||
@@ -331,6 +446,7 @@ func parseFile(filename string) ([]structInfo, string, error) {
|
||||
typeName := getTypeName(field.Type)
|
||||
isOptional := false
|
||||
baseType := typeName
|
||||
isComparable := false
|
||||
|
||||
// Check if field is optional:
|
||||
// 1. Pointer types are always optional
|
||||
@@ -344,6 +460,10 @@ func parseFile(filename string) ([]structInfo, string, error) {
|
||||
isOptional = true
|
||||
}
|
||||
|
||||
// Check if the type is comparable (for non-optional fields)
|
||||
// For optional fields, we don't need to check since they use LensO
|
||||
isComparable = isComparableType(field.Type)
|
||||
|
||||
// Extract imports from this field's type
|
||||
fieldImports := make(map[string]string)
|
||||
extractImports(field.Type, fieldImports)
|
||||
@@ -356,10 +476,11 @@ func parseFile(filename string) ([]structInfo, string, error) {
|
||||
}
|
||||
|
||||
fields = append(fields, fieldInfo{
|
||||
Name: name.Name,
|
||||
TypeName: typeName,
|
||||
BaseType: baseType,
|
||||
IsOptional: isOptional,
|
||||
Name: name.Name,
|
||||
TypeName: typeName,
|
||||
BaseType: baseType,
|
||||
IsOptional: isOptional,
|
||||
IsComparable: isComparable,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,6 +168,91 @@ func TestIsPointerType(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsComparableType(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
code string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "basic type - string",
|
||||
code: "type T struct { F string }",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "basic type - int",
|
||||
code: "type T struct { F int }",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "basic type - bool",
|
||||
code: "type T struct { F bool }",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "pointer type",
|
||||
code: "type T struct { F *string }",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "slice type - not comparable",
|
||||
code: "type T struct { F []string }",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "map type - not comparable",
|
||||
code: "type T struct { F map[string]int }",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "array type - comparable if element is",
|
||||
code: "type T struct { F [5]int }",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "interface type",
|
||||
code: "type T struct { F interface{} }",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "channel type",
|
||||
code: "type T struct { F chan int }",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "function type - not comparable",
|
||||
code: "type T struct { F func() }",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "struct literal - conservatively not comparable",
|
||||
code: "type T struct { F struct{ X int } }",
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fset := token.NewFileSet()
|
||||
file, err := parser.ParseFile(fset, "", "package test\n"+tt.code, 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
var fieldType ast.Expr
|
||||
ast.Inspect(file, func(n ast.Node) bool {
|
||||
if field, ok := n.(*ast.Field); ok && len(field.Names) > 0 {
|
||||
fieldType = field.Type
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
require.NotNil(t, fieldType)
|
||||
result := isComparableType(fieldType)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasOmitEmpty(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -337,6 +422,167 @@ type Config struct {
|
||||
assert.False(t, config.Fields[4].IsOptional, "Required field without omitempty should not be optional")
|
||||
}
|
||||
|
||||
func TestParseFileWithComparableTypes(t *testing.T) {
|
||||
// Create a temporary test file
|
||||
tmpDir := t.TempDir()
|
||||
testFile := filepath.Join(tmpDir, "test.go")
|
||||
|
||||
testCode := `package testpkg
|
||||
|
||||
// fp-go:Lens
|
||||
type TypeTest struct {
|
||||
Name string
|
||||
Age int
|
||||
Pointer *string
|
||||
Slice []string
|
||||
Map map[string]int
|
||||
Channel chan int
|
||||
}
|
||||
`
|
||||
|
||||
err := os.WriteFile(testFile, []byte(testCode), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Parse the file
|
||||
structs, pkg, err := parseFile(testFile)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify results
|
||||
assert.Equal(t, "testpkg", pkg)
|
||||
assert.Len(t, structs, 1)
|
||||
|
||||
// Check TypeTest struct
|
||||
typeTest := structs[0]
|
||||
assert.Equal(t, "TypeTest", typeTest.Name)
|
||||
assert.Len(t, typeTest.Fields, 6)
|
||||
|
||||
// Name - string is comparable
|
||||
assert.Equal(t, "Name", typeTest.Fields[0].Name)
|
||||
assert.Equal(t, "string", typeTest.Fields[0].TypeName)
|
||||
assert.False(t, typeTest.Fields[0].IsOptional)
|
||||
assert.True(t, typeTest.Fields[0].IsComparable, "string should be comparable")
|
||||
|
||||
// Age - int is comparable
|
||||
assert.Equal(t, "Age", typeTest.Fields[1].Name)
|
||||
assert.Equal(t, "int", typeTest.Fields[1].TypeName)
|
||||
assert.False(t, typeTest.Fields[1].IsOptional)
|
||||
assert.True(t, typeTest.Fields[1].IsComparable, "int should be comparable")
|
||||
|
||||
// Pointer - pointer is optional, IsComparable not checked for optional fields
|
||||
assert.Equal(t, "Pointer", typeTest.Fields[2].Name)
|
||||
assert.Equal(t, "*string", typeTest.Fields[2].TypeName)
|
||||
assert.True(t, typeTest.Fields[2].IsOptional)
|
||||
|
||||
// Slice - not comparable
|
||||
assert.Equal(t, "Slice", typeTest.Fields[3].Name)
|
||||
assert.Equal(t, "[]string", typeTest.Fields[3].TypeName)
|
||||
assert.False(t, typeTest.Fields[3].IsOptional)
|
||||
assert.False(t, typeTest.Fields[3].IsComparable, "slice should not be comparable")
|
||||
|
||||
// Map - not comparable
|
||||
assert.Equal(t, "Map", typeTest.Fields[4].Name)
|
||||
assert.Equal(t, "map[string]int", typeTest.Fields[4].TypeName)
|
||||
assert.False(t, typeTest.Fields[4].IsOptional)
|
||||
assert.False(t, typeTest.Fields[4].IsComparable, "map should not be comparable")
|
||||
|
||||
// Channel - comparable (note: getTypeName returns "any" for channel types, but isComparableType correctly identifies them)
|
||||
assert.Equal(t, "Channel", typeTest.Fields[5].Name)
|
||||
assert.Equal(t, "any", typeTest.Fields[5].TypeName) // getTypeName doesn't handle chan types specifically
|
||||
assert.False(t, typeTest.Fields[5].IsOptional)
|
||||
assert.True(t, typeTest.Fields[5].IsComparable, "channel should be comparable")
|
||||
}
|
||||
|
||||
func TestLensRefTemplatesWithComparable(t *testing.T) {
|
||||
s := structInfo{
|
||||
Name: "TestStruct",
|
||||
Fields: []fieldInfo{
|
||||
{Name: "Name", TypeName: "string", IsOptional: false, IsComparable: true},
|
||||
{Name: "Age", TypeName: "int", IsOptional: false, IsComparable: true},
|
||||
{Name: "Data", TypeName: "[]byte", IsOptional: false, IsComparable: false},
|
||||
{Name: "Pointer", TypeName: "*string", IsOptional: true, IsComparable: false},
|
||||
},
|
||||
}
|
||||
|
||||
// Test constructor template for RefLenses
|
||||
var constructorBuf bytes.Buffer
|
||||
err := constructorTmpl.Execute(&constructorBuf, s)
|
||||
require.NoError(t, err)
|
||||
|
||||
constructorStr := constructorBuf.String()
|
||||
|
||||
// Check that MakeLensStrict is used for comparable types in RefLenses
|
||||
assert.Contains(t, constructorStr, "func MakeTestStructRefLenses() TestStructRefLenses")
|
||||
|
||||
// Name field - comparable, should use MakeLensStrict
|
||||
assert.Contains(t, constructorStr, "Name: L.MakeLensStrict(",
|
||||
"comparable field Name should use MakeLensStrict in RefLenses")
|
||||
|
||||
// Age field - comparable, should use MakeLensStrict
|
||||
assert.Contains(t, constructorStr, "Age: L.MakeLensStrict(",
|
||||
"comparable field Age should use MakeLensStrict in RefLenses")
|
||||
|
||||
// Data field - not comparable, should use MakeLensRef
|
||||
assert.Contains(t, constructorStr, "Data: L.MakeLensRef(",
|
||||
"non-comparable field Data should use MakeLensRef in RefLenses")
|
||||
|
||||
}
|
||||
|
||||
func TestGenerateLensHelpersWithComparable(t *testing.T) {
|
||||
// Create a temporary directory with test files
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
testCode := `package testpkg
|
||||
|
||||
// fp-go:Lens
|
||||
type TestStruct struct {
|
||||
Name string
|
||||
Count int
|
||||
Data []byte
|
||||
}
|
||||
`
|
||||
|
||||
testFile := filepath.Join(tmpDir, "test.go")
|
||||
err := os.WriteFile(testFile, []byte(testCode), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Generate lens code
|
||||
outputFile := "gen.go"
|
||||
err = generateLensHelpers(tmpDir, outputFile, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify the generated file exists
|
||||
genPath := filepath.Join(tmpDir, outputFile)
|
||||
_, err = os.Stat(genPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Read and verify the generated content
|
||||
content, err := os.ReadFile(genPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
contentStr := string(content)
|
||||
|
||||
// Check for expected content in RefLenses
|
||||
assert.Contains(t, contentStr, "MakeTestStructRefLenses")
|
||||
|
||||
// Name and Count are comparable, should use MakeLensStrict
|
||||
assert.Contains(t, contentStr, "L.MakeLensStrict",
|
||||
"comparable fields should use MakeLensStrict in RefLenses")
|
||||
|
||||
// Data is not comparable (slice), should use MakeLensRef
|
||||
assert.Contains(t, contentStr, "L.MakeLensRef",
|
||||
"non-comparable fields should use MakeLensRef in RefLenses")
|
||||
|
||||
// Verify the pattern appears for Name field (comparable)
|
||||
namePattern := "Name: L.MakeLensStrict("
|
||||
assert.Contains(t, contentStr, namePattern,
|
||||
"Name field should use MakeLensStrict")
|
||||
|
||||
// Verify the pattern appears for Data field (not comparable)
|
||||
dataPattern := "Data: L.MakeLensRef("
|
||||
assert.Contains(t, contentStr, dataPattern,
|
||||
"Data field should use MakeLensRef")
|
||||
}
|
||||
|
||||
func TestGenerateLensHelpers(t *testing.T) {
|
||||
// Create a temporary directory with test files
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
@@ -96,7 +96,7 @@ func Bind[S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
f Kleisli[S1, T],
|
||||
) Operator[S1, S2] {
|
||||
return RIOR.Bind[context.Context](setter, f)
|
||||
return RIOR.Bind(setter, f)
|
||||
}
|
||||
|
||||
// Let attaches the result of a computation to a context [S1] to produce a context [S2]
|
||||
@@ -256,7 +256,7 @@ func BindL[S, T any](
|
||||
lens L.Lens[S, T],
|
||||
f Kleisli[T, T],
|
||||
) Operator[S, S] {
|
||||
return RIOR.BindL[context.Context](lens, f)
|
||||
return RIOR.BindL(lens, f)
|
||||
}
|
||||
|
||||
// LetL is a variant of Let that uses a lens to focus on a specific part of the context.
|
||||
@@ -570,7 +570,7 @@ func ApReaderS[S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
fa Reader[context.Context, T],
|
||||
) Operator[S1, S2] {
|
||||
return ApS(setter, FromReader[T](fa))
|
||||
return ApS(setter, FromReader(fa))
|
||||
}
|
||||
|
||||
// ApReaderIOS is an applicative variant that works with ReaderIO values.
|
||||
@@ -585,7 +585,7 @@ func ApReaderIOS[S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
fa ReaderIO[T],
|
||||
) Operator[S1, S2] {
|
||||
return ApS(setter, FromReaderIO[T](fa))
|
||||
return ApS(setter, FromReaderIO(fa))
|
||||
}
|
||||
|
||||
// ApEitherS is an applicative variant that works with Either (Result) values.
|
||||
@@ -600,7 +600,7 @@ func ApEitherS[S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
fa Result[T],
|
||||
) Operator[S1, S2] {
|
||||
return ApS(setter, FromEither[T](fa))
|
||||
return ApS(setter, FromEither(fa))
|
||||
}
|
||||
|
||||
// ApResultS is an applicative variant that works with Result values.
|
||||
@@ -615,7 +615,7 @@ func ApResultS[S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
fa Result[T],
|
||||
) Operator[S1, S2] {
|
||||
return ApS(setter, FromResult[T](fa))
|
||||
return ApS(setter, FromResult(fa))
|
||||
}
|
||||
|
||||
// ApIOEitherSL is a lens-based variant of ApIOEitherS.
|
||||
@@ -675,7 +675,7 @@ func ApReaderSL[S, T any](
|
||||
lens L.Lens[S, T],
|
||||
fa Reader[context.Context, T],
|
||||
) Operator[S, S] {
|
||||
return ApSL(lens, FromReader[T](fa))
|
||||
return ApSL(lens, FromReader(fa))
|
||||
}
|
||||
|
||||
// ApReaderIOSL is a lens-based variant of ApReaderIOS.
|
||||
@@ -690,7 +690,7 @@ func ApReaderIOSL[S, T any](
|
||||
lens L.Lens[S, T],
|
||||
fa ReaderIO[T],
|
||||
) Operator[S, S] {
|
||||
return ApSL(lens, FromReaderIO[T](fa))
|
||||
return ApSL(lens, FromReaderIO(fa))
|
||||
}
|
||||
|
||||
// ApEitherSL is a lens-based variant of ApEitherS.
|
||||
@@ -705,7 +705,7 @@ func ApEitherSL[S, T any](
|
||||
lens L.Lens[S, T],
|
||||
fa Result[T],
|
||||
) Operator[S, S] {
|
||||
return ApSL(lens, FromEither[T](fa))
|
||||
return ApSL(lens, FromEither(fa))
|
||||
}
|
||||
|
||||
// ApResultSL is a lens-based variant of ApResultS.
|
||||
@@ -720,5 +720,5 @@ func ApResultSL[S, T any](
|
||||
lens L.Lens[S, T],
|
||||
fa Result[T],
|
||||
) Operator[S, S] {
|
||||
return ApSL(lens, FromResult[T](fa))
|
||||
return ApSL(lens, FromResult(fa))
|
||||
}
|
||||
|
||||
@@ -83,5 +83,5 @@ func AlternativeMonoid[A any](m monoid.Monoid[A]) Monoid[A] {
|
||||
//
|
||||
// Returns a Monoid for ReaderIOResult[A] with Alt-based combination.
|
||||
func AltMonoid[A any](zero Lazy[ReaderIOResult[A]]) Monoid[A] {
|
||||
return RIOR.AltMonoid[context.Context](zero)
|
||||
return RIOR.AltMonoid(zero)
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
@@ -78,7 +82,7 @@ func Left[A any](l error) ReaderIOResult[A] {
|
||||
//
|
||||
//go:inline
|
||||
func Right[A any](r A) ReaderIOResult[A] {
|
||||
return RIOR.Right[context.Context, A](r)
|
||||
return RIOR.Right[context.Context](r)
|
||||
}
|
||||
|
||||
// MonadMap transforms the success value of a [ReaderIOResult] using the provided function.
|
||||
@@ -458,12 +462,12 @@ func FromIO[A any](t IO[A]) ReaderIOResult[A] {
|
||||
|
||||
//go:inline
|
||||
func FromReader[A any](t Reader[context.Context, A]) ReaderIOResult[A] {
|
||||
return RIOR.FromReader[context.Context](t)
|
||||
return RIOR.FromReader(t)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func FromReaderIO[A any](t ReaderIO[A]) ReaderIOResult[A] {
|
||||
return RIOR.FromReaderIO[context.Context](t)
|
||||
return RIOR.FromReaderIO(t)
|
||||
}
|
||||
|
||||
// FromLazy converts a [Lazy] computation into a [ReaderIOResult].
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -561,7 +561,7 @@ func BenchmarkPipeline_Chain_Right(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = F.Pipe1(
|
||||
rioe,
|
||||
Chain(func(x int) ReaderIOResult[int] { return Right[int](x * 2) }),
|
||||
Chain(func(x int) ReaderIOResult[int] { return Right(x * 2) }),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -573,7 +573,7 @@ func BenchmarkPipeline_Chain_Left(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchRIOE = F.Pipe1(
|
||||
rioe,
|
||||
Chain(func(x int) ReaderIOResult[int] { return Right[int](x * 2) }),
|
||||
Chain(func(x int) ReaderIOResult[int] { return Right(x * 2) }),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -586,7 +586,7 @@ func BenchmarkPipeline_Complex_Right(b *testing.B) {
|
||||
benchRIOE = F.Pipe3(
|
||||
rioe,
|
||||
Map(func(x int) int { return x * 2 }),
|
||||
Chain(func(x int) ReaderIOResult[int] { return Right[int](x + 1) }),
|
||||
Chain(func(x int) ReaderIOResult[int] { return Right(x + 1) }),
|
||||
Map(func(x int) int { return x * 2 }),
|
||||
)
|
||||
}
|
||||
@@ -600,7 +600,7 @@ func BenchmarkPipeline_Complex_Left(b *testing.B) {
|
||||
benchRIOE = F.Pipe3(
|
||||
rioe,
|
||||
Map(func(x int) int { return x * 2 }),
|
||||
Chain(func(x int) ReaderIOResult[int] { return Right[int](x + 1) }),
|
||||
Chain(func(x int) ReaderIOResult[int] { return Right(x + 1) }),
|
||||
Map(func(x int) int { return x * 2 }),
|
||||
)
|
||||
}
|
||||
@@ -610,7 +610,7 @@ func BenchmarkExecutePipeline_Complex_Right(b *testing.B) {
|
||||
rioe := F.Pipe3(
|
||||
Right(10),
|
||||
Map(func(x int) int { return x * 2 }),
|
||||
Chain(func(x int) ReaderIOResult[int] { return Right[int](x + 1) }),
|
||||
Chain(func(x int) ReaderIOResult[int] { return Right(x + 1) }),
|
||||
Map(func(x int) int { return x * 2 }),
|
||||
)
|
||||
b.ResetTimer()
|
||||
@@ -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
@@ -118,7 +118,7 @@ func SequenceRecord[K comparable, A any](ma map[K]ReaderIOResult[A]) ReaderIORes
|
||||
//
|
||||
// Returns a ReaderIOResult containing an array of transformed values.
|
||||
func MonadTraverseArraySeq[A, B any](as []A, f Kleisli[A, B]) ReaderIOResult[[]B] {
|
||||
return array.MonadTraverse[[]A](
|
||||
return array.MonadTraverse(
|
||||
Of[[]B],
|
||||
Map[[]B, func(B) []B],
|
||||
ApSeq[[]B, B],
|
||||
@@ -168,7 +168,7 @@ func SequenceArraySeq[A any](ma []ReaderIOResult[A]) ReaderIOResult[[]A] {
|
||||
|
||||
// MonadTraverseRecordSeq uses transforms a record [map[K]A] into [map[K]ReaderIOResult[B]] and then resolves that into a [ReaderIOResult[map[K]B]]
|
||||
func MonadTraverseRecordSeq[K comparable, A, B any](as map[K]A, f Kleisli[A, B]) ReaderIOResult[map[K]B] {
|
||||
return record.MonadTraverse[map[K]A](
|
||||
return record.MonadTraverse(
|
||||
Of[map[K]B],
|
||||
Map[map[K]B, func(B) map[K]B],
|
||||
ApSeq[map[K]B, B],
|
||||
@@ -213,7 +213,7 @@ func SequenceRecordSeq[K comparable, A any](ma map[K]ReaderIOResult[A]) ReaderIO
|
||||
//
|
||||
// Returns a ReaderIOResult containing an array of transformed values.
|
||||
func MonadTraverseArrayPar[A, B any](as []A, f Kleisli[A, B]) ReaderIOResult[[]B] {
|
||||
return array.MonadTraverse[[]A](
|
||||
return array.MonadTraverse(
|
||||
Of[[]B],
|
||||
Map[[]B, func(B) []B],
|
||||
ApPar[[]B, B],
|
||||
@@ -285,7 +285,7 @@ func TraverseRecordWithIndexPar[K comparable, A, B any](f func(K, A) ReaderIORes
|
||||
|
||||
// MonadTraverseRecordPar uses transforms a record [map[K]A] into [map[K]ReaderIOResult[B]] and then resolves that into a [ReaderIOResult[map[K]B]]
|
||||
func MonadTraverseRecordPar[K comparable, A, B any](as map[K]A, f Kleisli[A, B]) ReaderIOResult[map[K]B] {
|
||||
return record.MonadTraverse[map[K]A](
|
||||
return record.MonadTraverse(
|
||||
Of[map[K]B],
|
||||
Map[map[K]B, func(B) map[K]B],
|
||||
ApPar[map[K]B, B],
|
||||
|
||||
@@ -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]
|
||||
)
|
||||
3186
v2/di/gen.go
3186
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[R](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[O.Option[T]](fmt.Sprintf("Option[%s]", name), id, DIE.Option, toOptionType(toIdentity), providerFactory),
|
||||
makeToken[IOE.IOEither[error, T]](fmt.Sprintf("IOEither[%s]", name), id, DIE.IOEither, toIOEitherType(toIdentity), providerFactory),
|
||||
makeToken[IOO.IOOption[T]](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[O.Option[[]T]](fmt.Sprintf("Option[%s]", containerName), id, DIE.Multi|DIE.Option, toOptionType(toContainer), providerFactory),
|
||||
makeToken[IOE.IOEither[error, []T]](fmt.Sprintf("IOEither[%s]", containerName), id, DIE.Multi|DIE.IOEither, toIOEitherType(toContainer), providerFactory),
|
||||
makeToken[IOO.IOOption[[]T]](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[O.Option[T]](fmt.Sprintf("Option[%s]", itemName), id, DIE.Item|DIE.Option, toOptionType(toItem), providerFactory),
|
||||
makeToken[IOE.IOEither[error, T]](fmt.Sprintf("IOEither[%s]", itemName), id, DIE.Item|DIE.IOEither, toIOEitherType(toItem), providerFactory),
|
||||
makeToken[IOO.IOOption[T]](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")))))
|
||||
}
|
||||
|
||||
@@ -273,7 +273,7 @@ func BindL[E, S, T any](
|
||||
lens Lens[S, T],
|
||||
f Kleisli[E, T, T],
|
||||
) Endomorphism[Either[E, S]] {
|
||||
return Bind[E, S, S, T](lens.Set, function.Flow2(lens.Get, f))
|
||||
return Bind(lens.Set, function.Flow2(lens.Get, f))
|
||||
}
|
||||
|
||||
// LetL attaches the result of a pure computation to a context using a lens-based setter.
|
||||
@@ -323,7 +323,7 @@ func LetL[E, S, T any](
|
||||
lens Lens[S, T],
|
||||
f Endomorphism[T],
|
||||
) Endomorphism[Either[E, S]] {
|
||||
return Let[E, S, S, T](lens.Set, function.Flow2(lens.Get, f))
|
||||
return Let[E](lens.Set, function.Flow2(lens.Get, f))
|
||||
}
|
||||
|
||||
// LetToL attaches a constant value to a context using a lens-based setter.
|
||||
@@ -371,5 +371,5 @@ func LetToL[E, S, T any](
|
||||
lens Lens[S, T],
|
||||
b T,
|
||||
) Endomorphism[Either[E, S]] {
|
||||
return LetTo[E, S, S, T](lens.Set, b)
|
||||
return LetTo[E](lens.Set, b)
|
||||
}
|
||||
|
||||
@@ -362,7 +362,7 @@ func Fold[E, A, B any](onLeft func(E) B, onRight func(A) B) func(Either[E, A]) B
|
||||
//
|
||||
//go:inline
|
||||
func UnwrapError[A any](ma Either[error, A]) (A, error) {
|
||||
return Unwrap[error](ma)
|
||||
return Unwrap(ma)
|
||||
}
|
||||
|
||||
// FromPredicate creates an Either based on a predicate.
|
||||
@@ -381,7 +381,7 @@ func FromPredicate[E, A any](pred func(A) bool, onFalse func(A) E) func(A) Eithe
|
||||
if pred(a) {
|
||||
return Right[E](a)
|
||||
}
|
||||
return Left[A, E](onFalse(a))
|
||||
return Left[A](onFalse(a))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -267,7 +267,7 @@ func BenchmarkMonadChain_Left(b *testing.B) {
|
||||
|
||||
func BenchmarkChain_Right(b *testing.B) {
|
||||
right := Right[error](42)
|
||||
chainer := Chain[error](func(a int) Either[error, int] { return Right[error](a * 2) })
|
||||
chainer := Chain(func(a int) Either[error, int] { return Right[error](a * 2) })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
@@ -277,7 +277,7 @@ func BenchmarkChain_Right(b *testing.B) {
|
||||
|
||||
func BenchmarkChain_Left(b *testing.B) {
|
||||
left := Left[int](errBench)
|
||||
chainer := Chain[error](func(a int) Either[error, int] { return Right[error](a * 2) })
|
||||
chainer := Chain(func(a int) Either[error, int] { return Right[error](a * 2) })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
@@ -297,7 +297,7 @@ func BenchmarkChainFirst_Right(b *testing.B) {
|
||||
|
||||
func BenchmarkChainFirst_Left(b *testing.B) {
|
||||
left := Left[int](errBench)
|
||||
chainer := ChainFirst[error](func(a int) Either[error, string] { return Right[error]("logged") })
|
||||
chainer := ChainFirst(func(a int) Either[error, string] { return Right[error]("logged") })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
@@ -357,7 +357,7 @@ func BenchmarkMonadAp_LeftRight(b *testing.B) {
|
||||
func BenchmarkAp_RightRight(b *testing.B) {
|
||||
fab := Right[error](func(a int) int { return a * 2 })
|
||||
fa := Right[error](42)
|
||||
ap := Ap[int, error, int](fa)
|
||||
ap := Ap[int](fa)
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
@@ -378,7 +378,7 @@ func BenchmarkAlt_RightRight(b *testing.B) {
|
||||
|
||||
func BenchmarkAlt_LeftRight(b *testing.B) {
|
||||
left := Left[int](errBench)
|
||||
alternative := Alt[error](func() Either[error, int] { return Right[error](99) })
|
||||
alternative := Alt(func() Either[error, int] { return Right[error](99) })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
@@ -388,7 +388,7 @@ func BenchmarkAlt_LeftRight(b *testing.B) {
|
||||
|
||||
func BenchmarkOrElse_Right(b *testing.B) {
|
||||
right := Right[error](42)
|
||||
recover := OrElse[error](func(e error) Either[error, int] { return Right[error](0) })
|
||||
recover := OrElse(func(e error) Either[error, int] { return Right[error](0) })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
@@ -457,7 +457,7 @@ func BenchmarkSwap_Left(b *testing.B) {
|
||||
|
||||
func BenchmarkGetOrElse_Right(b *testing.B) {
|
||||
right := Right[error](42)
|
||||
getter := GetOrElse[error](func(e error) int { return 0 })
|
||||
getter := GetOrElse(func(e error) int { return 0 })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
@@ -467,7 +467,7 @@ func BenchmarkGetOrElse_Right(b *testing.B) {
|
||||
|
||||
func BenchmarkGetOrElse_Left(b *testing.B) {
|
||||
left := Left[int](errBench)
|
||||
getter := GetOrElse[error](func(e error) int { return 0 })
|
||||
getter := GetOrElse(func(e error) int { return 0 })
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
@@ -507,7 +507,7 @@ func BenchmarkPipeline_Chain_Right(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = F.Pipe1(
|
||||
right,
|
||||
Chain[error](func(x int) Either[error, int] { return Right[error](x * 2) }),
|
||||
Chain(func(x int) Either[error, int] { return Right[error](x * 2) }),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -519,7 +519,7 @@ func BenchmarkPipeline_Chain_Left(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
benchResult = F.Pipe1(
|
||||
left,
|
||||
Chain[error](func(x int) Either[error, int] { return Right[error](x * 2) }),
|
||||
Chain(func(x int) Either[error, int] { return Right[error](x * 2) }),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -532,7 +532,7 @@ func BenchmarkPipeline_Complex_Right(b *testing.B) {
|
||||
benchResult = F.Pipe3(
|
||||
right,
|
||||
Map[error](func(x int) int { return x * 2 }),
|
||||
Chain[error](func(x int) Either[error, int] { return Right[error](x + 1) }),
|
||||
Chain(func(x int) Either[error, int] { return Right[error](x + 1) }),
|
||||
Map[error](func(x int) int { return x * 2 }),
|
||||
)
|
||||
}
|
||||
@@ -546,7 +546,7 @@ func BenchmarkPipeline_Complex_Left(b *testing.B) {
|
||||
benchResult = F.Pipe3(
|
||||
left,
|
||||
Map[error](func(x int) int { return x * 2 }),
|
||||
Chain[error](func(x int) Either[error, int] { return Right[error](x + 1) }),
|
||||
Chain(func(x int) Either[error, int] { return Right[error](x + 1) }),
|
||||
Map[error](func(x int) int { return x * 2 }),
|
||||
)
|
||||
}
|
||||
@@ -599,7 +599,7 @@ func BenchmarkDo(b *testing.B) {
|
||||
func BenchmarkBind_Right(b *testing.B) {
|
||||
type State struct{ value int }
|
||||
initial := Do[error](State{})
|
||||
binder := Bind[error, State, State](
|
||||
binder := Bind(
|
||||
func(v int) func(State) State {
|
||||
return func(s State) State { return State{value: v} }
|
||||
},
|
||||
@@ -617,7 +617,7 @@ func BenchmarkBind_Right(b *testing.B) {
|
||||
func BenchmarkLet_Right(b *testing.B) {
|
||||
type State struct{ value int }
|
||||
initial := Right[error](State{value: 10})
|
||||
letter := Let[error, State, State](
|
||||
letter := Let[error](
|
||||
func(v int) func(State) State {
|
||||
return func(s State) State { return State{value: s.value + v} }
|
||||
},
|
||||
@@ -648,5 +648,3 @@ func BenchmarkString_Left(b *testing.B) {
|
||||
benchString = left.String()
|
||||
}
|
||||
}
|
||||
|
||||
// Made with Bob
|
||||
|
||||
@@ -341,7 +341,7 @@ func TestTraverseRecordWithIndex(t *testing.T) {
|
||||
}
|
||||
|
||||
input := map[string]string{"a": "1"}
|
||||
result := TraverseRecordWithIndex[string](validate)(input)
|
||||
result := TraverseRecordWithIndex(validate)(input)
|
||||
expected := Right[error](map[string]string{"a": "a:1"})
|
||||
assert.Equal(t, expected, result)
|
||||
}
|
||||
@@ -658,7 +658,7 @@ func TestAlternativeMonoid(t *testing.T) {
|
||||
// Test AltMonoid
|
||||
func TestAltMonoid(t *testing.T) {
|
||||
zero := func() Either[error, int] { return Left[int](errors.New("empty")) }
|
||||
m := AltMonoid[error, int](zero)
|
||||
m := AltMonoid(zero)
|
||||
|
||||
result := m.Concat(Left[int](errors.New("err1")), Right[error](42))
|
||||
assert.Equal(t, Right[error](42), result)
|
||||
|
||||
@@ -47,7 +47,7 @@ func TestMapEither(t *testing.T) {
|
||||
|
||||
assert.Equal(t, F.Pipe1(Right[error]("abc"), Map[error](utils.StringLen)), Right[error](3))
|
||||
|
||||
val2 := F.Pipe1(Left[string, string]("s"), Map[string](utils.StringLen))
|
||||
val2 := F.Pipe1(Left[string]("s"), Map[string](utils.StringLen))
|
||||
exp2 := Left[int]("s")
|
||||
|
||||
assert.Equal(t, val2, exp2)
|
||||
@@ -69,15 +69,15 @@ func TestReduce(t *testing.T) {
|
||||
s := S.Semigroup()
|
||||
|
||||
assert.Equal(t, "foobar", F.Pipe1(Right[string]("bar"), Reduce[string](s.Concat, "foo")))
|
||||
assert.Equal(t, "foo", F.Pipe1(Left[string, string]("bar"), Reduce[string](s.Concat, "foo")))
|
||||
assert.Equal(t, "foo", F.Pipe1(Left[string]("bar"), Reduce[string](s.Concat, "foo")))
|
||||
|
||||
}
|
||||
func TestAp(t *testing.T) {
|
||||
f := S.Size
|
||||
|
||||
assert.Equal(t, Right[string](3), F.Pipe1(Right[string](f), Ap[int, string, string](Right[string]("abc"))))
|
||||
assert.Equal(t, Left[int]("maError"), F.Pipe1(Right[string](f), Ap[int, string, string](Left[string, string]("maError"))))
|
||||
assert.Equal(t, Left[int]("mabError"), F.Pipe1(Left[func(string) int]("mabError"), Ap[int, string, string](Left[string, string]("maError"))))
|
||||
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]("maError"))))
|
||||
assert.Equal(t, Left[int]("mabError"), F.Pipe1(Left[func(string) int]("mabError"), Ap[int](Left[string]("maError"))))
|
||||
}
|
||||
|
||||
func TestAlt(t *testing.T) {
|
||||
@@ -91,7 +91,7 @@ func TestChainFirst(t *testing.T) {
|
||||
f := F.Flow2(S.Size, Right[string, int])
|
||||
|
||||
assert.Equal(t, Right[string]("abc"), F.Pipe1(Right[string]("abc"), ChainFirst(f)))
|
||||
assert.Equal(t, Left[string, string]("maError"), F.Pipe1(Left[string, string]("maError"), ChainFirst(f)))
|
||||
assert.Equal(t, Left[string]("maError"), F.Pipe1(Left[string]("maError"), ChainFirst(f)))
|
||||
}
|
||||
|
||||
func TestChainOptionK(t *testing.T) {
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
type eitherFunctor[E, A, B any] struct{}
|
||||
|
||||
func (o *eitherFunctor[E, A, B]) Map(f func(A) B) Operator[E, A, B] {
|
||||
return Map[E, A, B](f)
|
||||
return Map[E](f)
|
||||
}
|
||||
|
||||
// Functor implements the functoric operations for Either.
|
||||
|
||||
@@ -26,7 +26,7 @@ func _log[E, A any](left func(string, ...any), right func(string, ...any), prefi
|
||||
return Fold(
|
||||
func(e E) Either[E, A] {
|
||||
left("%s: %v", prefix, e)
|
||||
return Left[A, E](e)
|
||||
return Left[A](e)
|
||||
},
|
||||
func(a A) Either[E, A] {
|
||||
right("%s: %v", prefix, a)
|
||||
|
||||
@@ -22,19 +22,19 @@ import (
|
||||
type eitherMonad[E, A, B any] struct{}
|
||||
|
||||
func (o *eitherMonad[E, A, B]) Of(a A) Either[E, A] {
|
||||
return Of[E, A](a)
|
||||
return Of[E](a)
|
||||
}
|
||||
|
||||
func (o *eitherMonad[E, A, B]) Map(f func(A) B) Operator[E, A, B] {
|
||||
return Map[E, A, B](f)
|
||||
return Map[E](f)
|
||||
}
|
||||
|
||||
func (o *eitherMonad[E, A, B]) Chain(f func(A) Either[E, B]) Operator[E, A, B] {
|
||||
return Chain[E, A, B](f)
|
||||
return Chain(f)
|
||||
}
|
||||
|
||||
func (o *eitherMonad[E, A, B]) Ap(fa Either[E, A]) Operator[E, func(A) B, B] {
|
||||
return Ap[B, E, A](fa)
|
||||
return Ap[B](fa)
|
||||
}
|
||||
|
||||
// Monad implements the monadic operations for Either.
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
type eitherPointed[E, A any] struct{}
|
||||
|
||||
func (o *eitherPointed[E, A]) Of(a A) Either[E, A] {
|
||||
return Of[E, A](a)
|
||||
return Of[E](a)
|
||||
}
|
||||
|
||||
// Pointed implements the pointed functor operations for Either.
|
||||
|
||||
@@ -39,13 +39,18 @@
|
||||
// double := func(x int) int { return x * 2 }
|
||||
// increment := func(x int) int { return x + 1 }
|
||||
//
|
||||
// // Compose them
|
||||
// doubleAndIncrement := endomorphism.Compose(double, increment)
|
||||
// result := doubleAndIncrement(5) // (5 * 2) + 1 = 11
|
||||
// // Compose them (RIGHT-TO-LEFT execution)
|
||||
// composed := endomorphism.Compose(double, increment)
|
||||
// result := composed(5) // increment(5) then double: (5 + 1) * 2 = 12
|
||||
//
|
||||
// // Chain them (LEFT-TO-RIGHT execution)
|
||||
// chained := endomorphism.MonadChain(double, increment)
|
||||
// result2 := chained(5) // double(5) then increment: (5 * 2) + 1 = 11
|
||||
//
|
||||
// # Monoid Operations
|
||||
//
|
||||
// Endomorphisms form a monoid, which means you can combine multiple endomorphisms:
|
||||
// Endomorphisms form a monoid, which means you can combine multiple endomorphisms.
|
||||
// The monoid uses Compose, which executes RIGHT-TO-LEFT:
|
||||
//
|
||||
// import (
|
||||
// "github.com/IBM/fp-go/v2/endomorphism"
|
||||
@@ -55,22 +60,39 @@
|
||||
// // Get the monoid for int endomorphisms
|
||||
// monoid := endomorphism.Monoid[int]()
|
||||
//
|
||||
// // Combine multiple endomorphisms
|
||||
// // Combine multiple endomorphisms (RIGHT-TO-LEFT execution)
|
||||
// combined := M.ConcatAll(monoid)(
|
||||
// func(x int) int { return x * 2 },
|
||||
// func(x int) int { return x + 1 },
|
||||
// func(x int) int { return x * 3 },
|
||||
// func(x int) int { return x * 2 }, // applied third
|
||||
// func(x int) int { return x + 1 }, // applied second
|
||||
// func(x int) int { return x * 3 }, // applied first
|
||||
// )
|
||||
// result := combined(5) // ((5 * 2) + 1) * 3 = 33
|
||||
// result := combined(5) // (5 * 3) = 15, (15 + 1) = 16, (16 * 2) = 32
|
||||
//
|
||||
// # Monad Operations
|
||||
//
|
||||
// The package also provides monadic operations for endomorphisms:
|
||||
// The package also provides monadic operations for endomorphisms.
|
||||
// MonadChain executes LEFT-TO-RIGHT, unlike Compose:
|
||||
//
|
||||
// // Chain allows sequencing of endomorphisms
|
||||
// // Chain allows sequencing of endomorphisms (LEFT-TO-RIGHT)
|
||||
// f := func(x int) int { return x * 2 }
|
||||
// g := func(x int) int { return x + 1 }
|
||||
// chained := endomorphism.MonadChain(f, g)
|
||||
// chained := endomorphism.MonadChain(f, g) // f first, then g
|
||||
// result := chained(5) // (5 * 2) + 1 = 11
|
||||
//
|
||||
// # Compose vs Chain
|
||||
//
|
||||
// The key difference between Compose and Chain/MonadChain is execution order:
|
||||
//
|
||||
// double := func(x int) int { return x * 2 }
|
||||
// increment := func(x int) int { return x + 1 }
|
||||
//
|
||||
// // Compose: RIGHT-TO-LEFT (mathematical composition)
|
||||
// composed := endomorphism.Compose(double, increment)
|
||||
// result1 := composed(5) // increment(5) * 2 = (5 + 1) * 2 = 12
|
||||
//
|
||||
// // MonadChain: LEFT-TO-RIGHT (sequential application)
|
||||
// chained := endomorphism.MonadChain(double, increment)
|
||||
// result2 := chained(5) // double(5) + 1 = (5 * 2) + 1 = 11
|
||||
//
|
||||
// # Type Safety
|
||||
//
|
||||
|
||||
@@ -17,115 +17,290 @@ package endomorphism
|
||||
|
||||
import (
|
||||
"github.com/IBM/fp-go/v2/function"
|
||||
"github.com/IBM/fp-go/v2/identity"
|
||||
)
|
||||
|
||||
// MonadAp applies an endomorphism to a value in a monadic context.
|
||||
// MonadAp applies an endomorphism in a function to an endomorphism value.
|
||||
//
|
||||
// This function applies the endomorphism fab to the value fa, returning the result.
|
||||
// It's the monadic application operation for endomorphisms.
|
||||
// For endomorphisms, Ap composes two endomorphisms using RIGHT-TO-LEFT composition.
|
||||
// This is the applicative functor operation for endomorphisms.
|
||||
//
|
||||
// IMPORTANT: Execution order is RIGHT-TO-LEFT (same as MonadCompose):
|
||||
// - fa is applied first to the input
|
||||
// - fab is applied to the result
|
||||
//
|
||||
// Parameters:
|
||||
// - fab: An endomorphism to apply
|
||||
// - fa: The value to apply the endomorphism to
|
||||
// - fab: An endomorphism to apply (outer function)
|
||||
// - fa: An endomorphism to apply first (inner function)
|
||||
//
|
||||
// Returns:
|
||||
// - The result of applying fab to fa
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// double := func(x int) int { return x * 2 }
|
||||
// result := endomorphism.MonadAp(double, 5) // Returns: 10
|
||||
func MonadAp[A any](fab Endomorphism[A], fa A) A {
|
||||
return identity.MonadAp(fab, fa)
|
||||
}
|
||||
|
||||
// Ap returns a function that applies a value to an endomorphism.
|
||||
//
|
||||
// This is the curried version of MonadAp. It takes a value and returns a function
|
||||
// that applies that value to any endomorphism.
|
||||
//
|
||||
// Parameters:
|
||||
// - fa: The value to be applied
|
||||
//
|
||||
// Returns:
|
||||
// - A function that takes an endomorphism and applies fa to it
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// applyFive := endomorphism.Ap(5)
|
||||
// double := func(x int) int { return x * 2 }
|
||||
// result := applyFive(double) // Returns: 10
|
||||
func Ap[A any](fa A) func(Endomorphism[A]) A {
|
||||
return identity.Ap[A](fa)
|
||||
}
|
||||
|
||||
// Compose composes two endomorphisms into a single endomorphism.
|
||||
//
|
||||
// Given two endomorphisms f1 and f2, Compose returns a new endomorphism that
|
||||
// applies f1 first, then applies f2 to the result. This is function composition:
|
||||
// Compose(f1, f2)(x) = f2(f1(x))
|
||||
//
|
||||
// Composition is associative: Compose(Compose(f, g), h) = Compose(f, Compose(g, h))
|
||||
//
|
||||
// Parameters:
|
||||
// - f1: The first endomorphism to apply
|
||||
// - f2: The second endomorphism to apply
|
||||
//
|
||||
// Returns:
|
||||
// - A new endomorphism that is the composition of f1 and f2
|
||||
// - A new endomorphism that applies fa, then fab
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// double := func(x int) int { return x * 2 }
|
||||
// increment := func(x int) int { return x + 1 }
|
||||
// doubleAndIncrement := endomorphism.Compose(double, increment)
|
||||
// result := doubleAndIncrement(5) // (5 * 2) + 1 = 11
|
||||
func Compose[A any](f1, f2 Endomorphism[A]) Endomorphism[A] {
|
||||
return function.Flow2(f1, f2)
|
||||
// result := endomorphism.MonadAp(double, increment) // Composes: double ∘ increment
|
||||
// // result(5) = double(increment(5)) = double(6) = 12
|
||||
func MonadAp[A any](fab Endomorphism[A], fa Endomorphism[A]) Endomorphism[A] {
|
||||
return MonadCompose(fab, fa)
|
||||
}
|
||||
|
||||
// MonadChain chains two endomorphisms together.
|
||||
// Ap returns a function that applies an endomorphism to another endomorphism.
|
||||
//
|
||||
// This is the monadic bind operation for endomorphisms. It composes two endomorphisms
|
||||
// ma and f, returning a new endomorphism that applies ma first, then f.
|
||||
// MonadChain is equivalent to Compose.
|
||||
// This is the curried version of MonadAp. It takes an endomorphism fa and returns
|
||||
// a function that composes any endomorphism with fa using RIGHT-TO-LEFT composition.
|
||||
//
|
||||
// IMPORTANT: Execution order is RIGHT-TO-LEFT:
|
||||
// - fa is applied first to the input
|
||||
// - The endomorphism passed to the returned function is applied to the result
|
||||
//
|
||||
// Parameters:
|
||||
// - ma: The first endomorphism in the chain
|
||||
// - f: The second endomorphism in the chain
|
||||
// - fa: The first endomorphism to apply (inner function)
|
||||
//
|
||||
// Returns:
|
||||
// - A new endomorphism that chains ma and f
|
||||
// - A function that takes an endomorphism and composes it with fa (right-to-left)
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// increment := func(x int) int { return x + 1 }
|
||||
// applyIncrement := endomorphism.Ap(increment)
|
||||
// double := func(x int) int { return x * 2 }
|
||||
// composed := applyIncrement(double) // double ∘ increment
|
||||
// // composed(5) = double(increment(5)) = double(6) = 12
|
||||
func Ap[A any](fa Endomorphism[A]) Operator[A] {
|
||||
return Compose(fa)
|
||||
}
|
||||
|
||||
// MonadCompose composes two endomorphisms, executing them from right to left.
|
||||
//
|
||||
// MonadCompose creates a new endomorphism that applies f2 first, then f1.
|
||||
// This follows the mathematical notation of function composition: (f1 ∘ f2)(x) = f1(f2(x))
|
||||
//
|
||||
// IMPORTANT: The execution order is RIGHT-TO-LEFT:
|
||||
// - f2 is applied first to the input
|
||||
// - f1 is applied to the result of f2
|
||||
//
|
||||
// This is different from Chain/MonadChain which executes LEFT-TO-RIGHT.
|
||||
//
|
||||
// Parameters:
|
||||
// - f1: The second function to apply (outer function)
|
||||
// - f2: The first function to apply (inner function)
|
||||
//
|
||||
// Returns:
|
||||
// - A new endomorphism that applies f2, then f1
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// double := func(x int) int { return x * 2 }
|
||||
// increment := func(x int) int { return x + 1 }
|
||||
//
|
||||
// // MonadCompose executes RIGHT-TO-LEFT: increment first, then double
|
||||
// composed := endomorphism.MonadCompose(double, increment)
|
||||
// result := composed(5) // (5 + 1) * 2 = 12
|
||||
//
|
||||
// // Compare with Chain which executes LEFT-TO-RIGHT:
|
||||
// chained := endomorphism.MonadChain(double, increment)
|
||||
// result2 := chained(5) // (5 * 2) + 1 = 11
|
||||
func MonadCompose[A any](f, g Endomorphism[A]) Endomorphism[A] {
|
||||
return function.Flow2(g, f)
|
||||
}
|
||||
|
||||
// MonadMap maps an endomorphism over another endomorphism using function composition.
|
||||
//
|
||||
// For endomorphisms, Map is equivalent to Compose (RIGHT-TO-LEFT composition).
|
||||
// This is the functor map operation for endomorphisms.
|
||||
//
|
||||
// IMPORTANT: Execution order is RIGHT-TO-LEFT:
|
||||
// - g is applied first to the input
|
||||
// - f is applied to the result
|
||||
//
|
||||
// Parameters:
|
||||
// - f: The function to map (outer function)
|
||||
// - g: The endomorphism to map over (inner function)
|
||||
//
|
||||
// Returns:
|
||||
// - A new endomorphism that applies g, then f
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// double := func(x int) int { return x * 2 }
|
||||
// increment := func(x int) int { return x + 1 }
|
||||
// mapped := endomorphism.MonadMap(double, increment)
|
||||
// // mapped(5) = double(increment(5)) = double(6) = 12
|
||||
func MonadMap[A any](f, g Endomorphism[A]) Endomorphism[A] {
|
||||
return MonadCompose(f, g)
|
||||
}
|
||||
|
||||
// Compose returns a function that composes an endomorphism with another, executing right to left.
|
||||
//
|
||||
// This is the curried version of MonadCompose. It takes an endomorphism g and returns
|
||||
// a function that composes any endomorphism with g, applying g first (inner function),
|
||||
// then the input endomorphism (outer function).
|
||||
//
|
||||
// IMPORTANT: Execution order is RIGHT-TO-LEFT (mathematical composition):
|
||||
// - g is applied first to the input
|
||||
// - The endomorphism passed to the returned function is applied to the result of g
|
||||
//
|
||||
// This follows the mathematical composition notation where Compose(g)(f) = f ∘ g
|
||||
//
|
||||
// Parameters:
|
||||
// - g: The first endomorphism to apply (inner function)
|
||||
//
|
||||
// Returns:
|
||||
// - A function that takes an endomorphism f and composes it with g (right-to-left)
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// increment := func(x int) int { return x + 1 }
|
||||
// composeWithIncrement := endomorphism.Compose(increment)
|
||||
// double := func(x int) int { return x * 2 }
|
||||
//
|
||||
// // Composes double with increment (RIGHT-TO-LEFT: increment first, then double)
|
||||
// composed := composeWithIncrement(double)
|
||||
// result := composed(5) // (5 + 1) * 2 = 12
|
||||
//
|
||||
// // Compare with Chain which executes LEFT-TO-RIGHT:
|
||||
// chainWithIncrement := endomorphism.Chain(increment)
|
||||
// chained := chainWithIncrement(double)
|
||||
// result2 := chained(5) // (5 * 2) + 1 = 11
|
||||
func Compose[A any](g Endomorphism[A]) Operator[A] {
|
||||
return function.Bind2nd(MonadCompose, g)
|
||||
}
|
||||
|
||||
// Map returns a function that maps an endomorphism over another endomorphism.
|
||||
//
|
||||
// This is the curried version of MonadMap. It takes an endomorphism f and returns
|
||||
// a function that maps f over any endomorphism using RIGHT-TO-LEFT composition.
|
||||
//
|
||||
// IMPORTANT: Execution order is RIGHT-TO-LEFT (same as Compose):
|
||||
// - The endomorphism passed to the returned function is applied first
|
||||
// - f is applied to the result
|
||||
//
|
||||
// For endomorphisms, Map is equivalent to Compose.
|
||||
//
|
||||
// Parameters:
|
||||
// - f: The function to map (outer function)
|
||||
//
|
||||
// Returns:
|
||||
// - A function that takes an endomorphism and maps f over it (right-to-left)
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// double := func(x int) int { return x * 2 }
|
||||
// mapDouble := endomorphism.Map(double)
|
||||
// increment := func(x int) int { return x + 1 }
|
||||
// mapped := mapDouble(increment)
|
||||
// // mapped(5) = double(increment(5)) = double(6) = 12
|
||||
func Map[A any](f Endomorphism[A]) Operator[A] {
|
||||
return Compose(f)
|
||||
}
|
||||
|
||||
// MonadChain chains two endomorphisms together, executing them from left to right.
|
||||
//
|
||||
// This is the monadic bind operation for endomorphisms. For endomorphisms, bind is
|
||||
// simply left-to-right function composition: ma is applied first, then f.
|
||||
//
|
||||
// IMPORTANT: The execution order is LEFT-TO-RIGHT:
|
||||
// - ma is applied first to the input
|
||||
// - f is applied to the result of ma
|
||||
//
|
||||
// This is different from MonadCompose which executes RIGHT-TO-LEFT.
|
||||
//
|
||||
// Parameters:
|
||||
// - ma: The first endomorphism to apply
|
||||
// - f: The second endomorphism to apply
|
||||
//
|
||||
// Returns:
|
||||
// - A new endomorphism that applies ma, then f
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// double := func(x int) int { return x * 2 }
|
||||
// increment := func(x int) int { return x + 1 }
|
||||
//
|
||||
// // MonadChain executes LEFT-TO-RIGHT: double first, then increment
|
||||
// chained := endomorphism.MonadChain(double, increment)
|
||||
// result := chained(5) // (5 * 2) + 1 = 11
|
||||
//
|
||||
// // Compare with MonadCompose which executes RIGHT-TO-LEFT:
|
||||
// composed := endomorphism.MonadCompose(increment, double)
|
||||
// result2 := composed(5) // (5 * 2) + 1 = 11 (same result, different parameter order)
|
||||
func MonadChain[A any](ma Endomorphism[A], f Endomorphism[A]) Endomorphism[A] {
|
||||
return Compose(ma, f)
|
||||
return function.Flow2(ma, f)
|
||||
}
|
||||
|
||||
// Chain returns a function that chains an endomorphism with another.
|
||||
// MonadChainFirst chains two endomorphisms but returns the result of the first.
|
||||
//
|
||||
// This is the curried version of MonadChain. It takes an endomorphism f and returns
|
||||
// a function that chains any endomorphism with f.
|
||||
// This applies ma first, then f, but discards the result of f and returns the result of ma.
|
||||
// Useful for performing side-effects while preserving the original value.
|
||||
//
|
||||
// Parameters:
|
||||
// - f: The endomorphism to chain with
|
||||
// - ma: The endomorphism whose result to keep
|
||||
// - f: The endomorphism to apply for its effect
|
||||
//
|
||||
// Returns:
|
||||
// - A function that takes an endomorphism and chains it with f
|
||||
// - A new endomorphism that applies both but returns ma's result
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// double := func(x int) int { return x * 2 }
|
||||
// log := func(x int) int { fmt.Println(x); return x }
|
||||
// chained := endomorphism.MonadChainFirst(double, log)
|
||||
// result := chained(5) // Prints 10, returns 10
|
||||
func MonadChainFirst[A any](ma Endomorphism[A], f Endomorphism[A]) Endomorphism[A] {
|
||||
return func(a A) A {
|
||||
result := ma(a)
|
||||
f(result) // Apply f for its effect
|
||||
return result // But return ma's result
|
||||
}
|
||||
}
|
||||
|
||||
// ChainFirst returns a function that chains for effect but preserves the original result.
|
||||
//
|
||||
// This is the curried version of MonadChainFirst.
|
||||
//
|
||||
// Parameters:
|
||||
// - f: The endomorphism to apply for its effect
|
||||
//
|
||||
// Returns:
|
||||
// - A function that takes an endomorphism and chains it with f, keeping the first result
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// log := func(x int) int { fmt.Println(x); return x }
|
||||
// chainLog := endomorphism.ChainFirst(log)
|
||||
// double := func(x int) int { return x * 2 }
|
||||
// chained := chainLog(double)
|
||||
// result := chained(5) // Prints 10, returns 10
|
||||
func ChainFirst[A any](f Endomorphism[A]) Operator[A] {
|
||||
return function.Bind2nd(MonadChainFirst, f)
|
||||
}
|
||||
|
||||
// Chain returns a function that chains an endomorphism with another, executing left to right.
|
||||
//
|
||||
// This is the curried version of MonadChain. It takes an endomorphism f and returns
|
||||
// a function that chains any endomorphism with f, applying the input endomorphism first,
|
||||
// then f.
|
||||
//
|
||||
// IMPORTANT: Execution order is LEFT-TO-RIGHT:
|
||||
// - The endomorphism passed to the returned function is applied first
|
||||
// - f is applied to the result
|
||||
//
|
||||
// Parameters:
|
||||
// - f: The second endomorphism to apply
|
||||
//
|
||||
// Returns:
|
||||
// - A function that takes an endomorphism and chains it with f (left-to-right)
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// increment := func(x int) int { return x + 1 }
|
||||
// chainWithIncrement := endomorphism.Chain(increment)
|
||||
// double := func(x int) int { return x * 2 }
|
||||
//
|
||||
// // Chains double (first) with increment (second)
|
||||
// chained := chainWithIncrement(double)
|
||||
// result := chained(5) // (5 * 2) + 1 = 11
|
||||
func Chain[A any](f Endomorphism[A]) Endomorphism[Endomorphism[A]] {
|
||||
func Chain[A any](f Endomorphism[A]) Operator[A] {
|
||||
return function.Bind2nd(MonadChain, f)
|
||||
}
|
||||
|
||||
@@ -76,84 +76,152 @@ func TestCurry3(t *testing.T) {
|
||||
|
||||
// TestMonadAp tests the MonadAp function
|
||||
func TestMonadAp(t *testing.T) {
|
||||
result := MonadAp(double, 5)
|
||||
assert.Equal(t, 10, result, "MonadAp should apply endomorphism to value")
|
||||
// MonadAp composes two endomorphisms (RIGHT-TO-LEFT)
|
||||
// MonadAp(double, increment) means: increment first, then double
|
||||
composed := MonadAp(double, increment)
|
||||
result := composed(5)
|
||||
assert.Equal(t, 12, result, "MonadAp should compose right-to-left: (5 + 1) * 2 = 12")
|
||||
|
||||
result2 := MonadAp(increment, 10)
|
||||
assert.Equal(t, 11, result2, "MonadAp should work with different endomorphisms")
|
||||
// Test with different order
|
||||
composed2 := MonadAp(increment, double)
|
||||
result2 := composed2(5)
|
||||
assert.Equal(t, 11, result2, "MonadAp should compose right-to-left: (5 * 2) + 1 = 11")
|
||||
|
||||
result3 := MonadAp(square, 4)
|
||||
assert.Equal(t, 16, result3, "MonadAp should work with square function")
|
||||
// Test with square
|
||||
composed3 := MonadAp(square, increment)
|
||||
result3 := composed3(5)
|
||||
assert.Equal(t, 36, result3, "MonadAp should compose right-to-left: (5 + 1) ^ 2 = 36")
|
||||
}
|
||||
|
||||
// TestAp tests the Ap function
|
||||
func TestAp(t *testing.T) {
|
||||
applyFive := Ap(5)
|
||||
// Ap is the curried version of MonadAp
|
||||
// Ap(increment) returns a function that composes with increment (RIGHT-TO-LEFT)
|
||||
applyIncrement := Ap(increment)
|
||||
|
||||
result := applyFive(double)
|
||||
assert.Equal(t, 10, result, "Ap should apply value to endomorphism")
|
||||
composed := applyIncrement(double)
|
||||
result := composed(5)
|
||||
assert.Equal(t, 12, result, "Ap should compose right-to-left: (5 + 1) * 2 = 12")
|
||||
|
||||
result2 := applyFive(increment)
|
||||
assert.Equal(t, 6, result2, "Ap should work with different endomorphisms")
|
||||
// Test with different endomorphism
|
||||
composed2 := applyIncrement(square)
|
||||
result2 := composed2(5)
|
||||
assert.Equal(t, 36, result2, "Ap should compose right-to-left: (5 + 1) ^ 2 = 36")
|
||||
|
||||
applyTen := Ap(10)
|
||||
result3 := applyTen(square)
|
||||
assert.Equal(t, 100, result3, "Ap should work with different values")
|
||||
// Test with different base endomorphism
|
||||
applyDouble := Ap(double)
|
||||
composed3 := applyDouble(increment)
|
||||
result3 := composed3(5)
|
||||
assert.Equal(t, 11, result3, "Ap should compose right-to-left: (5 * 2) + 1 = 11")
|
||||
}
|
||||
|
||||
// TestCompose tests the Compose function
|
||||
func TestCompose(t *testing.T) {
|
||||
// Test basic composition: (5 * 2) + 1 = 11
|
||||
doubleAndIncrement := Compose(double, increment)
|
||||
result := doubleAndIncrement(5)
|
||||
assert.Equal(t, 11, result, "Compose should compose endomorphisms correctly")
|
||||
// TestMonadCompose tests the MonadCompose function
|
||||
func TestMonadCompose(t *testing.T) {
|
||||
// Test basic composition: RIGHT-TO-LEFT execution
|
||||
// MonadCompose(double, increment) means: increment first, then double
|
||||
composed := MonadCompose(double, increment)
|
||||
result := composed(5)
|
||||
assert.Equal(t, 12, result, "MonadCompose should execute right-to-left: (5 + 1) * 2 = 12")
|
||||
|
||||
// Test composition order: (5 + 1) * 2 = 12
|
||||
incrementAndDouble := Compose(increment, double)
|
||||
result2 := incrementAndDouble(5)
|
||||
assert.Equal(t, 12, result2, "Compose should respect order of composition")
|
||||
// Test composition order: RIGHT-TO-LEFT execution
|
||||
// MonadCompose(increment, double) means: double first, then increment
|
||||
composed2 := MonadCompose(increment, double)
|
||||
result2 := composed2(5)
|
||||
assert.Equal(t, 11, result2, "MonadCompose should execute right-to-left: (5 * 2) + 1 = 11")
|
||||
|
||||
// Test with three compositions: ((5 * 2) + 1) * ((5 * 2) + 1) = 121
|
||||
complex := Compose(Compose(double, increment), square)
|
||||
// Test with three compositions: RIGHT-TO-LEFT execution
|
||||
// MonadCompose(MonadCompose(double, increment), square) means: square, then increment, then double
|
||||
complex := MonadCompose(MonadCompose(double, increment), square)
|
||||
result3 := complex(5)
|
||||
assert.Equal(t, 121, result3, "Compose should work with nested compositions")
|
||||
// 5 -> square -> 25 -> increment -> 26 -> double -> 52
|
||||
assert.Equal(t, 52, result3, "MonadCompose should work with nested compositions: square(5)=25, +1=26, *2=52")
|
||||
}
|
||||
|
||||
// TestMonadChain tests the MonadChain function
|
||||
func TestMonadChain(t *testing.T) {
|
||||
// MonadChain should behave like Compose
|
||||
// MonadChain executes LEFT-TO-RIGHT (first arg first, second arg second)
|
||||
chained := MonadChain(double, increment)
|
||||
result := chained(5)
|
||||
assert.Equal(t, 11, result, "MonadChain should chain endomorphisms correctly")
|
||||
assert.Equal(t, 11, result, "MonadChain should execute left-to-right: (5 * 2) + 1 = 11")
|
||||
|
||||
chained2 := MonadChain(increment, double)
|
||||
result2 := chained2(5)
|
||||
assert.Equal(t, 12, result2, "MonadChain should respect order")
|
||||
assert.Equal(t, 12, result2, "MonadChain should execute left-to-right: (5 + 1) * 2 = 12")
|
||||
|
||||
// Test with negative values
|
||||
chained3 := MonadChain(negate, increment)
|
||||
result3 := chained3(5)
|
||||
assert.Equal(t, -4, result3, "MonadChain should work with negative values")
|
||||
assert.Equal(t, -4, result3, "MonadChain should execute left-to-right: -(5) + 1 = -4")
|
||||
}
|
||||
|
||||
// TestChain tests the Chain function
|
||||
func TestChain(t *testing.T) {
|
||||
// Chain(f) returns a function that applies its argument first, then f
|
||||
chainWithIncrement := Chain(increment)
|
||||
|
||||
// chainWithIncrement(double) means: double first, then increment
|
||||
chained := chainWithIncrement(double)
|
||||
result := chained(5)
|
||||
assert.Equal(t, 11, result, "Chain should create chaining function correctly")
|
||||
assert.Equal(t, 11, result, "Chain should execute left-to-right: (5 * 2) + 1 = 11")
|
||||
|
||||
chainWithDouble := Chain(double)
|
||||
// chainWithDouble(increment) means: increment first, then double
|
||||
chained2 := chainWithDouble(increment)
|
||||
result2 := chained2(5)
|
||||
assert.Equal(t, 12, result2, "Chain should work with different endomorphisms")
|
||||
assert.Equal(t, 12, result2, "Chain should execute left-to-right: (5 + 1) * 2 = 12")
|
||||
|
||||
// Test chaining with square
|
||||
chainWithSquare := Chain(square)
|
||||
// chainWithSquare(double) means: double first, then square
|
||||
chained3 := chainWithSquare(double)
|
||||
result3 := chained3(3)
|
||||
assert.Equal(t, 36, result3, "Chain should work with square function")
|
||||
assert.Equal(t, 36, result3, "Chain should execute left-to-right: (3 * 2) ^ 2 = 36")
|
||||
}
|
||||
|
||||
// TestCompose tests the curried Compose function
|
||||
func TestCompose(t *testing.T) {
|
||||
// Compose(g) returns a function that applies g first, then its argument
|
||||
composeWithIncrement := Compose(increment)
|
||||
|
||||
// composeWithIncrement(double) means: increment first, then double
|
||||
composed := composeWithIncrement(double)
|
||||
result := composed(5)
|
||||
assert.Equal(t, 12, result, "Compose should execute right-to-left: (5 + 1) * 2 = 12")
|
||||
|
||||
composeWithDouble := Compose(double)
|
||||
// composeWithDouble(increment) means: double first, then increment
|
||||
composed2 := composeWithDouble(increment)
|
||||
result2 := composed2(5)
|
||||
assert.Equal(t, 11, result2, "Compose should execute right-to-left: (5 * 2) + 1 = 11")
|
||||
|
||||
// Test composing with square
|
||||
composeWithSquare := Compose(square)
|
||||
// composeWithSquare(double) means: square first, then double
|
||||
composed3 := composeWithSquare(double)
|
||||
result3 := composed3(3)
|
||||
assert.Equal(t, 18, result3, "Compose should execute right-to-left: (3 ^ 2) * 2 = 18")
|
||||
}
|
||||
|
||||
// TestMonadComposeVsCompose demonstrates the relationship between MonadCompose and Compose
|
||||
func TestMonadComposeVsCompose(t *testing.T) {
|
||||
double := func(x int) int { return x * 2 }
|
||||
increment := func(x int) int { return x + 1 }
|
||||
|
||||
// MonadCompose takes both functions at once
|
||||
monadComposed := MonadCompose(double, increment)
|
||||
result1 := monadComposed(5) // (5 + 1) * 2 = 12
|
||||
|
||||
// Compose is the curried version - takes one function, returns a function
|
||||
curriedCompose := Compose(increment)
|
||||
composed := curriedCompose(double)
|
||||
result2 := composed(5) // (5 + 1) * 2 = 12
|
||||
|
||||
assert.Equal(t, result1, result2, "MonadCompose and Compose should produce the same result")
|
||||
assert.Equal(t, 12, result1, "Both should execute right-to-left: (5 + 1) * 2 = 12")
|
||||
|
||||
// Demonstrate that Compose(g)(f) is equivalent to MonadCompose(f, g)
|
||||
assert.Equal(t, MonadCompose(double, increment)(5), Compose(increment)(double)(5),
|
||||
"Compose(g)(f) should equal MonadCompose(f, g)")
|
||||
}
|
||||
|
||||
// TestOf tests the Of function
|
||||
@@ -191,12 +259,14 @@ func TestIdentity(t *testing.T) {
|
||||
assert.Equal(t, 0, id(0), "Identity should work with zero")
|
||||
assert.Equal(t, -10, id(-10), "Identity should work with negative values")
|
||||
|
||||
// Identity should be neutral for composition
|
||||
composed1 := Compose(id, double)
|
||||
assert.Equal(t, 10, composed1(5), "Identity should be right neutral for composition")
|
||||
// Identity should be neutral for composition (RIGHT-TO-LEFT)
|
||||
// Compose(id, double) means: double first, then id
|
||||
composed1 := MonadCompose(id, double)
|
||||
assert.Equal(t, 10, composed1(5), "Identity should be left neutral: double(5) = 10")
|
||||
|
||||
composed2 := Compose(double, id)
|
||||
assert.Equal(t, 10, composed2(5), "Identity should be left neutral for composition")
|
||||
// Compose(double, id) means: id first, then double
|
||||
composed2 := MonadCompose(double, id)
|
||||
assert.Equal(t, 10, composed2(5), "Identity should be right neutral: id(5) then double = 10")
|
||||
|
||||
// Test with strings
|
||||
idStr := Identity[string]()
|
||||
@@ -207,10 +277,11 @@ func TestIdentity(t *testing.T) {
|
||||
func TestSemigroup(t *testing.T) {
|
||||
sg := Semigroup[int]()
|
||||
|
||||
// Test basic concat
|
||||
// Test basic concat (RIGHT-TO-LEFT execution via Compose)
|
||||
// Concat(double, increment) means: increment first, then double
|
||||
combined := sg.Concat(double, increment)
|
||||
result := combined(5)
|
||||
assert.Equal(t, 11, result, "Semigroup concat should compose endomorphisms")
|
||||
assert.Equal(t, 12, result, "Semigroup concat should execute right-to-left: (5 + 1) * 2 = 12")
|
||||
|
||||
// Test associativity: (f . g) . h = f . (g . h)
|
||||
f := double
|
||||
@@ -223,10 +294,12 @@ func TestSemigroup(t *testing.T) {
|
||||
testValue := 3
|
||||
assert.Equal(t, left(testValue), right(testValue), "Semigroup should be associative")
|
||||
|
||||
// Test with ConcatAll from semigroup package
|
||||
// Test with ConcatAll from semigroup package (RIGHT-TO-LEFT)
|
||||
// ConcatAll(double)(increment, square) means: square, then increment, then double
|
||||
combined2 := S.ConcatAll(sg)(double)([]Endomorphism[int]{increment, square})
|
||||
result2 := combined2(5)
|
||||
assert.Equal(t, 121, result2, "Semigroup should work with ConcatAll")
|
||||
// 5 -> square -> 25 -> increment -> 26 -> double -> 52
|
||||
assert.Equal(t, 52, result2, "Semigroup ConcatAll should execute right-to-left: square(5)=25, +1=26, *2=52")
|
||||
}
|
||||
|
||||
// TestMonoid tests the Monoid function
|
||||
@@ -237,19 +310,21 @@ func TestMonoid(t *testing.T) {
|
||||
empty := monoid.Empty()
|
||||
assert.Equal(t, 42, empty(42), "Monoid empty should be identity")
|
||||
|
||||
// Test right identity: x . empty = x
|
||||
// Test right identity: x . empty = x (RIGHT-TO-LEFT: empty first, then x)
|
||||
// Concat(double, empty) means: empty first, then double
|
||||
rightIdentity := monoid.Concat(double, empty)
|
||||
assert.Equal(t, 10, rightIdentity(5), "Monoid should satisfy right identity")
|
||||
assert.Equal(t, 10, rightIdentity(5), "Monoid should satisfy right identity: empty(5) then double = 10")
|
||||
|
||||
// Test left identity: empty . x = x
|
||||
// Test left identity: empty . x = x (RIGHT-TO-LEFT: x first, then empty)
|
||||
// Concat(empty, double) means: double first, then empty
|
||||
leftIdentity := monoid.Concat(empty, double)
|
||||
assert.Equal(t, 10, leftIdentity(5), "Monoid should satisfy left identity")
|
||||
assert.Equal(t, 10, leftIdentity(5), "Monoid should satisfy left identity: double(5) then empty = 10")
|
||||
|
||||
// Test ConcatAll with multiple endomorphisms
|
||||
// Test ConcatAll with multiple endomorphisms (RIGHT-TO-LEFT execution)
|
||||
combined := M.ConcatAll(monoid)([]Endomorphism[int]{double, increment, square})
|
||||
result := combined(5)
|
||||
// (5 * 2) = 10, (10 + 1) = 11, (11 * 11) = 121
|
||||
assert.Equal(t, 121, result, "Monoid should work with ConcatAll")
|
||||
// RIGHT-TO-LEFT: square(5) = 25, increment(25) = 26, double(26) = 52
|
||||
assert.Equal(t, 52, result, "Monoid ConcatAll should execute right-to-left: square(5)=25, +1=26, *2=52")
|
||||
|
||||
// Test ConcatAll with empty list should return identity
|
||||
emptyResult := M.ConcatAll(monoid)([]Endomorphism[int]{})
|
||||
@@ -294,19 +369,20 @@ func TestMonoidLaws(t *testing.T) {
|
||||
|
||||
// TestEndomorphismWithDifferentTypes tests endomorphisms with different types
|
||||
func TestEndomorphismWithDifferentTypes(t *testing.T) {
|
||||
// Test with strings
|
||||
toUpper := func(s string) string {
|
||||
// Test with strings (RIGHT-TO-LEFT execution)
|
||||
addExclamation := func(s string) string {
|
||||
return s + "!"
|
||||
}
|
||||
addPrefix := func(s string) string {
|
||||
return "Hello, " + s
|
||||
}
|
||||
|
||||
strComposed := Compose(toUpper, addPrefix)
|
||||
// Compose(addExclamation, addPrefix) means: addPrefix first, then addExclamation
|
||||
strComposed := MonadCompose(addExclamation, addPrefix)
|
||||
result := strComposed("World")
|
||||
assert.Equal(t, "Hello, World!", result, "Endomorphism should work with strings")
|
||||
assert.Equal(t, "Hello, World!", result, "Compose should execute right-to-left with strings")
|
||||
|
||||
// Test with float64
|
||||
// Test with float64 (RIGHT-TO-LEFT execution)
|
||||
doubleFloat := func(x float64) float64 {
|
||||
return x * 2.0
|
||||
}
|
||||
@@ -314,64 +390,63 @@ func TestEndomorphismWithDifferentTypes(t *testing.T) {
|
||||
return x + 1.0
|
||||
}
|
||||
|
||||
floatComposed := Compose(doubleFloat, addOne)
|
||||
// Compose(doubleFloat, addOne) means: addOne first, then doubleFloat
|
||||
floatComposed := MonadCompose(doubleFloat, addOne)
|
||||
resultFloat := floatComposed(5.5)
|
||||
assert.Equal(t, 12.0, resultFloat, "Endomorphism should work with float64")
|
||||
// 5.5 + 1.0 = 6.5, 6.5 * 2.0 = 13.0
|
||||
assert.Equal(t, 13.0, resultFloat, "Compose should execute right-to-left: (5.5 + 1.0) * 2.0 = 13.0")
|
||||
}
|
||||
|
||||
// TestComplexCompositions tests more complex composition scenarios
|
||||
func TestComplexCompositions(t *testing.T) {
|
||||
// Create a pipeline of transformations
|
||||
pipeline := Compose(
|
||||
Compose(
|
||||
Compose(double, increment),
|
||||
// Create a pipeline of transformations (RIGHT-TO-LEFT execution)
|
||||
// Innermost Compose is evaluated first in the composition chain
|
||||
pipeline := MonadCompose(
|
||||
MonadCompose(
|
||||
MonadCompose(double, increment),
|
||||
square,
|
||||
),
|
||||
negate,
|
||||
)
|
||||
|
||||
// (5 * 2) = 10, (10 + 1) = 11, (11 * 11) = 121, -(121) = -121
|
||||
// RIGHT-TO-LEFT: negate(5) = -5, square(-5) = 25, increment(25) = 26, double(26) = 52
|
||||
result := pipeline(5)
|
||||
assert.Equal(t, -121, result, "Complex composition should work correctly")
|
||||
assert.Equal(t, 52, result, "Complex composition should execute right-to-left")
|
||||
|
||||
// Test using monoid to build the same pipeline
|
||||
// Test using monoid to build the same pipeline (RIGHT-TO-LEFT)
|
||||
monoid := Monoid[int]()
|
||||
pipelineMonoid := M.ConcatAll(monoid)([]Endomorphism[int]{double, increment, square, negate})
|
||||
resultMonoid := pipelineMonoid(5)
|
||||
assert.Equal(t, -121, resultMonoid, "Monoid-based pipeline should match composition")
|
||||
// RIGHT-TO-LEFT: negate(5) = -5, square(-5) = 25, increment(25) = 26, double(26) = 52
|
||||
assert.Equal(t, 52, resultMonoid, "Monoid-based pipeline should match composition (right-to-left)")
|
||||
}
|
||||
|
||||
// TestOperatorType tests the Operator type
|
||||
func TestOperatorType(t *testing.T) {
|
||||
// Create an operator that lifts an int endomorphism to work on the length of strings
|
||||
lengthOperator := func(f Endomorphism[int]) Endomorphism[string] {
|
||||
return func(s string) string {
|
||||
newLen := f(len(s))
|
||||
if newLen > len(s) {
|
||||
// Pad with spaces
|
||||
for i := len(s); i < newLen; i++ {
|
||||
s += " "
|
||||
}
|
||||
} else if newLen < len(s) {
|
||||
// Truncate
|
||||
s = s[:newLen]
|
||||
}
|
||||
return s
|
||||
// Create an operator that transforms int endomorphisms
|
||||
// This operator takes an endomorphism and returns a new one that applies it twice
|
||||
applyTwice := func(f Endomorphism[int]) Endomorphism[int] {
|
||||
return func(x int) int {
|
||||
return f(f(x))
|
||||
}
|
||||
}
|
||||
|
||||
// Use the operator
|
||||
var op Operator[int, string] = lengthOperator
|
||||
doubleLength := op(double)
|
||||
var op Operator[int] = applyTwice
|
||||
doubleDouble := op(double)
|
||||
|
||||
result := doubleLength("hello") // len("hello") = 5, 5 * 2 = 10
|
||||
assert.Equal(t, 10, len(result), "Operator should transform endomorphisms correctly")
|
||||
assert.Equal(t, "hello ", result, "Operator should pad string correctly")
|
||||
result := doubleDouble(5) // double(double(5)) = double(10) = 20
|
||||
assert.Equal(t, 20, result, "Operator should transform endomorphisms correctly")
|
||||
|
||||
// Test with increment
|
||||
incrementTwice := op(increment)
|
||||
result2 := incrementTwice(5) // increment(increment(5)) = increment(6) = 7
|
||||
assert.Equal(t, 7, result2, "Operator should work with different endomorphisms")
|
||||
}
|
||||
|
||||
// BenchmarkCompose benchmarks the Compose function
|
||||
func BenchmarkCompose(b *testing.B) {
|
||||
composed := Compose(double, increment)
|
||||
composed := MonadCompose(double, increment)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = composed(5)
|
||||
@@ -379,6 +454,47 @@ func BenchmarkCompose(b *testing.B) {
|
||||
}
|
||||
|
||||
// BenchmarkMonoidConcatAll benchmarks ConcatAll with monoid
|
||||
// TestComposeVsChain demonstrates the key difference between Compose and Chain
|
||||
func TestComposeVsChain(t *testing.T) {
|
||||
double := func(x int) int { return x * 2 }
|
||||
increment := func(x int) int { return x + 1 }
|
||||
|
||||
// Compose executes RIGHT-TO-LEFT
|
||||
// Compose(double, increment) means: increment first, then double
|
||||
composed := MonadCompose(double, increment)
|
||||
composedResult := composed(5) // (5 + 1) * 2 = 12
|
||||
|
||||
// MonadChain executes LEFT-TO-RIGHT
|
||||
// MonadChain(double, increment) means: double first, then increment
|
||||
chained := MonadChain(double, increment)
|
||||
chainedResult := chained(5) // (5 * 2) + 1 = 11
|
||||
|
||||
assert.Equal(t, 12, composedResult, "Compose should execute right-to-left")
|
||||
assert.Equal(t, 11, chainedResult, "MonadChain should execute left-to-right")
|
||||
assert.NotEqual(t, composedResult, chainedResult, "Compose and Chain should produce different results with non-commutative operations")
|
||||
|
||||
// To get the same result with Compose, we need to reverse the order
|
||||
composedReversed := MonadCompose(increment, double)
|
||||
assert.Equal(t, chainedResult, composedReversed(5), "Compose with reversed args should match Chain")
|
||||
|
||||
// Demonstrate with a more complex example
|
||||
square := func(x int) int { return x * x }
|
||||
|
||||
// Compose: RIGHT-TO-LEFT
|
||||
composed3 := MonadCompose(MonadCompose(square, increment), double)
|
||||
// double(5) = 10, increment(10) = 11, square(11) = 121
|
||||
result1 := composed3(5)
|
||||
|
||||
// MonadChain: LEFT-TO-RIGHT
|
||||
chained3 := MonadChain(MonadChain(double, increment), square)
|
||||
// double(5) = 10, increment(10) = 11, square(11) = 121
|
||||
result2 := chained3(5)
|
||||
|
||||
assert.Equal(t, 121, result1, "Compose should execute right-to-left")
|
||||
assert.Equal(t, 121, result2, "MonadChain should execute left-to-right")
|
||||
assert.Equal(t, result1, result2, "Both should produce same result when operations are in correct order")
|
||||
}
|
||||
|
||||
func BenchmarkMonoidConcatAll(b *testing.B) {
|
||||
monoid := Monoid[int]()
|
||||
combined := M.ConcatAll(monoid)([]Endomorphism[int]{double, increment, square})
|
||||
@@ -397,3 +513,211 @@ func BenchmarkChain(b *testing.B) {
|
||||
_ = chained(5)
|
||||
}
|
||||
}
|
||||
|
||||
// TestFunctorLaws tests that endomorphisms satisfy the functor laws
|
||||
func TestFunctorLaws(t *testing.T) {
|
||||
// Functor Law 1: Identity
|
||||
// map(id) = id
|
||||
t.Run("Identity", func(t *testing.T) {
|
||||
id := Identity[int]()
|
||||
endo := double
|
||||
|
||||
// map(id)(endo) should equal endo
|
||||
mapped := MonadMap(id, endo)
|
||||
testValue := 5
|
||||
assert.Equal(t, endo(testValue), mapped(testValue), "map(id) should equal id")
|
||||
})
|
||||
|
||||
// Functor Law 2: Composition
|
||||
// map(f . g) = map(f) . map(g)
|
||||
t.Run("Composition", func(t *testing.T) {
|
||||
f := double
|
||||
g := increment
|
||||
endo := square
|
||||
|
||||
// Left side: map(f . g)(endo)
|
||||
composed := MonadCompose(f, g)
|
||||
left := MonadMap(composed, endo)
|
||||
|
||||
// Right side: map(f)(map(g)(endo))
|
||||
mappedG := MonadMap(g, endo)
|
||||
right := MonadMap(f, mappedG)
|
||||
|
||||
testValue := 3
|
||||
assert.Equal(t, left(testValue), right(testValue), "map(f . g) should equal map(f) . map(g)")
|
||||
})
|
||||
}
|
||||
|
||||
// TestApplicativeLaws tests that endomorphisms satisfy the applicative functor laws
|
||||
func TestApplicativeLaws(t *testing.T) {
|
||||
// Applicative Law 1: Identity
|
||||
// ap(id, v) = v
|
||||
t.Run("Identity", func(t *testing.T) {
|
||||
id := Identity[int]()
|
||||
v := double
|
||||
|
||||
applied := MonadAp(id, v)
|
||||
testValue := 5
|
||||
assert.Equal(t, v(testValue), applied(testValue), "ap(id, v) should equal v")
|
||||
})
|
||||
|
||||
// Applicative Law 2: Composition
|
||||
// ap(ap(ap(compose, u), v), w) = ap(u, ap(v, w))
|
||||
t.Run("Composition", func(t *testing.T) {
|
||||
u := double
|
||||
v := increment
|
||||
w := square
|
||||
|
||||
// For endomorphisms, ap is just composition
|
||||
// Left side: ap(ap(ap(compose, u), v), w) = compose(compose(u, v), w)
|
||||
left := MonadCompose(MonadCompose(u, v), w)
|
||||
|
||||
// Right side: ap(u, ap(v, w)) = compose(u, compose(v, w))
|
||||
right := MonadCompose(u, MonadCompose(v, w))
|
||||
|
||||
testValue := 3
|
||||
assert.Equal(t, left(testValue), right(testValue), "Applicative composition law")
|
||||
})
|
||||
|
||||
// Applicative Law 3: Homomorphism
|
||||
// ap(pure(f), pure(x)) = pure(f(x))
|
||||
t.Run("Homomorphism", func(t *testing.T) {
|
||||
// For endomorphisms, "pure" is just the identity function that returns a constant
|
||||
// This law is trivially satisfied for endomorphisms
|
||||
f := double
|
||||
x := 5
|
||||
|
||||
// ap(f, id) applied to x should equal f(x)
|
||||
id := Identity[int]()
|
||||
applied := MonadAp(f, id)
|
||||
assert.Equal(t, f(x), applied(x), "Homomorphism law")
|
||||
})
|
||||
}
|
||||
|
||||
// TestMonadLaws tests that endomorphisms satisfy the monad laws
|
||||
func TestMonadLaws(t *testing.T) {
|
||||
// Monad Law 1: Left Identity
|
||||
// chain(pure(a), f) = f(a)
|
||||
t.Run("LeftIdentity", func(t *testing.T) {
|
||||
// For endomorphisms, "pure" is the identity function
|
||||
// chain(id, f) = f
|
||||
id := Identity[int]()
|
||||
f := double
|
||||
|
||||
chained := MonadChain(id, f)
|
||||
testValue := 5
|
||||
assert.Equal(t, f(testValue), chained(testValue), "chain(id, f) should equal f")
|
||||
})
|
||||
|
||||
// Monad Law 2: Right Identity
|
||||
// chain(m, pure) = m
|
||||
t.Run("RightIdentity", func(t *testing.T) {
|
||||
m := double
|
||||
id := Identity[int]()
|
||||
|
||||
chained := MonadChain(m, id)
|
||||
testValue := 5
|
||||
assert.Equal(t, m(testValue), chained(testValue), "chain(m, id) should equal m")
|
||||
})
|
||||
|
||||
// Monad Law 3: Associativity
|
||||
// chain(chain(m, f), g) = chain(m, x => chain(f(x), g))
|
||||
t.Run("Associativity", func(t *testing.T) {
|
||||
m := square
|
||||
f := double
|
||||
g := increment
|
||||
|
||||
// Left side: chain(chain(m, f), g)
|
||||
left := MonadChain(MonadChain(m, f), g)
|
||||
|
||||
// Right side: chain(m, chain(f, g))
|
||||
// For simple endomorphisms (not Kleisli arrows), this simplifies to:
|
||||
right := MonadChain(m, MonadChain(f, g))
|
||||
|
||||
testValue := 3
|
||||
assert.Equal(t, left(testValue), right(testValue), "Monad associativity law")
|
||||
})
|
||||
}
|
||||
|
||||
// TestMonadComposeVsMonadChain verifies the relationship between Compose and Chain
|
||||
func TestMonadComposeVsMonadChain(t *testing.T) {
|
||||
f := double
|
||||
g := increment
|
||||
|
||||
// MonadCompose(f, g) should equal MonadChain(g, f)
|
||||
// Because Compose is right-to-left and Chain is left-to-right
|
||||
composed := MonadCompose(f, g)
|
||||
chained := MonadChain(g, f)
|
||||
|
||||
testValue := 5
|
||||
assert.Equal(t, composed(testValue), chained(testValue),
|
||||
"MonadCompose(f, g) should equal MonadChain(g, f)")
|
||||
}
|
||||
|
||||
// TestMapEqualsCompose verifies that Map is equivalent to Compose for endomorphisms
|
||||
func TestMapEqualsCompose(t *testing.T) {
|
||||
f := double
|
||||
g := increment
|
||||
|
||||
// MonadMap(f, g) should equal MonadCompose(f, g)
|
||||
mapped := MonadMap(f, g)
|
||||
composed := MonadCompose(f, g)
|
||||
|
||||
testValue := 5
|
||||
assert.Equal(t, composed(testValue), mapped(testValue),
|
||||
"MonadMap should equal MonadCompose for endomorphisms")
|
||||
|
||||
// Curried versions
|
||||
mapF := Map(f)
|
||||
composeF := Compose(f)
|
||||
|
||||
mappedG := mapF(g)
|
||||
composedG := composeF(g)
|
||||
|
||||
assert.Equal(t, composedG(testValue), mappedG(testValue),
|
||||
"Map should equal Compose for endomorphisms (curried)")
|
||||
}
|
||||
|
||||
// TestApEqualsCompose verifies that Ap is equivalent to Compose for endomorphisms
|
||||
func TestApEqualsCompose(t *testing.T) {
|
||||
f := double
|
||||
g := increment
|
||||
|
||||
// MonadAp(f, g) should equal MonadCompose(f, g)
|
||||
applied := MonadAp(f, g)
|
||||
composed := MonadCompose(f, g)
|
||||
|
||||
testValue := 5
|
||||
assert.Equal(t, composed(testValue), applied(testValue),
|
||||
"MonadAp should equal MonadCompose for endomorphisms")
|
||||
|
||||
// Curried versions
|
||||
apG := Ap(g)
|
||||
composeG := Compose(g)
|
||||
|
||||
appliedF := apG(f)
|
||||
composedF := composeG(f)
|
||||
|
||||
assert.Equal(t, composedF(testValue), appliedF(testValue),
|
||||
"Ap should equal Compose for endomorphisms (curried)")
|
||||
}
|
||||
|
||||
// TestChainFirst tests the ChainFirst operation
|
||||
func TestChainFirst(t *testing.T) {
|
||||
double := func(x int) int { return x * 2 }
|
||||
|
||||
// Track side effect
|
||||
var sideEffect int
|
||||
logEffect := func(x int) int {
|
||||
sideEffect = x
|
||||
return x + 100 // This result should be discarded
|
||||
}
|
||||
|
||||
chained := MonadChainFirst(double, logEffect)
|
||||
result := chained(5)
|
||||
|
||||
// Should return double's result (10), not logEffect's result
|
||||
assert.Equal(t, 10, result, "ChainFirst should return first result")
|
||||
// But side effect should have been executed with double's result
|
||||
assert.Equal(t, 10, sideEffect, "ChainFirst should execute second function for effect")
|
||||
}
|
||||
|
||||
@@ -88,11 +88,15 @@ func Identity[A any]() Endomorphism[A] {
|
||||
// For endomorphisms, this operation is composition (Compose). This means:
|
||||
// - Concat(f, Concat(g, h)) = Concat(Concat(f, g), h)
|
||||
//
|
||||
// IMPORTANT: Concat uses Compose, which executes RIGHT-TO-LEFT:
|
||||
// - Concat(f, g) applies g first, then f
|
||||
// - This is equivalent to Compose(f, g)
|
||||
//
|
||||
// The returned semigroup can be used with semigroup operations to combine
|
||||
// multiple endomorphisms.
|
||||
//
|
||||
// Returns:
|
||||
// - A Semigroup[Endomorphism[A]] where concat is composition
|
||||
// - A Semigroup[Endomorphism[A]] where concat is composition (right-to-left)
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
@@ -102,11 +106,11 @@ func Identity[A any]() Endomorphism[A] {
|
||||
// double := func(x int) int { return x * 2 }
|
||||
// increment := func(x int) int { return x + 1 }
|
||||
//
|
||||
// // Combine using the semigroup
|
||||
// // Combine using the semigroup (RIGHT-TO-LEFT execution)
|
||||
// combined := sg.Concat(double, increment)
|
||||
// result := combined(5) // (5 * 2) + 1 = 11
|
||||
// result := combined(5) // (5 + 1) * 2 = 12 (increment first, then double)
|
||||
func Semigroup[A any]() S.Semigroup[Endomorphism[A]] {
|
||||
return S.MakeSemigroup(Compose[A])
|
||||
return S.MakeSemigroup(MonadCompose[A])
|
||||
}
|
||||
|
||||
// Monoid returns a Monoid for endomorphisms where concat is composition and empty is identity.
|
||||
@@ -115,6 +119,10 @@ func Semigroup[A any]() S.Semigroup[Endomorphism[A]] {
|
||||
// - The binary operation is composition (Compose)
|
||||
// - The identity element is the identity function (Identity)
|
||||
//
|
||||
// IMPORTANT: Concat uses Compose, which executes RIGHT-TO-LEFT:
|
||||
// - Concat(f, g) applies g first, then f
|
||||
// - ConcatAll applies functions from right to left
|
||||
//
|
||||
// This satisfies the monoid laws:
|
||||
// - Right identity: Concat(x, Empty) = x
|
||||
// - Left identity: Concat(Empty, x) = x
|
||||
@@ -124,7 +132,7 @@ func Semigroup[A any]() S.Semigroup[Endomorphism[A]] {
|
||||
// combine multiple endomorphisms.
|
||||
//
|
||||
// Returns:
|
||||
// - A Monoid[Endomorphism[A]] with composition and identity
|
||||
// - A Monoid[Endomorphism[A]] with composition (right-to-left) and identity
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
@@ -135,9 +143,9 @@ func Semigroup[A any]() S.Semigroup[Endomorphism[A]] {
|
||||
// increment := func(x int) int { return x + 1 }
|
||||
// square := func(x int) int { return x * x }
|
||||
//
|
||||
// // Combine multiple endomorphisms
|
||||
// // Combine multiple endomorphisms (RIGHT-TO-LEFT execution)
|
||||
// combined := M.ConcatAll(monoid)(double, increment, square)
|
||||
// result := combined(5) // ((5 * 2) + 1) * ((5 * 2) + 1) = 121
|
||||
// result := combined(5) // square(increment(double(5))) = square(increment(10)) = square(11) = 121
|
||||
func Monoid[A any]() M.Monoid[Endomorphism[A]] {
|
||||
return M.MakeMonoid(Compose[A], Identity[A]())
|
||||
return M.MakeMonoid(MonadCompose[A], Identity[A]())
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ type (
|
||||
// var g endomorphism.Endomorphism[int] = increment
|
||||
Endomorphism[A any] = func(A) A
|
||||
|
||||
Kleisli[A any] = func(A) Endomorphism[A]
|
||||
|
||||
// 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 any] = Endomorphism[Endomorphism[A]]
|
||||
)
|
||||
|
||||
@@ -15,7 +15,84 @@
|
||||
|
||||
package eq
|
||||
|
||||
// Contramap implements an Equals predicate based on a mapping
|
||||
// Contramap creates an Eq[B] from an Eq[A] by providing a function that maps B to A.
|
||||
// This is a contravariant functor operation that allows you to transform equality predicates
|
||||
// by mapping the input type. It's particularly useful for comparing complex types by
|
||||
// extracting comparable fields.
|
||||
//
|
||||
// The name "contramap" comes from category theory, where it represents a contravariant
|
||||
// functor. Unlike regular map (covariant), which transforms the output, contramap
|
||||
// transforms the input in the opposite direction.
|
||||
//
|
||||
// Type Parameters:
|
||||
// - A: The type that has an existing Eq instance
|
||||
// - B: The type for which we want to create an Eq instance
|
||||
//
|
||||
// Parameters:
|
||||
// - f: A function that extracts or converts a value of type B to type A
|
||||
//
|
||||
// Returns:
|
||||
// - A function that takes an Eq[A] and returns an Eq[B]
|
||||
//
|
||||
// The resulting Eq[B] compares two B values by:
|
||||
// 1. Applying f to both values to get A values
|
||||
// 2. Using the original Eq[A] to compare those A values
|
||||
//
|
||||
// Example - Compare structs by a single field:
|
||||
//
|
||||
// type Person struct {
|
||||
// ID int
|
||||
// Name string
|
||||
// Age int
|
||||
// }
|
||||
//
|
||||
// // Compare persons by ID only
|
||||
// personEqByID := eq.Contramap(func(p Person) int {
|
||||
// return p.ID
|
||||
// })(eq.FromStrictEquals[int]())
|
||||
//
|
||||
// p1 := Person{ID: 1, Name: "Alice", Age: 30}
|
||||
// p2 := Person{ID: 1, Name: "Bob", Age: 25}
|
||||
// assert.True(t, personEqByID.Equals(p1, p2)) // Same ID, different names
|
||||
//
|
||||
// Example - Case-insensitive string comparison:
|
||||
//
|
||||
// type User struct {
|
||||
// Username string
|
||||
// Email string
|
||||
// }
|
||||
//
|
||||
// caseInsensitiveEq := eq.FromEquals(func(a, b string) bool {
|
||||
// return strings.EqualFold(a, b)
|
||||
// })
|
||||
//
|
||||
// userEqByUsername := eq.Contramap(func(u User) string {
|
||||
// return u.Username
|
||||
// })(caseInsensitiveEq)
|
||||
//
|
||||
// u1 := User{Username: "Alice", Email: "alice@example.com"}
|
||||
// u2 := User{Username: "ALICE", Email: "different@example.com"}
|
||||
// assert.True(t, userEqByUsername.Equals(u1, u2)) // Case-insensitive match
|
||||
//
|
||||
// Example - Nested field access:
|
||||
//
|
||||
// type Address struct {
|
||||
// City string
|
||||
// }
|
||||
//
|
||||
// type Person struct {
|
||||
// Name string
|
||||
// Address Address
|
||||
// }
|
||||
//
|
||||
// // Compare persons by city
|
||||
// personEqByCity := eq.Contramap(func(p Person) string {
|
||||
// return p.Address.City
|
||||
// })(eq.FromStrictEquals[string]())
|
||||
//
|
||||
// Contramap Law:
|
||||
// Contramap must satisfy: Contramap(f)(Contramap(g)(eq)) = Contramap(g ∘ f)(eq)
|
||||
// This means contramapping twice is the same as contramapping with the composed function.
|
||||
func Contramap[A, B any](f func(b B) A) func(Eq[A]) Eq[B] {
|
||||
return func(fa Eq[A]) Eq[B] {
|
||||
equals := fa.Equals
|
||||
|
||||
158
v2/eq/eq.go
158
v2/eq/eq.go
@@ -19,38 +19,188 @@ import (
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
)
|
||||
|
||||
// Eq represents an equality type class for type T.
|
||||
// It provides a way to define custom equality semantics for any type,
|
||||
// not just those that are comparable with Go's == operator.
|
||||
//
|
||||
// Type Parameters:
|
||||
// - T: The type for which equality is defined
|
||||
//
|
||||
// Methods:
|
||||
// - Equals(x, y T) bool: Returns true if x and y are considered equal
|
||||
//
|
||||
// Laws:
|
||||
// An Eq instance must satisfy the equivalence relation laws:
|
||||
// 1. Reflexivity: Equals(x, x) = true for all x
|
||||
// 2. Symmetry: Equals(x, y) = Equals(y, x) for all x, y
|
||||
// 3. Transitivity: If Equals(x, y) and Equals(y, z), then Equals(x, z)
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// // Create an equality predicate for integers
|
||||
// intEq := eq.FromStrictEquals[int]()
|
||||
// assert.True(t, intEq.Equals(42, 42))
|
||||
// assert.False(t, intEq.Equals(42, 43))
|
||||
//
|
||||
// // Create a custom equality predicate
|
||||
// caseInsensitiveEq := eq.FromEquals(func(a, b string) bool {
|
||||
// return strings.EqualFold(a, b)
|
||||
// })
|
||||
// assert.True(t, caseInsensitiveEq.Equals("Hello", "HELLO"))
|
||||
type Eq[T any] interface {
|
||||
// Equals returns true if x and y are considered equal according to this equality predicate.
|
||||
//
|
||||
// Parameters:
|
||||
// - x: The first value to compare
|
||||
// - y: The second value to compare
|
||||
//
|
||||
// Returns:
|
||||
// - true if x and y are equal, false otherwise
|
||||
Equals(x, y T) bool
|
||||
}
|
||||
|
||||
// eq is the internal implementation of the Eq interface.
|
||||
// It wraps a comparison function to provide the Eq interface.
|
||||
type eq[T any] struct {
|
||||
c func(x, y T) bool
|
||||
}
|
||||
|
||||
// Equals implements the Eq interface by delegating to the wrapped comparison function.
|
||||
func (e eq[T]) Equals(x, y T) bool {
|
||||
return e.c(x, y)
|
||||
}
|
||||
|
||||
// strictEq is a helper function that uses Go's built-in == operator for comparison.
|
||||
// It can only be used with comparable types.
|
||||
func strictEq[A comparable](a, b A) bool {
|
||||
return a == b
|
||||
}
|
||||
|
||||
// FromStrictEquals constructs an [EQ.Eq] from the canonical comparison function
|
||||
// FromStrictEquals constructs an Eq instance using Go's built-in == operator.
|
||||
// This is the most common way to create an Eq for types that support ==.
|
||||
//
|
||||
// Type Parameters:
|
||||
// - T: Must be a comparable type (supports ==)
|
||||
//
|
||||
// Returns:
|
||||
// - An Eq[T] that uses == for equality comparison
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// intEq := eq.FromStrictEquals[int]()
|
||||
// assert.True(t, intEq.Equals(42, 42))
|
||||
// assert.False(t, intEq.Equals(42, 43))
|
||||
//
|
||||
// stringEq := eq.FromStrictEquals[string]()
|
||||
// assert.True(t, stringEq.Equals("hello", "hello"))
|
||||
// assert.False(t, stringEq.Equals("hello", "world"))
|
||||
//
|
||||
// Note: For types that are not comparable or require custom equality logic,
|
||||
// use FromEquals instead.
|
||||
func FromStrictEquals[T comparable]() Eq[T] {
|
||||
return FromEquals(strictEq[T])
|
||||
}
|
||||
|
||||
// FromEquals constructs an [EQ.Eq] from the comparison function
|
||||
// FromEquals constructs an Eq instance from a custom comparison function.
|
||||
// This allows defining equality for any type, including non-comparable types
|
||||
// or types that need custom equality semantics.
|
||||
//
|
||||
// Type Parameters:
|
||||
// - T: The type for which equality is being defined (can be any type)
|
||||
//
|
||||
// Parameters:
|
||||
// - c: A function that takes two values of type T and returns true if they are equal
|
||||
//
|
||||
// Returns:
|
||||
// - An Eq[T] that uses the provided function for equality comparison
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// // Case-insensitive string equality
|
||||
// caseInsensitiveEq := eq.FromEquals(func(a, b string) bool {
|
||||
// return strings.EqualFold(a, b)
|
||||
// })
|
||||
// assert.True(t, caseInsensitiveEq.Equals("Hello", "HELLO"))
|
||||
//
|
||||
// // Approximate float equality
|
||||
// approxEq := eq.FromEquals(func(a, b float64) bool {
|
||||
// return math.Abs(a-b) < 0.0001
|
||||
// })
|
||||
// assert.True(t, approxEq.Equals(1.0, 1.00009))
|
||||
//
|
||||
// // Custom struct equality (compare by specific fields)
|
||||
// type Person struct { ID int; Name string }
|
||||
// personEq := eq.FromEquals(func(a, b Person) bool {
|
||||
// return a.ID == b.ID // Compare only by ID
|
||||
// })
|
||||
//
|
||||
// Note: The provided function should satisfy the equivalence relation laws
|
||||
// (reflexivity, symmetry, transitivity) for correct behavior.
|
||||
func FromEquals[T any](c func(x, y T) bool) Eq[T] {
|
||||
return eq[T]{c: c}
|
||||
}
|
||||
|
||||
// Empty returns the equals predicate that is always true
|
||||
// Empty returns an Eq instance that always returns true for any comparison.
|
||||
// This is the identity element for the Eq Monoid and is useful when you need
|
||||
// an equality predicate that accepts everything.
|
||||
//
|
||||
// Type Parameters:
|
||||
// - T: The type for which the always-true equality is defined
|
||||
//
|
||||
// Returns:
|
||||
// - An Eq[T] where Equals(x, y) always returns true
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// alwaysTrue := eq.Empty[int]()
|
||||
// assert.True(t, alwaysTrue.Equals(1, 2))
|
||||
// assert.True(t, alwaysTrue.Equals(42, 100))
|
||||
//
|
||||
// // Useful as identity in monoid operations
|
||||
// monoid := eq.Monoid[string]()
|
||||
// combined := monoid.Concat(eq.FromStrictEquals[string](), monoid.Empty())
|
||||
// // combined behaves the same as FromStrictEquals
|
||||
//
|
||||
// Use cases:
|
||||
// - As the identity element in Monoid operations
|
||||
// - When you need a placeholder equality that accepts everything
|
||||
// - In generic code that requires an Eq but doesn't need actual comparison
|
||||
func Empty[T any]() Eq[T] {
|
||||
return FromEquals(F.Constant2[T, T](true))
|
||||
}
|
||||
|
||||
// Equals returns a predicate to test if one value equals the other under an equals predicate
|
||||
// Equals returns a curried equality checking function.
|
||||
// This is useful for partial application and functional composition.
|
||||
//
|
||||
// Type Parameters:
|
||||
// - T: The type being compared
|
||||
//
|
||||
// Parameters:
|
||||
// - eq: The Eq instance to use for comparison
|
||||
//
|
||||
// Returns:
|
||||
// - A function that takes a value and returns another function that checks equality with that value
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// intEq := eq.FromStrictEquals[int]()
|
||||
// equals42 := eq.Equals(intEq)(42)
|
||||
//
|
||||
// assert.True(t, equals42(42))
|
||||
// assert.False(t, equals42(43))
|
||||
//
|
||||
// // Use in higher-order functions
|
||||
// numbers := []int{40, 41, 42, 43, 44}
|
||||
// filtered := array.Filter(equals42)(numbers)
|
||||
// // filtered = [42]
|
||||
//
|
||||
// // Partial application
|
||||
// equalsFunc := eq.Equals(intEq)
|
||||
// equals10 := equalsFunc(10)
|
||||
// equals20 := equalsFunc(20)
|
||||
//
|
||||
// This is particularly useful when working with functional programming patterns
|
||||
// like map, filter, and other higher-order functions.
|
||||
func Equals[T any](eq Eq[T]) func(T) func(T) bool {
|
||||
return func(other T) func(T) bool {
|
||||
return F.Bind2nd(eq.Equals, other)
|
||||
|
||||
120
v2/eq/monoid.go
120
v2/eq/monoid.go
@@ -20,6 +20,65 @@ import (
|
||||
S "github.com/IBM/fp-go/v2/semigroup"
|
||||
)
|
||||
|
||||
// Semigroup returns a Semigroup instance for Eq[A].
|
||||
// A Semigroup provides a way to combine two values of the same type.
|
||||
// For Eq, the combination uses logical AND - two values are equal only if
|
||||
// they are equal according to BOTH equality predicates.
|
||||
//
|
||||
// Type Parameters:
|
||||
// - A: The type for which equality predicates are being combined
|
||||
//
|
||||
// Returns:
|
||||
// - A Semigroup[Eq[A]] that combines equality predicates with logical AND
|
||||
//
|
||||
// The Concat operation satisfies:
|
||||
// - Associativity: Concat(Concat(x, y), z) = Concat(x, Concat(y, z))
|
||||
//
|
||||
// Example - Combine multiple equality checks:
|
||||
//
|
||||
// type User struct {
|
||||
// Username string
|
||||
// Email string
|
||||
// }
|
||||
//
|
||||
// usernameEq := eq.Contramap(func(u User) string {
|
||||
// return u.Username
|
||||
// })(eq.FromStrictEquals[string]())
|
||||
//
|
||||
// emailEq := eq.Contramap(func(u User) string {
|
||||
// return u.Email
|
||||
// })(eq.FromStrictEquals[string]())
|
||||
//
|
||||
// // Users are equal only if BOTH username AND email match
|
||||
// userEq := eq.Semigroup[User]().Concat(usernameEq, emailEq)
|
||||
//
|
||||
// u1 := User{Username: "alice", Email: "alice@example.com"}
|
||||
// u2 := User{Username: "alice", Email: "alice@example.com"}
|
||||
// u3 := User{Username: "alice", Email: "different@example.com"}
|
||||
//
|
||||
// assert.True(t, userEq.Equals(u1, u2)) // Both match
|
||||
// assert.False(t, userEq.Equals(u1, u3)) // Email differs
|
||||
//
|
||||
// Example - Combine multiple field checks:
|
||||
//
|
||||
// type Product struct {
|
||||
// ID int
|
||||
// Name string
|
||||
// Price float64
|
||||
// }
|
||||
//
|
||||
// idEq := eq.Contramap(func(p Product) int { return p.ID })(eq.FromStrictEquals[int]())
|
||||
// nameEq := eq.Contramap(func(p Product) string { return p.Name })(eq.FromStrictEquals[string]())
|
||||
// priceEq := eq.Contramap(func(p Product) float64 { return p.Price })(eq.FromStrictEquals[float64]())
|
||||
//
|
||||
// sg := eq.Semigroup[Product]()
|
||||
// // All three fields must match
|
||||
// productEq := sg.Concat(sg.Concat(idEq, nameEq), priceEq)
|
||||
//
|
||||
// Use cases:
|
||||
// - Combining multiple field comparisons for struct equality
|
||||
// - Building complex equality predicates from simpler ones
|
||||
// - Ensuring all conditions are met (logical AND of predicates)
|
||||
func Semigroup[A any]() S.Semigroup[Eq[A]] {
|
||||
return S.MakeSemigroup(func(x, y Eq[A]) Eq[A] {
|
||||
return FromEquals(func(a, b A) bool {
|
||||
@@ -28,6 +87,67 @@ func Semigroup[A any]() S.Semigroup[Eq[A]] {
|
||||
})
|
||||
}
|
||||
|
||||
// Monoid returns a Monoid instance for Eq[A].
|
||||
// A Monoid extends Semigroup with an identity element (Empty).
|
||||
// For Eq, the identity is an equality predicate that always returns true.
|
||||
//
|
||||
// Type Parameters:
|
||||
// - A: The type for which the equality monoid is defined
|
||||
//
|
||||
// Returns:
|
||||
// - A Monoid[Eq[A]] with:
|
||||
// - Concat: Combines equality predicates with logical AND (from Semigroup)
|
||||
// - Empty: An equality predicate that always returns true (identity element)
|
||||
//
|
||||
// Monoid Laws:
|
||||
// 1. Left Identity: Concat(Empty(), x) = x
|
||||
// 2. Right Identity: Concat(x, Empty()) = x
|
||||
// 3. Associativity: Concat(Concat(x, y), z) = Concat(x, Concat(y, z))
|
||||
//
|
||||
// Example - Using the identity element:
|
||||
//
|
||||
// monoid := eq.Monoid[int]()
|
||||
// intEq := eq.FromStrictEquals[int]()
|
||||
//
|
||||
// // Empty is the identity - combining with it doesn't change behavior
|
||||
// leftIdentity := monoid.Concat(monoid.Empty(), intEq)
|
||||
// rightIdentity := monoid.Concat(intEq, monoid.Empty())
|
||||
//
|
||||
// assert.True(t, leftIdentity.Equals(42, 42))
|
||||
// assert.False(t, leftIdentity.Equals(42, 43))
|
||||
// assert.True(t, rightIdentity.Equals(42, 42))
|
||||
// assert.False(t, rightIdentity.Equals(42, 43))
|
||||
//
|
||||
// Example - Empty always returns true:
|
||||
//
|
||||
// monoid := eq.Monoid[string]()
|
||||
// alwaysTrue := monoid.Empty()
|
||||
//
|
||||
// assert.True(t, alwaysTrue.Equals("hello", "world"))
|
||||
// assert.True(t, alwaysTrue.Equals("same", "same"))
|
||||
// assert.True(t, alwaysTrue.Equals("", "anything"))
|
||||
//
|
||||
// Example - Building complex equality with fold:
|
||||
//
|
||||
// type Person struct {
|
||||
// FirstName string
|
||||
// LastName string
|
||||
// Age int
|
||||
// }
|
||||
//
|
||||
// firstNameEq := eq.Contramap(func(p Person) string { return p.FirstName })(eq.FromStrictEquals[string]())
|
||||
// lastNameEq := eq.Contramap(func(p Person) string { return p.LastName })(eq.FromStrictEquals[string]())
|
||||
// ageEq := eq.Contramap(func(p Person) int { return p.Age })(eq.FromStrictEquals[int]())
|
||||
//
|
||||
// monoid := eq.Monoid[Person]()
|
||||
// // Combine all predicates - all fields must match
|
||||
// personEq := monoid.Concat(monoid.Concat(firstNameEq, lastNameEq), ageEq)
|
||||
//
|
||||
// Use cases:
|
||||
// - Providing a neutral element for equality combinations
|
||||
// - Generic algorithms that require a Monoid instance
|
||||
// - Folding multiple equality predicates into one
|
||||
// - Default "accept everything" equality predicate
|
||||
func Monoid[A any]() M.Monoid[Eq[A]] {
|
||||
return M.MakeMonoid(Semigroup[A]().Concat, Empty[A]())
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,105 @@
|
||||
|
||||
package function
|
||||
|
||||
// Flip reverses the order of parameters of a curried function
|
||||
// Flip reverses the order of parameters of a curried function.
|
||||
//
|
||||
// Given a curried function f that takes T1 then T2 and returns R,
|
||||
// Flip returns a new curried function that takes T2 then T1 and returns R.
|
||||
// This is useful when you have a curried function but need to apply its
|
||||
// arguments in a different order.
|
||||
//
|
||||
// Mathematical notation:
|
||||
// - Given: f: T1 → T2 → R
|
||||
// - Returns: g: T2 → T1 → R where g(t2)(t1) = f(t1)(t2)
|
||||
//
|
||||
// Type Parameters:
|
||||
// - T1: The type of the first parameter (becomes second after flip)
|
||||
// - T2: The type of the second parameter (becomes first after flip)
|
||||
// - R: The return type
|
||||
//
|
||||
// Parameters:
|
||||
// - f: A curried function taking T1 then T2 and returning R
|
||||
//
|
||||
// Returns:
|
||||
// - A new curried function taking T2 then T1 and returning R
|
||||
//
|
||||
// Relationship to Swap:
|
||||
//
|
||||
// Flip is the curried version of Swap. While Swap works with binary functions,
|
||||
// Flip works with curried functions:
|
||||
// - Swap: func(T1, T2) R → func(T2, T1) R
|
||||
// - Flip: func(T1) func(T2) R → func(T2) func(T1) R
|
||||
//
|
||||
// Example - Basic usage:
|
||||
//
|
||||
// // Create a curried division function
|
||||
// divide := Curry2(func(a, b float64) float64 { return a / b })
|
||||
// // divide(10)(2) = 5.0 (10 / 2)
|
||||
//
|
||||
// // Flip the parameter order
|
||||
// divideFlipped := Flip(divide)
|
||||
// // divideFlipped(10)(2) = 0.2 (2 / 10)
|
||||
//
|
||||
// Example - String formatting:
|
||||
//
|
||||
// // Curried string formatter: format(template)(value)
|
||||
// format := Curry2(func(template, value string) string {
|
||||
// return fmt.Sprintf(template, value)
|
||||
// })
|
||||
//
|
||||
// // Normal order: template first, then value
|
||||
// result1 := format("Hello, %s!")("World") // "Hello, World!"
|
||||
//
|
||||
// // Flipped order: value first, then template
|
||||
// formatFlipped := Flip(format)
|
||||
// result2 := formatFlipped("Hello, %s!")("World") // "Hello, World!"
|
||||
//
|
||||
// // Useful for partial application in different order
|
||||
// greetWorld := format("Hello, %s!")
|
||||
// greetWorld("Alice") // "Hello, Alice!"
|
||||
//
|
||||
// formatAlice := formatFlipped("Alice")
|
||||
// formatAlice("Hello, %s!") // "Hello, Alice!"
|
||||
//
|
||||
// Example - Practical use case with map operations:
|
||||
//
|
||||
// // Curried map lookup: getFrom(map)(key)
|
||||
// getFrom := Curry2(func(m map[string]int, key string) int {
|
||||
// return m[key]
|
||||
// })
|
||||
//
|
||||
// data := map[string]int{"a": 1, "b": 2, "c": 3}
|
||||
//
|
||||
// // Create a getter for this specific map
|
||||
// getValue := getFrom(data)
|
||||
// getValue("a") // 1
|
||||
//
|
||||
// // Flip to create key-first version: get(key)(map)
|
||||
// get := Flip(getFrom)
|
||||
// getA := get("a")
|
||||
// getA(data) // 1
|
||||
//
|
||||
// Example - Combining with other functional patterns:
|
||||
//
|
||||
// // Curried append: append(slice)(element)
|
||||
// appendTo := Curry2(func(slice []int, elem int) []int {
|
||||
// return append(slice, elem)
|
||||
// })
|
||||
//
|
||||
// // Flip to get: prepend(element)(slice)
|
||||
// prepend := Flip(appendTo)
|
||||
//
|
||||
// nums := []int{1, 2, 3}
|
||||
// add4 := appendTo(nums)
|
||||
// result1 := add4(4) // [1, 2, 3, 4]
|
||||
//
|
||||
// prependZero := prepend(0)
|
||||
// result2 := prependZero(nums) // [1, 2, 3, 0]
|
||||
//
|
||||
// See also:
|
||||
// - Swap: For flipping parameters of non-curried binary functions
|
||||
// - Curry2: For converting binary functions to curried form
|
||||
// - Uncurry2: For converting curried functions back to binary form
|
||||
func Flip[T1, T2, R any](f func(T1) func(T2) R) func(T2) func(T1) R {
|
||||
return func(t2 T2) func(T1) R {
|
||||
return func(t1 T1) R {
|
||||
|
||||
@@ -22,15 +22,265 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestFlip tests the Flip function with various scenarios
|
||||
func TestFlip(t *testing.T) {
|
||||
t.Run("flips string concatenation", func(t *testing.T) {
|
||||
// Create a curried function that formats strings
|
||||
format := Curry2(func(a, b string) string {
|
||||
return fmt.Sprintf("%s:%s", a, b)
|
||||
})
|
||||
|
||||
x := Curry2(func(a, b string) string {
|
||||
return fmt.Sprintf("%s:%s", a, b)
|
||||
// Original order: a then b
|
||||
assert.Equal(t, "a:b", format("a")("b"))
|
||||
assert.Equal(t, "hello:world", format("hello")("world"))
|
||||
|
||||
// Flipped order: b then a
|
||||
flipped := Flip(format)
|
||||
assert.Equal(t, "b:a", flipped("a")("b"))
|
||||
assert.Equal(t, "world:hello", flipped("hello")("world"))
|
||||
})
|
||||
|
||||
assert.Equal(t, "a:b", x("a")("b"))
|
||||
t.Run("flips numeric operations", func(t *testing.T) {
|
||||
// Curried subtraction: subtract(a)(b) = a - b
|
||||
subtract := Curry2(func(a, b int) int {
|
||||
return a - b
|
||||
})
|
||||
|
||||
y := Flip(x)
|
||||
// Original: 10 - 3 = 7
|
||||
assert.Equal(t, 7, subtract(10)(3))
|
||||
|
||||
assert.Equal(t, "b:a", y("a")("b"))
|
||||
// Flipped: 3 - 10 = -7
|
||||
flipped := Flip(subtract)
|
||||
assert.Equal(t, -7, flipped(10)(3))
|
||||
})
|
||||
|
||||
t.Run("flips division", func(t *testing.T) {
|
||||
// Curried division: divide(a)(b) = a / b
|
||||
divide := Curry2(func(a, b float64) float64 {
|
||||
return a / b
|
||||
})
|
||||
|
||||
// Original: 10 / 2 = 5.0
|
||||
assert.Equal(t, 5.0, divide(10)(2))
|
||||
|
||||
// Flipped: 2 / 10 = 0.2
|
||||
flipped := Flip(divide)
|
||||
assert.Equal(t, 0.2, flipped(10)(2))
|
||||
})
|
||||
|
||||
t.Run("flips with partial application", func(t *testing.T) {
|
||||
// Curried append-like operation
|
||||
prepend := Curry2(func(prefix, text string) string {
|
||||
return prefix + text
|
||||
})
|
||||
|
||||
// Create specialized functions with original order
|
||||
addHello := prepend("Hello, ")
|
||||
assert.Equal(t, "Hello, World", addHello("World"))
|
||||
assert.Equal(t, "Hello, Go", addHello("Go"))
|
||||
|
||||
// Flip and create specialized functions with reversed order
|
||||
flipped := Flip(prepend)
|
||||
addToWorld := flipped("World")
|
||||
assert.Equal(t, "Hello, World", addToWorld("Hello, "))
|
||||
assert.Equal(t, "Goodbye, World", addToWorld("Goodbye, "))
|
||||
})
|
||||
|
||||
t.Run("flips with different types", func(t *testing.T) {
|
||||
// Curried function with different input types
|
||||
repeat := Curry2(func(s string, n int) string {
|
||||
result := ""
|
||||
for i := 0; i < n; i++ {
|
||||
result += s
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
// Original: repeat("x")(3) = "xxx"
|
||||
assert.Equal(t, "xxx", repeat("x")(3))
|
||||
assert.Equal(t, "abab", repeat("ab")(2))
|
||||
|
||||
// Flipped: repeat(3)("x") = "xxx"
|
||||
flipped := Flip(repeat)
|
||||
assert.Equal(t, "xxx", flipped(3)("x"))
|
||||
assert.Equal(t, "abab", flipped(2)("ab"))
|
||||
})
|
||||
|
||||
t.Run("double flip returns to original", func(t *testing.T) {
|
||||
// Flipping twice should return to original behavior
|
||||
original := Curry2(func(a, b string) string {
|
||||
return a + "-" + b
|
||||
})
|
||||
|
||||
flipped := Flip(original)
|
||||
doubleFlipped := Flip(flipped)
|
||||
|
||||
// Original and double-flipped should behave the same
|
||||
assert.Equal(t, original("a")("b"), doubleFlipped("a")("b"))
|
||||
assert.Equal(t, "a-b", doubleFlipped("a")("b"))
|
||||
})
|
||||
|
||||
t.Run("flips with complex types", func(t *testing.T) {
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
// Curried function creating a person
|
||||
makePerson := Curry2(func(name string, age int) Person {
|
||||
return Person{Name: name, Age: age}
|
||||
})
|
||||
|
||||
// Original order: name then age
|
||||
alice := makePerson("Alice")(30)
|
||||
assert.Equal(t, "Alice", alice.Name)
|
||||
assert.Equal(t, 30, alice.Age)
|
||||
|
||||
// Flipped order: age then name
|
||||
flipped := Flip(makePerson)
|
||||
bob := flipped(25)("Bob")
|
||||
assert.Equal(t, "Bob", bob.Name)
|
||||
assert.Equal(t, 25, bob.Age)
|
||||
})
|
||||
|
||||
t.Run("flips map operations", func(t *testing.T) {
|
||||
// Curried map getter: get(map)(key)
|
||||
get := Curry2(func(m map[string]int, key string) int {
|
||||
return m[key]
|
||||
})
|
||||
|
||||
data := map[string]int{"a": 1, "b": 2, "c": 3}
|
||||
|
||||
// Original: provide map first, then key
|
||||
getValue := get(data)
|
||||
assert.Equal(t, 1, getValue("a"))
|
||||
assert.Equal(t, 2, getValue("b"))
|
||||
|
||||
// Flipped: provide key first, then map
|
||||
flipped := Flip(get)
|
||||
getA := flipped("a")
|
||||
assert.Equal(t, 1, getA(data))
|
||||
|
||||
data2 := map[string]int{"a": 10, "b": 20}
|
||||
assert.Equal(t, 10, getA(data2))
|
||||
})
|
||||
|
||||
t.Run("flips boolean operations", func(t *testing.T) {
|
||||
// Curried logical operation
|
||||
implies := Curry2(func(a, b bool) bool {
|
||||
return !a || b
|
||||
})
|
||||
|
||||
// Test truth table for implication
|
||||
assert.True(t, implies(true)(true)) // T → T = T
|
||||
assert.False(t, implies(true)(false)) // T → F = F
|
||||
assert.True(t, implies(false)(true)) // F → T = T
|
||||
assert.True(t, implies(false)(false)) // F → F = T
|
||||
|
||||
// Flipped version (reverse implication)
|
||||
flipped := Flip(implies)
|
||||
assert.True(t, flipped(true)(true)) // T ← T = T
|
||||
assert.True(t, flipped(true)(false)) // T ← F = T
|
||||
assert.False(t, flipped(false)(true)) // F ← T = F
|
||||
assert.True(t, flipped(false)(false)) // F ← F = T
|
||||
})
|
||||
|
||||
t.Run("flips with slice operations", func(t *testing.T) {
|
||||
// Curried slice append
|
||||
appendTo := Curry2(func(slice []int, elem int) []int {
|
||||
return append(slice, elem)
|
||||
})
|
||||
|
||||
nums := []int{1, 2, 3}
|
||||
|
||||
// Original: provide slice first, then element
|
||||
add4 := appendTo(nums)
|
||||
result1 := add4(4)
|
||||
assert.Equal(t, []int{1, 2, 3, 4}, result1)
|
||||
|
||||
// Flipped: provide element first, then slice
|
||||
flipped := Flip(appendTo)
|
||||
appendFive := flipped(5)
|
||||
result2 := appendFive(nums)
|
||||
assert.Equal(t, []int{1, 2, 3, 5}, result2)
|
||||
})
|
||||
}
|
||||
|
||||
// TestFlipProperties tests mathematical properties of Flip
|
||||
func TestFlipProperties(t *testing.T) {
|
||||
t.Run("flip is involutive (flip . flip = id)", func(t *testing.T) {
|
||||
// Flipping twice should give back the original function behavior
|
||||
original := Curry2(func(a, b int) int {
|
||||
return a*10 + b
|
||||
})
|
||||
|
||||
flipped := Flip(original)
|
||||
doubleFlipped := Flip(flipped)
|
||||
|
||||
// Test with multiple inputs
|
||||
testCases := []struct{ a, b int }{
|
||||
{1, 2},
|
||||
{5, 7},
|
||||
{0, 0},
|
||||
{-1, 3},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
assert.Equal(t,
|
||||
original(tc.a)(tc.b),
|
||||
doubleFlipped(tc.a)(tc.b),
|
||||
"flip(flip(f)) should equal f for inputs (%d, %d)", tc.a, tc.b)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("flip preserves function composition", func(t *testing.T) {
|
||||
// If we have f: A → B → C and g: C → D
|
||||
// then g ∘ f(a)(b) = g(f(a)(b))
|
||||
// and g ∘ flip(f)(b)(a) = g(flip(f)(b)(a))
|
||||
|
||||
f := Curry2(func(a, b int) int {
|
||||
return a + b
|
||||
})
|
||||
|
||||
g := func(n int) int {
|
||||
return n * 2
|
||||
}
|
||||
|
||||
flippedF := Flip(f)
|
||||
|
||||
// Compose g with f
|
||||
composed1 := func(a, b int) int {
|
||||
return g(f(a)(b))
|
||||
}
|
||||
|
||||
// Compose g with flipped f
|
||||
composed2 := func(a, b int) int {
|
||||
return g(flippedF(b)(a))
|
||||
}
|
||||
|
||||
// Both should give the same result
|
||||
assert.Equal(t, composed1(3, 5), composed2(3, 5))
|
||||
assert.Equal(t, 16, composed1(3, 5)) // (3 + 5) * 2 = 16
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkFlip benchmarks the Flip function
|
||||
func BenchmarkFlip(b *testing.B) {
|
||||
add := Curry2(func(a, b int) int {
|
||||
return a + b
|
||||
})
|
||||
|
||||
flipped := Flip(add)
|
||||
|
||||
b.Run("original", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = add(i)(i + 1)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("flipped", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = flipped(i)(i + 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -71,14 +71,14 @@ func TestConstant(t *testing.T) {
|
||||
// TestConstant1 tests the Constant1 function
|
||||
func TestConstant1(t *testing.T) {
|
||||
t.Run("ignores input and returns constant", func(t *testing.T) {
|
||||
alwaysZero := Constant1[string, int](0)
|
||||
alwaysZero := Constant1[string](0)
|
||||
assert.Equal(t, 0, alwaysZero("anything"))
|
||||
assert.Equal(t, 0, alwaysZero("something else"))
|
||||
assert.Equal(t, 0, alwaysZero(""))
|
||||
})
|
||||
|
||||
t.Run("works with different types", func(t *testing.T) {
|
||||
defaultName := Constant1[int, string]("Unknown")
|
||||
defaultName := Constant1[int]("Unknown")
|
||||
assert.Equal(t, "Unknown", defaultName(42))
|
||||
assert.Equal(t, "Unknown", defaultName(0))
|
||||
})
|
||||
@@ -87,13 +87,13 @@ func TestConstant1(t *testing.T) {
|
||||
// TestConstant2 tests the Constant2 function
|
||||
func TestConstant2(t *testing.T) {
|
||||
t.Run("ignores both inputs and returns constant", func(t *testing.T) {
|
||||
alwaysTrue := Constant2[int, string, bool](true)
|
||||
alwaysTrue := Constant2[int, string](true)
|
||||
assert.True(t, alwaysTrue(42, "test"))
|
||||
assert.True(t, alwaysTrue(0, ""))
|
||||
})
|
||||
|
||||
t.Run("works with different types", func(t *testing.T) {
|
||||
alwaysPi := Constant2[string, bool, float64](3.14)
|
||||
alwaysPi := Constant2[string, bool](3.14)
|
||||
assert.Equal(t, 3.14, alwaysPi("test", true))
|
||||
})
|
||||
}
|
||||
@@ -296,8 +296,8 @@ func TestTernary(t *testing.T) {
|
||||
isPositive := func(n int) bool { return n > 0 }
|
||||
classify := Ternary(
|
||||
isPositive,
|
||||
Constant1[int, string]("positive"),
|
||||
Constant1[int, string]("non-positive"),
|
||||
Constant1[int]("positive"),
|
||||
Constant1[int]("non-positive"),
|
||||
)
|
||||
|
||||
assert.Equal(t, "positive", classify(5))
|
||||
|
||||
@@ -28,7 +28,7 @@ func Memoize[F ~func(K) T, K comparable, T any](f F) F {
|
||||
|
||||
// ContramapMemoize converts a unary function into a unary function that caches the value depending on the parameter
|
||||
func ContramapMemoize[F ~func(A) T, KF func(A) K, A any, K comparable, T any](kf KF) func(F) F {
|
||||
return CacheCallback[func(F) F, func() func() T](kf, getOrCreate[K, T]())
|
||||
return CacheCallback[func(F) F](kf, getOrCreate[K, T]())
|
||||
}
|
||||
|
||||
// getOrCreate is a naive implementation of a cache, without bounds
|
||||
|
||||
@@ -377,7 +377,7 @@ func WithoutHeader(name string) Endomorphism {
|
||||
//
|
||||
// Deprecated: use [WithJSON] instead
|
||||
func WithJson[T any](data T) Endomorphism {
|
||||
return WithJSON[T](data)
|
||||
return WithJSON(data)
|
||||
}
|
||||
|
||||
// WithJSON creates a [Endomorphism] to send JSON payload
|
||||
|
||||
@@ -36,7 +36,7 @@ var (
|
||||
|
||||
func TestLaws(t *testing.T) {
|
||||
name := "Content-Type"
|
||||
fieldLaws := LT.AssertLaws[url.Values, O.Option[string]](t, O.Eq(sEq), valuesEq)(AtValue(name))
|
||||
fieldLaws := LT.AssertLaws(t, O.Eq(sEq), valuesEq)(AtValue(name))
|
||||
|
||||
n := O.None[string]()
|
||||
s1 := O.Some("s1")
|
||||
|
||||
@@ -36,7 +36,7 @@ var (
|
||||
|
||||
func TestLaws(t *testing.T) {
|
||||
name := "Content-Type"
|
||||
fieldLaws := LT.AssertLaws[http.Header, O.Option[string]](t, O.Eq(sEq), valuesEq)(AtValue(name))
|
||||
fieldLaws := LT.AssertLaws(t, O.Eq(sEq), valuesEq)(AtValue(name))
|
||||
|
||||
n := O.None[string]()
|
||||
s1 := O.Some("s1")
|
||||
|
||||
@@ -83,12 +83,12 @@ func TestMonadMap(t *testing.T) {
|
||||
|
||||
func TestMapTo(t *testing.T) {
|
||||
t.Run("replaces with constant int", func(t *testing.T) {
|
||||
result := F.Pipe1("ignored", MapTo[string, int](100))
|
||||
result := F.Pipe1("ignored", MapTo[string](100))
|
||||
assert.Equal(t, 100, result)
|
||||
})
|
||||
|
||||
t.Run("replaces with constant string", func(t *testing.T) {
|
||||
result := F.Pipe1(42, MapTo[int, string]("constant"))
|
||||
result := F.Pipe1(42, MapTo[int]("constant"))
|
||||
assert.Equal(t, "constant", result)
|
||||
})
|
||||
}
|
||||
@@ -165,7 +165,7 @@ func TestMonadChainFirst(t *testing.T) {
|
||||
|
||||
func TestAp(t *testing.T) {
|
||||
t.Run("applies function", func(t *testing.T) {
|
||||
result := F.Pipe1(utils.Double, Ap[int, int](1))
|
||||
result := F.Pipe1(utils.Double, Ap[int](1))
|
||||
assert.Equal(t, 2, result)
|
||||
})
|
||||
|
||||
@@ -173,7 +173,7 @@ func TestAp(t *testing.T) {
|
||||
add := func(a int) func(int) int {
|
||||
return func(b int) int { return a + b }
|
||||
}
|
||||
result := F.Pipe1(add(10), Ap[int, int](5))
|
||||
result := F.Pipe1(add(10), Ap[int](5))
|
||||
assert.Equal(t, 15, result)
|
||||
})
|
||||
|
||||
@@ -181,7 +181,7 @@ func TestAp(t *testing.T) {
|
||||
toString := func(n int) string {
|
||||
return fmt.Sprintf("Number: %d", n)
|
||||
}
|
||||
result := F.Pipe1(toString, Ap[string, int](42))
|
||||
result := F.Pipe1(toString, Ap[string](42))
|
||||
assert.Equal(t, "Number: 42", result)
|
||||
})
|
||||
}
|
||||
@@ -196,7 +196,7 @@ func TestMonadAp(t *testing.T) {
|
||||
func TestFlap(t *testing.T) {
|
||||
t.Run("flips application", func(t *testing.T) {
|
||||
double := func(n int) int { return n * 2 }
|
||||
result := F.Pipe1(double, Flap[int, int](5))
|
||||
result := F.Pipe1(double, Flap[int](5))
|
||||
assert.Equal(t, 10, result)
|
||||
})
|
||||
|
||||
@@ -209,7 +209,7 @@ func TestFlap(t *testing.T) {
|
||||
|
||||
results := make([]int, len(funcs))
|
||||
for i, f := range funcs {
|
||||
results[i] = Flap[int, int](5)(f)
|
||||
results[i] = Flap[int](5)(f)
|
||||
}
|
||||
|
||||
assert.Equal(t, []int{10, 15, 25}, results)
|
||||
|
||||
@@ -28,7 +28,7 @@ func Slice[GA ~[]A, A any](low, high int) func(as GA) GA {
|
||||
}
|
||||
|
||||
if low > length {
|
||||
return Empty[GA, A]()
|
||||
return Empty[GA]()
|
||||
}
|
||||
|
||||
// End index > array length: slice to the end
|
||||
@@ -38,7 +38,7 @@ func Slice[GA ~[]A, A any](low, high int) func(as GA) GA {
|
||||
|
||||
// Start >= end: return empty array
|
||||
if low >= high {
|
||||
return Empty[GA, A]()
|
||||
return Empty[GA]()
|
||||
}
|
||||
|
||||
return as[low:high]
|
||||
@@ -56,7 +56,7 @@ func SliceRight[GA ~[]A, A any](start int) func(as GA) GA {
|
||||
|
||||
// Start index > array length: return empty array
|
||||
if start > length {
|
||||
return Empty[GA, A]()
|
||||
return Empty[GA]()
|
||||
}
|
||||
|
||||
return as[start:]
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -27,12 +27,12 @@ func MonadFlap[FAB ~func(A) B, A, B, HKTFAB, HKTB any](
|
||||
fab HKTFAB,
|
||||
a A,
|
||||
) HKTB {
|
||||
return fmap(fab, flap[FAB, A, B](a))
|
||||
return fmap(fab, flap[FAB](a))
|
||||
}
|
||||
|
||||
func Flap[FAB ~func(A) B, A, B, HKTFAB, HKTB any](
|
||||
fmap func(func(FAB) B) func(HKTFAB) HKTB,
|
||||
a A,
|
||||
) func(HKTFAB) HKTB {
|
||||
return fmap(flap[FAB, A, B](a))
|
||||
return fmap(flap[FAB](a))
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
@@ -217,7 +217,7 @@ func BindL[S, T any](
|
||||
lens L.Lens[S, T],
|
||||
f Kleisli[T, T],
|
||||
) Operator[S, S] {
|
||||
return Bind[S, S, T](lens.Set, F.Flow2(lens.Get, f))
|
||||
return Bind(lens.Set, F.Flow2(lens.Get, f))
|
||||
}
|
||||
|
||||
// LetL attaches the result of a pure computation to a context using a lens-based setter.
|
||||
@@ -250,7 +250,7 @@ func LetL[S, T any](
|
||||
lens L.Lens[S, T],
|
||||
f func(T) T,
|
||||
) Operator[S, S] {
|
||||
return Let[S, S, T](lens.Set, F.Flow2(lens.Get, f))
|
||||
return Let(lens.Set, F.Flow2(lens.Get, f))
|
||||
}
|
||||
|
||||
// LetToL attaches a constant value to a context using a lens-based setter.
|
||||
@@ -281,5 +281,5 @@ func LetToL[S, T any](
|
||||
lens L.Lens[S, T],
|
||||
b T,
|
||||
) Operator[S, S] {
|
||||
return LetTo[S, S, T](lens.Set, b)
|
||||
return LetTo(lens.Set, b)
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ func TestMonadMapTo(t *testing.T) {
|
||||
|
||||
// Test MapTo
|
||||
func TestMapTo(t *testing.T) {
|
||||
result := F.Pipe1(Of(1), MapTo[int, string]("hello"))
|
||||
result := F.Pipe1(Of(1), MapTo[int]("hello"))
|
||||
assert.Equal(t, "hello", result())
|
||||
}
|
||||
|
||||
@@ -276,7 +276,7 @@ func TestWithResource(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
withRes := WithResource[int, int, any](Of(onCreate()), onRelease)
|
||||
withRes := WithResource[int, int](Of(onCreate()), onRelease)
|
||||
|
||||
result := withRes(func(x int) IO[int] {
|
||||
return Of(x * 2)
|
||||
@@ -604,7 +604,7 @@ func TestSequenceSeqTuple2(t *testing.T) {
|
||||
io1 := func() int { order = append(order, 1); return 10 }
|
||||
io2 := func() int { order = append(order, 2); return 20 }
|
||||
|
||||
tup := T.MakeTuple2[IO[int], IO[int]](io1, io2)
|
||||
tup := T.MakeTuple2(io1, io2)
|
||||
result := SequenceSeqTuple2(tup)
|
||||
tuple := result()
|
||||
|
||||
@@ -804,7 +804,7 @@ func TestSequenceSeqTuple3(t *testing.T) {
|
||||
io2 := func() int { order = append(order, 2); return 20 }
|
||||
io3 := func() int { order = append(order, 3); return 30 }
|
||||
|
||||
tup := T.MakeTuple3[IO[int], IO[int], IO[int]](io1, io2, io3)
|
||||
tup := T.MakeTuple3(io1, io2, io3)
|
||||
result := SequenceSeqTuple3(tup)
|
||||
tuple := result()
|
||||
|
||||
@@ -862,7 +862,7 @@ func TestSequenceSeqTuple4(t *testing.T) {
|
||||
io3 := func() int { order = append(order, 3); return 3 }
|
||||
io4 := func() int { order = append(order, 4); return 4 }
|
||||
|
||||
tup := T.MakeTuple4[IO[int], IO[int], IO[int], IO[int]](io1, io2, io3, io4)
|
||||
tup := T.MakeTuple4(io1, io2, io3, io4)
|
||||
result := SequenceSeqTuple4(tup)
|
||||
tuple := result()
|
||||
|
||||
@@ -939,7 +939,7 @@ func TestSequenceSeqTuple1(t *testing.T) {
|
||||
var executed bool
|
||||
io1 := func() int { executed = true; return 42 }
|
||||
|
||||
tup := T.MakeTuple1[IO[int]](io1)
|
||||
tup := T.MakeTuple1(io1)
|
||||
result := SequenceSeqTuple1(tup)
|
||||
tuple := result()
|
||||
|
||||
@@ -1013,7 +1013,7 @@ func TestSequenceSeqTuple5(t *testing.T) {
|
||||
io4 := func() int { order = append(order, 4); return 4 }
|
||||
io5 := func() int { order = append(order, 5); return 5 }
|
||||
|
||||
tup := T.MakeTuple5[IO[int], IO[int], IO[int], IO[int], IO[int]](io1, io2, io3, io4, io5)
|
||||
tup := T.MakeTuple5(io1, io2, io3, io4, io5)
|
||||
result := SequenceSeqTuple5(tup)
|
||||
tuple := result()
|
||||
|
||||
@@ -1231,7 +1231,7 @@ func TestSequenceSeqTuple6(t *testing.T) {
|
||||
io5 := func() int { order = append(order, 5); return 5 }
|
||||
io6 := func() int { order = append(order, 6); return 6 }
|
||||
|
||||
tup := T.MakeTuple6[IO[int], IO[int], IO[int], IO[int], IO[int], IO[int]](io1, io2, io3, io4, io5, io6)
|
||||
tup := T.MakeTuple6(io1, io2, io3, io4, io5, io6)
|
||||
result := SequenceSeqTuple6(tup)
|
||||
tuple := result()
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ func MonadChainTo[GA ~func() A, GB ~func() B, A, B any](fa GA, fb GB) GB {
|
||||
|
||||
// ChainTo composes computations in sequence, ignoring the return value of the first computation
|
||||
func ChainTo[GA ~func() A, GB ~func() B, A, B any](fb GB) func(GA) GB {
|
||||
return Chain[GA, GB](F.Constant1[A](fb))
|
||||
return Chain[GA](F.Constant1[A](fb))
|
||||
}
|
||||
|
||||
// MonadChainFirst composes computations in sequence, using the return value of one computation to determine the next computation and
|
||||
@@ -130,7 +130,7 @@ func Flatten[GA ~func() A, GAA ~func() GA, A any](mma GAA) GA {
|
||||
|
||||
// Memoize computes the value of the provided IO monad lazily but exactly once
|
||||
func Memoize[GA ~func() A, A any](ma GA) GA {
|
||||
return L.Memoize[GA, A](ma)
|
||||
return L.Memoize(ma)
|
||||
}
|
||||
|
||||
// Delay creates an operation that passes in the value after some delay
|
||||
|
||||
@@ -233,7 +233,7 @@ func BindL[E, S, T any](
|
||||
lens L.Lens[S, T],
|
||||
f Kleisli[E, T, T],
|
||||
) Operator[E, S, S] {
|
||||
return Bind[E, S, S, T](lens.Set, F.Flow2(lens.Get, f))
|
||||
return Bind(lens.Set, F.Flow2(lens.Get, f))
|
||||
}
|
||||
|
||||
// LetL attaches the result of a pure computation to a context using a lens-based setter.
|
||||
@@ -265,7 +265,7 @@ func LetL[E, S, T any](
|
||||
lens L.Lens[S, T],
|
||||
f func(T) T,
|
||||
) Operator[E, S, S] {
|
||||
return Let[E, S, S, T](lens.Set, F.Flow2(lens.Get, f))
|
||||
return Let[E](lens.Set, F.Flow2(lens.Get, f))
|
||||
}
|
||||
|
||||
// LetToL attaches a constant value to a context using a lens-based setter.
|
||||
@@ -296,5 +296,5 @@ func LetToL[E, S, T any](
|
||||
lens L.Lens[S, T],
|
||||
b T,
|
||||
) Operator[E, S, S] {
|
||||
return LetTo[E, S, S, T](lens.Set, b)
|
||||
return LetTo[E](lens.Set, b)
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ func MonadChainTo[GA ~func() either.Either[E, A], GB ~func() either.Either[E, B]
|
||||
}
|
||||
|
||||
func ChainTo[GA ~func() either.Either[E, A], GB ~func() either.Either[E, B], E, A, B any](fb GB) func(GA) GB {
|
||||
return Chain[GA, GB, E, A, B](F.Constant1[A](fb))
|
||||
return Chain[GA](F.Constant1[A](fb))
|
||||
}
|
||||
|
||||
// Deprecated:
|
||||
@@ -394,7 +394,7 @@ func FromImpure[GA ~func() either.Either[E, any], IMP ~func(), E any](f IMP) GA
|
||||
//
|
||||
// Deprecated:
|
||||
func Defer[GEA ~func() either.Either[E, A], E, A any](gen func() GEA) GEA {
|
||||
return IO.Defer[GEA](gen)
|
||||
return IO.Defer(gen)
|
||||
}
|
||||
|
||||
// Deprecated:
|
||||
|
||||
@@ -140,6 +140,6 @@ func readJSON(client Client) func(Requester) ioeither.IOEither[error, []byte] {
|
||||
func ReadJSON[A any](client Client) func(Requester) ioeither.IOEither[error, A] {
|
||||
return F.Flow2(
|
||||
readJSON(client),
|
||||
ioeither.ChainEitherK[error](J.Unmarshal[A]),
|
||||
ioeither.ChainEitherK(J.Unmarshal[A]),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ func TestChainIOK(t *testing.T) {
|
||||
})
|
||||
|
||||
assert.Equal(t, E.Right[string]("1"), f(Right[string](1))())
|
||||
assert.Equal(t, E.Left[string, string]("b"), f(Left[int]("b"))())
|
||||
assert.Equal(t, E.Left[string]("b"), f(Left[int]("b"))())
|
||||
}
|
||||
|
||||
func TestChainWithIO(t *testing.T) {
|
||||
@@ -102,7 +102,7 @@ func TestChainFirst(t *testing.T) {
|
||||
ch := ChainFirst(f)
|
||||
|
||||
assert.Equal(t, E.Of[string]("foo"), F.Pipe1(good, ch)())
|
||||
assert.Equal(t, E.Left[string, string]("foo"), F.Pipe1(bad, ch)())
|
||||
assert.Equal(t, E.Left[string]("foo"), F.Pipe1(bad, ch)())
|
||||
}
|
||||
|
||||
func TestChainFirstIOK(t *testing.T) {
|
||||
|
||||
@@ -30,27 +30,27 @@ type (
|
||||
)
|
||||
|
||||
func (o *ioEitherPointed[E, A]) Of(a A) IOEither[E, A] {
|
||||
return Of[E, A](a)
|
||||
return Of[E](a)
|
||||
}
|
||||
|
||||
func (o *ioEitherMonad[E, A, B]) Of(a A) IOEither[E, A] {
|
||||
return Of[E, A](a)
|
||||
return Of[E](a)
|
||||
}
|
||||
|
||||
func (o *ioEitherMonad[E, A, B]) Map(f func(A) B) Operator[E, A, B] {
|
||||
return Map[E, A, B](f)
|
||||
return Map[E](f)
|
||||
}
|
||||
|
||||
func (o *ioEitherMonad[E, A, B]) Chain(f Kleisli[E, A, B]) Operator[E, A, B] {
|
||||
return Chain[E, A, B](f)
|
||||
return Chain(f)
|
||||
}
|
||||
|
||||
func (o *ioEitherMonad[E, A, B]) Ap(fa IOEither[E, A]) Operator[E, func(A) B, B] {
|
||||
return Ap[B, E, A](fa)
|
||||
return Ap[B](fa)
|
||||
}
|
||||
|
||||
func (o *ioEitherFunctor[E, A, B]) Map(f func(A) B) Operator[E, A, B] {
|
||||
return Map[E, A, B](f)
|
||||
return Map[E](f)
|
||||
}
|
||||
|
||||
// Pointed implements the pointed operations for [IOEither]
|
||||
|
||||
@@ -225,7 +225,7 @@ func BindL[S, T any](
|
||||
lens L.Lens[S, T],
|
||||
f Kleisli[T, T],
|
||||
) Kleisli[IOOption[S], S] {
|
||||
return Bind[S, S, T](lens.Set, F.Flow2(lens.Get, f))
|
||||
return Bind(lens.Set, F.Flow2(lens.Get, f))
|
||||
}
|
||||
|
||||
// LetL attaches the result of a pure computation to a context using a lens-based setter.
|
||||
@@ -258,7 +258,7 @@ func LetL[S, T any](
|
||||
lens L.Lens[S, T],
|
||||
f func(T) T,
|
||||
) Kleisli[IOOption[S], S] {
|
||||
return Let[S, S, T](lens.Set, F.Flow2(lens.Get, f))
|
||||
return Let(lens.Set, F.Flow2(lens.Get, f))
|
||||
}
|
||||
|
||||
// LetToL attaches a constant value to a context using a lens-based setter.
|
||||
@@ -289,5 +289,5 @@ func LetToL[S, T any](
|
||||
lens L.Lens[S, T],
|
||||
b T,
|
||||
) Kleisli[IOOption[S], S] {
|
||||
return LetTo[S, S, T](lens.Set, b)
|
||||
return LetTo(lens.Set, b)
|
||||
}
|
||||
|
||||
@@ -233,12 +233,12 @@ 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
|
||||
func Defer[GA ~func() O.Option[A], A any](gen func() GA) GA {
|
||||
return IO.Defer[GA](gen)
|
||||
return IO.Defer(gen)
|
||||
}
|
||||
|
||||
func MonadAlt[LAZY ~func() GIOA, GIOA ~func() O.Option[A], A any](first GIOA, second LAZY) GIOA {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ func Bind[S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
f Kleisli[S1, T],
|
||||
) Kleisli[Iterator[S1], S2] {
|
||||
return G.Bind[Iterator[S1], Iterator[S2], Iterator[T], S1, S2, T](setter, f)
|
||||
return G.Bind[Iterator[S1], Iterator[S2]](setter, f)
|
||||
}
|
||||
|
||||
// Let attaches the result of a computation to a context [S1] to produce a context [S2]
|
||||
@@ -81,7 +81,7 @@ func Let[S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
f func(S1) T,
|
||||
) Kleisli[Iterator[S1], S2] {
|
||||
return G.Let[Iterator[S1], Iterator[S2], S1, S2, T](setter, f)
|
||||
return G.Let[Iterator[S1], Iterator[S2]](setter, f)
|
||||
}
|
||||
|
||||
// LetTo attaches the a value to a context [S1] to produce a context [S2]
|
||||
@@ -89,14 +89,14 @@ func LetTo[S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
b T,
|
||||
) Kleisli[Iterator[S1], S2] {
|
||||
return G.LetTo[Iterator[S1], Iterator[S2], S1, S2, T](setter, b)
|
||||
return G.LetTo[Iterator[S1], Iterator[S2]](setter, b)
|
||||
}
|
||||
|
||||
// BindTo initializes a new state [S1] from a value [T]
|
||||
func BindTo[S1, T any](
|
||||
setter func(T) S1,
|
||||
) Kleisli[Iterator[T], S1] {
|
||||
return G.BindTo[Iterator[S1], Iterator[T], S1, T](setter)
|
||||
return G.BindTo[Iterator[S1], Iterator[T]](setter)
|
||||
}
|
||||
|
||||
// ApS attaches a value to a context [S1] to produce a context [S2] by considering
|
||||
@@ -136,5 +136,5 @@ func ApS[S1, S2, T any](
|
||||
setter func(T) func(S1) S2,
|
||||
fa Iterator[T],
|
||||
) Kleisli[Iterator[S1], S2] {
|
||||
return G.ApS[Iterator[func(T) S2], Iterator[S1], Iterator[S2], Iterator[T], S1, S2, T](setter, fa)
|
||||
return G.ApS[Iterator[func(T) S2], Iterator[S1], Iterator[S2]](setter, fa)
|
||||
}
|
||||
|
||||
@@ -22,5 +22,5 @@ import (
|
||||
// DropWhile creates an [Iterator] that drops elements from the [Iterator] as long as the predicate is true; afterwards, returns every element.
|
||||
// Note, the [Iterator] does not produce any output until the predicate first becomes false
|
||||
func Cycle[U any](ma Iterator[U]) Iterator[U] {
|
||||
return G.Cycle[Iterator[U]](ma)
|
||||
return G.Cycle(ma)
|
||||
}
|
||||
|
||||
@@ -22,5 +22,5 @@ import (
|
||||
|
||||
// First returns the first item in an iterator if such an item exists
|
||||
func First[U any](mu Iterator[U]) O.Option[U] {
|
||||
return G.First[Iterator[U]](mu)
|
||||
return G.First(mu)
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ func Ap[GUV ~func() O.Option[P.Pair[GUV, func(U) V]], GV ~func() O.Option[P.Pair
|
||||
}
|
||||
|
||||
func MonadAp[GUV ~func() O.Option[P.Pair[GUV, func(U) V]], GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.Pair[GU, U]], U, V any](fab GUV, ma GU) GV {
|
||||
return Ap[GUV, GV, GU](ma)(fab)
|
||||
return Ap[GUV, GV](ma)(fab)
|
||||
}
|
||||
|
||||
func FilterChain[GVV ~func() O.Option[P.Pair[GVV, GV]], GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.Pair[GU, U]], FCT ~func(U) O.Option[GV], U, V any](f FCT) func(ma GU) GV {
|
||||
|
||||
@@ -24,19 +24,19 @@ import (
|
||||
type iteratorMonad[A, B any, GA ~func() O.Option[P.Pair[GA, A]], GB ~func() O.Option[P.Pair[GB, B]], GAB ~func() O.Option[P.Pair[GAB, func(A) B]]] struct{}
|
||||
|
||||
func (o *iteratorMonad[A, B, GA, GB, GAB]) Of(a A) GA {
|
||||
return Of[GA, A](a)
|
||||
return Of[GA](a)
|
||||
}
|
||||
|
||||
func (o *iteratorMonad[A, B, GA, GB, GAB]) Map(f func(A) B) func(GA) GB {
|
||||
return Map[GB, GA, func(A) B, A, B](f)
|
||||
return Map[GB, GA](f)
|
||||
}
|
||||
|
||||
func (o *iteratorMonad[A, B, GA, GB, GAB]) Chain(f func(A) GB) func(GA) GB {
|
||||
return Chain[GB, GA, A, B](f)
|
||||
return Chain[GB, GA](f)
|
||||
}
|
||||
|
||||
func (o *iteratorMonad[A, B, GA, GB, GAB]) Ap(fa GA) func(GAB) GB {
|
||||
return Ap[GAB, GB, GA, A, B](fa)
|
||||
return Ap[GAB, GB](fa)
|
||||
}
|
||||
|
||||
// Monad implements the monadic operations for iterators
|
||||
|
||||
@@ -23,10 +23,10 @@ import (
|
||||
|
||||
// FromLazy returns an [Iterator] on top of a lazy function
|
||||
func FromLazy[U any](l L.Lazy[U]) Iterator[U] {
|
||||
return G.FromLazy[Iterator[U], L.Lazy[U]](l)
|
||||
return G.FromLazy[Iterator[U]](l)
|
||||
}
|
||||
|
||||
// FromIO returns an [Iterator] on top of an IO function
|
||||
func FromIO[U any](io IO.IO[U]) Iterator[U] {
|
||||
return G.FromLazy[Iterator[U], IO.IO[U]](io)
|
||||
return G.FromLazy[Iterator[U]](io)
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ func Reduce[U, V any](f func(V, U) V, initial V) func(Iterator[U]) V {
|
||||
|
||||
// MonadMap transforms an [Iterator] of type [U] into an [Iterator] of type [V] via a mapping function
|
||||
func MonadMap[U, V any](ma Iterator[U], f func(U) V) Iterator[V] {
|
||||
return G.MonadMap[Iterator[V], Iterator[U]](ma, f)
|
||||
return G.MonadMap[Iterator[V]](ma, f)
|
||||
}
|
||||
|
||||
// Map transforms an [Iterator] of type [U] into an [Iterator] of type [V] via a mapping function
|
||||
@@ -69,7 +69,7 @@ func Map[U, V any](f func(U) V) Operator[U, V] {
|
||||
}
|
||||
|
||||
func MonadChain[U, V any](ma Iterator[U], f Kleisli[U, V]) Iterator[V] {
|
||||
return G.MonadChain[Iterator[V], Iterator[U]](ma, f)
|
||||
return G.MonadChain(ma, f)
|
||||
}
|
||||
|
||||
func Chain[U, V any](f Kleisli[U, V]) Kleisli[Iterator[U], V] {
|
||||
@@ -78,7 +78,7 @@ func Chain[U, V any](f Kleisli[U, V]) Kleisli[Iterator[U], V] {
|
||||
|
||||
// Flatten converts an [Iterator] of [Iterator] into a simple [Iterator]
|
||||
func Flatten[U any](ma Iterator[Iterator[U]]) Iterator[U] {
|
||||
return G.Flatten[Iterator[Iterator[U]], Iterator[U]](ma)
|
||||
return G.Flatten(ma)
|
||||
}
|
||||
|
||||
// From constructs an [Iterator] from a set of variadic arguments
|
||||
@@ -134,7 +134,7 @@ func FilterChain[U, V any](f func(U) O.Option[Iterator[V]]) Operator[U, V] {
|
||||
|
||||
// FoldMap maps and folds an iterator. Map the iterator passing each value to the iterating function. Then fold the results using the provided Monoid.
|
||||
func FoldMap[U, V any](m M.Monoid[V]) func(func(U) V) func(ma Iterator[U]) V {
|
||||
return G.FoldMap[Iterator[U], func(U) V, U, V](m)
|
||||
return G.FoldMap[Iterator[U], func(U) V](m)
|
||||
}
|
||||
|
||||
// Fold folds the iterator using the provided Monoid.
|
||||
@@ -143,9 +143,9 @@ func Fold[U any](m M.Monoid[U]) func(Iterator[U]) U {
|
||||
}
|
||||
|
||||
func MonadChainFirst[U, V any](ma Iterator[U], f Kleisli[U, V]) Iterator[U] {
|
||||
return G.MonadChainFirst[Iterator[V], Iterator[U], U, V](ma, f)
|
||||
return G.MonadChainFirst(ma, f)
|
||||
}
|
||||
|
||||
func ChainFirst[U, V any](f Kleisli[U, V]) Operator[U, U] {
|
||||
return G.ChainFirst[Iterator[V], Iterator[U], U, V](f)
|
||||
return G.ChainFirst[Iterator[V], Iterator[U]](f)
|
||||
}
|
||||
|
||||
@@ -23,5 +23,5 @@ import (
|
||||
// Last returns the last item in an iterator if such an item exists
|
||||
// Note that the function will consume the [Iterator] in this call completely, to identify the last element. Do not use this for infinite iterators
|
||||
func Last[U any](mu Iterator[U]) O.Option[U] {
|
||||
return G.Last[Iterator[U]](mu)
|
||||
return G.Last(mu)
|
||||
}
|
||||
|
||||
@@ -23,5 +23,5 @@ import (
|
||||
// of the new [Iterator] are the result of the application of `f` to the value of the
|
||||
// source iterator with the previously accumulated value
|
||||
func Scan[FCT ~func(V, U) V, U, V any](f FCT, initial V) func(ma Iterator[U]) Iterator[V] {
|
||||
return G.Scan[Iterator[V], Iterator[U], FCT](f, initial)
|
||||
return G.Scan[Iterator[V], Iterator[U]](f, initial)
|
||||
}
|
||||
|
||||
@@ -22,11 +22,11 @@ import (
|
||||
// StrictUniq converts an [Iterator] of arbitrary items into an [Iterator] or unique items
|
||||
// where uniqueness is determined by the built-in uniqueness constraint
|
||||
func StrictUniq[A comparable](as Iterator[A]) Iterator[A] {
|
||||
return G.StrictUniq[Iterator[A]](as)
|
||||
return G.StrictUniq(as)
|
||||
}
|
||||
|
||||
// Uniq converts an [Iterator] of arbitrary items into an [Iterator] or unique items
|
||||
// where uniqueness is determined based on a key extractor function
|
||||
func Uniq[A any, K comparable](f func(A) K) func(as Iterator[A]) Iterator[A] {
|
||||
return G.Uniq[Iterator[A], K](f)
|
||||
return G.Uniq[Iterator[A]](f)
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -36,7 +36,7 @@ func TestChain(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAp(t *testing.T) {
|
||||
assert.Equal(t, 2, F.Pipe1(Of(utils.Double), Ap[int, int](Of(1)))())
|
||||
assert.Equal(t, 2, F.Pipe1(Of(utils.Double), Ap[int](Of(1)))())
|
||||
}
|
||||
|
||||
func TestFlatten(t *testing.T) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -51,7 +75,7 @@ func TraverseRecord[K comparable, A, B any](f Kleisli[A, B]) Kleisli[map[K]A, ma
|
||||
// TraverseRecord applies a function returning an [IO] to all elements in a record and the
|
||||
// transforms this into an [IO] of that record
|
||||
func TraverseRecordWithIndex[K comparable, A, B any](f func(K, A) Lazy[B]) Kleisli[map[K]A, map[K]B] {
|
||||
return io.TraverseRecordWithIndex[K](f)
|
||||
return io.TraverseRecordWithIndex(f)
|
||||
}
|
||||
|
||||
// SequenceRecord converts a record of [IO] to an [IO] of a record
|
||||
|
||||
@@ -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]
|
||||
)
|
||||
|
||||
@@ -65,7 +65,7 @@ import (
|
||||
// assert.Equal(t, f1("test"), funcMonoid.Concat(f1, funcMonoid.Empty())("test"))
|
||||
func FunctionMonoid[A, B any](m Monoid[B]) Monoid[func(A) B] {
|
||||
return MakeMonoid(
|
||||
S.FunctionSemigroup[A, B](m).Concat,
|
||||
S.FunctionSemigroup[A](m).Concat,
|
||||
F.Constant1[A](m.Empty()),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ func MakeMonoid[A any](c func(A, A) A, e A) Monoid[A] {
|
||||
// reversed := Reverse(stringMonoid)
|
||||
// result := reversed.Concat("Hello", "World") // "WorldHello"
|
||||
func Reverse[A any](m Monoid[A]) Monoid[A] {
|
||||
return MakeMonoid(S.Reverse[A](m).Concat, m.Empty())
|
||||
return MakeMonoid(S.Reverse(m).Concat, m.Empty())
|
||||
}
|
||||
|
||||
// ToSemigroup converts a Monoid to a Semigroup by discarding the identity element.
|
||||
|
||||
@@ -371,7 +371,7 @@ func TestFunctionMonoid(t *testing.T) {
|
||||
0,
|
||||
)
|
||||
|
||||
funcMonoid := FunctionMonoid[string, int](intAddMonoid)
|
||||
funcMonoid := FunctionMonoid[string](intAddMonoid)
|
||||
|
||||
// Create some functions
|
||||
f1 := func(s string) int { return len(s) }
|
||||
@@ -408,7 +408,7 @@ func TestFunctionMonoid_DifferentTypes(t *testing.T) {
|
||||
false,
|
||||
)
|
||||
|
||||
funcMonoid := FunctionMonoid[int, bool](boolOrMonoid)
|
||||
funcMonoid := FunctionMonoid[int](boolOrMonoid)
|
||||
|
||||
isEven := func(n int) bool { return n%2 == 0 }
|
||||
isPositive := func(n int) bool { return n > 0 }
|
||||
@@ -702,7 +702,7 @@ func BenchmarkFunctionMonoid(b *testing.B) {
|
||||
0,
|
||||
)
|
||||
|
||||
funcMonoid := FunctionMonoid[string, int](intAddMonoid)
|
||||
funcMonoid := FunctionMonoid[string](intAddMonoid)
|
||||
|
||||
f1 := func(s string) int { return len(s) }
|
||||
f2 := func(s string) int { return len(s) * 2 }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user