1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-12-09 23:11:40 +02:00

Compare commits

..

2 Commits

Author SHA1 Message Date
Dr. Carsten Leue
311ed55f06 fix: add Read method to Readers
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-12 11:59:20 +01:00
Dr. Carsten Leue
23333ce52c doc: improve doc
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-12 11:08:18 +01:00
24 changed files with 316 additions and 98 deletions

View File

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

View File

@@ -836,3 +836,8 @@ func ChainReaderOptionK[A, B any](onNone func() error) func(readeroption.Kleisli
func ChainFirstReaderOptionK[A, B any](onNone func() error) func(readeroption.Kleisli[context.Context, A, B]) Operator[A, A] {
return RIOR.ChainFirstReaderOptionK[context.Context, A, B](onNone)
}
//go:inline
func Read[A any](r context.Context) func(ReaderIOResult[A]) IOResult[A] {
return RIOR.Read[A](r)
}

View File

@@ -883,5 +883,3 @@ func BenchmarkExecuteApPar_CanceledContext(b *testing.B) {
benchResult = rioe(ctx)()
}
}
// Made with Bob

View File

@@ -875,5 +875,3 @@ func TestBracket(t *testing.T) {
assert.Equal(t, E.Left[int](err), res)
})
}
// Made with Bob

View File

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

View File

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

View File

@@ -648,5 +648,3 @@ func BenchmarkString_Left(b *testing.B) {
benchString = left.String()
}
}
// Made with Bob

View File

@@ -78,12 +78,26 @@ 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(HKTEA, func(O.Option[A]) HKTB) HKTB, onNone func() HKTB, onSome func(A) HKTB) func(HKTEA) HKTB {
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)
}

View File

@@ -233,7 +233,7 @@ func After[GA ~func() O.Option[A], A any](timestamp time.Time) func(GA) GA {
// Fold convers an IOOption into an IO
func Fold[GA ~func() O.Option[A], GB ~func() B, A, B any](onNone func() GB, onSome func(A) GB) func(GA) GB {
return optiont.MatchE(IO.MonadChain[GA, GB, O.Option[A], B], onNone, onSome)
return optiont.MatchE(IO.Chain[GA, GB, O.Option[A], B], onNone, onSome)
}
// Defer creates an IO by creating a brand new IO via a generator function, each time

View File

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

View File

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

View File

@@ -265,5 +265,3 @@
// In practice, they are the same type, but the lazy package provides a more focused
// API for pure computations.
package lazy
// Made with Bob

View File

@@ -501,5 +501,3 @@ func TestMapComposition(t *testing.T) {
assert.Equal(t, 20, result())
}
// Made with Bob

View File

@@ -278,7 +278,7 @@ func Local[A, R2, R1 any](f func(R2) R1) func(Reader[R1, A]) Reader[R2, A] {
// getPort := reader.Asks(func(c Config) int { return c.Port })
// run := reader.Read(Config{Port: 8080})
// port := run(getPort) // 8080
func Read[E, A any](e E) func(Reader[E, A]) A {
func Read[A, E any](e E) func(Reader[E, A]) A {
return I.Ap[A](e)
}

View File

@@ -175,7 +175,7 @@ func TestLocal(t *testing.T) {
func TestRead(t *testing.T) {
config := Config{Port: 8080}
getPort := Asks(func(c Config) int { return c.Port })
run := Read[Config, int](config)
run := Read[int](config)
port := run(getPort)
assert.Equal(t, 8080, port)
}

View File

@@ -153,7 +153,7 @@ func Local[E, A, R2, R1 any](f func(R2) R1) func(ReaderEither[R1, E, A]) ReaderE
// Read applies a context to a reader to obtain its value
func Read[E1, A, E any](e E) func(ReaderEither[E, E1, A]) Either[E1, A] {
return reader.Read[E, Either[E1, A]](e)
return reader.Read[Either[E1, A]](e)
}
func MonadFlap[L, E, A, B any](fab ReaderEither[L, E, func(A) B], a A) ReaderEither[L, E, B] {

View File

@@ -750,3 +750,8 @@ func MapLeft[R, A, E1, E2 any](f func(E1) E2) func(ReaderIOEither[R, E1, A]) Rea
func Local[E, A, R1, R2 any](f func(R2) R1) func(ReaderIOEither[R1, E, A]) ReaderIOEither[R2, E, A] {
return reader.Local[IOEither[E, A]](f)
}
//go:inline
func Read[E, A, R any](r R) func(ReaderIOEither[R, E, A]) IOEither[E, A] {
return reader.Read[IOEither[E, A]](r)
}

View File

@@ -56,5 +56,3 @@ func TestApS(t *testing.T) {
assert.Equal(t, res(context.Background())(), result.Of("John Doe"))
}
// Made with Bob

View File

@@ -677,3 +677,8 @@ func MapLeft[R, A, E any](f func(error) E) func(ReaderIOResult[R, A]) RIOE.Reade
func Local[A, R1, R2 any](f func(R2) R1) func(ReaderIOResult[R1, A]) ReaderIOResult[R2, A] {
return RIOE.Local[error, A](f)
}
//go:inline
func Read[A, R any](r R) func(ReaderIOResult[R, A]) IOResult[A] {
return RIOE.Read[error, A](r)
}

View File

@@ -160,5 +160,3 @@ func TestUncurry3(t *testing.T) {
assert.True(t, ok)
assert.Equal(t, 42, result)
}
// Made with Bob

View File

@@ -110,5 +110,3 @@ func TestFrom3(t *testing.T) {
result2 := roFunc("test", 5, false)(ctx1)
assert.Equal(t, O.None[string](), result2)
}
// Made with Bob

View File

@@ -82,11 +82,11 @@ func FromPredicate[GEA ~func(E) O.Option[A], E, A any](pred func(A) bool) func(A
}
func Fold[GEA ~func(E) O.Option[A], GB ~func(E) B, E, A, B any](onNone func() GB, onRight func(A) GB) func(GEA) GB {
return optiont.MatchE(R.MonadChain[GEA, GB, E, O.Option[A], B], onNone, onRight)
return optiont.MatchE(R.Chain[GEA, GB, E, O.Option[A], B], onNone, onRight)
}
func GetOrElse[GEA ~func(E) O.Option[A], GA ~func(E) A, E, A any](onNone func() GA) func(GEA) GA {
return optiont.GetOrElse(R.MonadChain[GEA, GA, E, O.Option[A], A], onNone, R.Of[GA, E, A])
return optiont.GetOrElse(R.Chain[GEA, GA, E, O.Option[A], A], onNone, R.Of[GA, E, A])
}
func Ask[GEE ~func(E) O.Option[E], E, L any]() GEE {

View File

@@ -36,6 +36,8 @@ func FromOption[E, A any](e Option[A]) ReaderOption[E, A] {
// Some wraps a value in a ReaderOption, representing a successful computation.
// This is equivalent to Of but more explicit about the Option semantics.
//
//go:inline
func Some[E, A any](r A) ReaderOption[E, A] {
return optiont.Of(reader.Of[E, Option[A]], r)
}
@@ -50,6 +52,8 @@ func FromReader[E, A any](r Reader[E, A]) ReaderOption[E, A] {
// SomeReader lifts a Reader[E, A] into a ReaderOption[E, A].
// The resulting computation always succeeds (returns Some).
//
//go:inline
func SomeReader[E, A any](r Reader[E, A]) ReaderOption[E, A] {
return optiont.SomeF(reader.MonadMap[E, A, Option[A]], r)
}
@@ -61,6 +65,8 @@ func SomeReader[E, A any](r Reader[E, A]) ReaderOption[E, A] {
//
// ro := readeroption.Of[Config](42)
// doubled := readeroption.MonadMap(ro, func(x int) int { return x * 2 })
//
//go:inline
func MonadMap[E, A, B any](fa ReaderOption[E, A], f func(A) B) ReaderOption[E, B] {
return readert.MonadMap[ReaderOption[E, A], ReaderOption[E, B]](O.MonadMap[A, B], fa, f)
}
@@ -74,6 +80,8 @@ func MonadMap[E, A, B any](fa ReaderOption[E, A], f func(A) B) ReaderOption[E, B
// readeroption.Of[Config](42),
// readeroption.Map[Config](func(x int) int { return x * 2 }),
// )
//
//go:inline
func Map[E, A, B any](f func(A) B) Operator[E, A, B] {
return readert.Map[ReaderOption[E, A], ReaderOption[E, B]](O.Map[A, B], f)
}
@@ -86,6 +94,8 @@ func Map[E, A, B any](f func(A) B) Operator[E, A, B] {
// findUser := func(id int) readeroption.ReaderOption[DB, User] { ... }
// loadProfile := func(user User) readeroption.ReaderOption[DB, Profile] { ... }
// result := readeroption.MonadChain(findUser(123), loadProfile)
//
//go:inline
func MonadChain[E, A, B any](ma ReaderOption[E, A], f Kleisli[E, A, B]) ReaderOption[E, B] {
return readert.MonadChain(O.MonadChain[A, B], ma, f)
}
@@ -99,6 +109,8 @@ func MonadChain[E, A, B any](ma ReaderOption[E, A], f Kleisli[E, A, B]) ReaderOp
// findUser(123),
// readeroption.Chain(loadProfile),
// )
//
//go:inline
func Chain[E, A, B any](f Kleisli[E, A, B]) Operator[E, A, B] {
return readert.Chain[ReaderOption[E, A]](O.Chain[A, B], f)
}
@@ -110,6 +122,8 @@ func Chain[E, A, B any](f Kleisli[E, A, B]) Operator[E, A, B] {
//
// ro := readeroption.Of[Config](42)
// result := ro(config) // Returns option.Some(42)
//
//go:inline
func Of[E, A any](a A) ReaderOption[E, A] {
return readert.MonadOf[ReaderOption[E, A]](O.Of[A], a)
}
@@ -121,6 +135,8 @@ func Of[E, A any](a A) ReaderOption[E, A] {
//
// ro := readeroption.None[Config, int]()
// result := ro(config) // Returns option.None[int]()
//
//go:inline
func None[E, A any]() ReaderOption[E, A] {
return reader.Of[E](O.None[A]())
}
@@ -128,12 +144,16 @@ func None[E, A any]() ReaderOption[E, A] {
// MonadAp applies a function wrapped in a ReaderOption to a value wrapped in a ReaderOption.
// Both computations are executed with the same environment.
// If either computation returns None, the result is None.
//
//go:inline
func MonadAp[E, A, B any](fab ReaderOption[E, func(A) B], fa ReaderOption[E, A]) ReaderOption[E, B] {
return readert.MonadAp[ReaderOption[E, A], ReaderOption[E, B], ReaderOption[E, func(A) B], E, A](O.MonadAp[B, A], fab, fa)
}
// Ap returns a function that applies a function wrapped in a ReaderOption to a value.
// This is the curried version of MonadAp.
//
//go:inline
func Ap[B, E, A any](fa ReaderOption[E, A]) Operator[E, func(A) B, B] {
return readert.Ap[ReaderOption[E, A], ReaderOption[E, B], ReaderOption[E, func(A) B], E, A](O.Ap[B, A], fa)
}
@@ -148,6 +168,8 @@ func Ap[B, E, A any](fa ReaderOption[E, A]) Operator[E, func(A) B, B] {
// readeroption.Of[Config](42),
// readeroption.Chain(isPositive),
// )
//
//go:inline
func FromPredicate[E, A any](pred func(A) bool) Kleisli[E, A, A] {
return fromoption.FromPredicate(FromOption[E, A], pred)
}
@@ -162,8 +184,14 @@ func FromPredicate[E, A any](pred func(A) bool) Kleisli[E, A, A] {
// func() reader.Reader[Config, string] { return reader.Of[Config]("not found") },
// func(user User) reader.Reader[Config, string] { return reader.Of[Config](user.Name) },
// )(findUser(123))
func Fold[E, A, B any](onNone func() Reader[E, B], onRight func(A) Reader[E, B]) func(ReaderOption[E, A]) Reader[E, B] {
return optiont.MatchE(reader.MonadChain[E, Option[A], B], onNone, onRight)
//
//go:inline
func Fold[E, A, B any](onNone Reader[E, B], onRight func(A) Reader[E, B]) func(ReaderOption[E, A]) Reader[E, B] {
return optiont.MatchE(reader.Chain[E, Option[A], B], function.Constant(onNone), onRight)
}
func MonadFold[E, A, B any](fa ReaderOption[E, A], onNone Reader[E, B], onRight func(A) Reader[E, B]) Reader[E, B] {
return optiont.MonadMatchE(fa, reader.MonadChain[E, Option[A], B], function.Constant(onNone), onRight)
}
// GetOrElse returns the value from a ReaderOption, or a default value if it's None.
@@ -173,8 +201,10 @@ func Fold[E, A, B any](onNone func() Reader[E, B], onRight func(A) Reader[E, B])
// result := readeroption.GetOrElse(
// func() reader.Reader[Config, User] { return reader.Of[Config](defaultUser) },
// )(findUser(123))
func GetOrElse[E, A any](onNone func() Reader[E, A]) func(ReaderOption[E, A]) Reader[E, A] {
return optiont.GetOrElse(reader.MonadChain[E, Option[A], A], onNone, reader.Of[E, A])
//
//go:inline
func GetOrElse[E, A any](onNone Reader[E, A]) func(ReaderOption[E, A]) Reader[E, A] {
return optiont.GetOrElse(reader.Chain[E, Option[A], A], function.Constant(onNone), reader.Of[E, A])
}
// Ask retrieves the current environment as a ReaderOption.
@@ -184,6 +214,8 @@ func GetOrElse[E, A any](onNone func() Reader[E, A]) func(ReaderOption[E, A]) Re
//
// getConfig := readeroption.Ask[Config, any]()
// result := getConfig(myConfig) // Returns option.Some(myConfig)
//
//go:inline
func Ask[E, L any]() ReaderOption[E, E] {
return fromreader.Ask(FromReader[E, E])()
}
@@ -195,6 +227,8 @@ func Ask[E, L any]() ReaderOption[E, E] {
//
// getTimeout := readeroption.Asks(func(cfg Config) int { return cfg.Timeout })
// result := getTimeout(myConfig) // Returns option.Some(myConfig.Timeout)
//
//go:inline
func Asks[E, A any](r Reader[E, A]) ReaderOption[E, A] {
return fromreader.Asks(FromReader[E, A])(r)
}
@@ -209,6 +243,8 @@ func Asks[E, A any](r Reader[E, A]) ReaderOption[E, A] {
// readeroption.Of[Config]("25"),
// parseAge,
// )
//
//go:inline
func MonadChainOptionK[E, A, B any](ma ReaderOption[E, A], f func(A) Option[B]) ReaderOption[E, B] {
return fromoption.MonadChainOptionK(
MonadChain[E, A, B],
@@ -228,6 +264,8 @@ func MonadChainOptionK[E, A, B any](ma ReaderOption[E, A], f func(A) Option[B])
// readeroption.Of[Config]("25"),
// readeroption.ChainOptionK[Config](parseAge),
// )
//
//go:inline
func ChainOptionK[E, A, B any](f func(A) Option[B]) Operator[E, A, B] {
return fromoption.ChainOptionK(
Chain[E, A, B],
@@ -243,6 +281,8 @@ func ChainOptionK[E, A, B any](f func(A) Option[B]) Operator[E, A, B] {
//
// nested := readeroption.Of[Config](readeroption.Of[Config](42))
// flattened := readeroption.Flatten(nested)
//
//go:inline
func Flatten[E, A any](mma ReaderOption[E, ReaderOption[E, A]]) ReaderOption[E, A] {
return MonadChain(mma, function.Identity[ReaderOption[E, A]])
}
@@ -264,6 +304,8 @@ func Flatten[E, A any](mma ReaderOption[E, ReaderOption[E, A]]) ReaderOption[E,
// result := readeroption.Local(func(g GlobalConfig) DBConfig { return g.DB })(
// readeroption.Asks(query),
// )
//
//go:inline
func Local[A, R2, R1 any](f func(R2) R1) func(ReaderOption[R1, A]) ReaderOption[R2, A] {
return reader.Local[Option[A]](f)
}
@@ -275,18 +317,34 @@ func Local[A, R2, R1 any](f func(R2) R1) func(ReaderOption[R1, A]) ReaderOption[
//
// ro := readeroption.Of[Config](42)
// result := readeroption.Read[int](myConfig)(ro) // Returns option.Some(42)
//
//go:inline
func Read[A, E any](e E) func(ReaderOption[E, A]) Option[A] {
return reader.Read[E, Option[A]](e)
return reader.Read[Option[A]](e)
}
// MonadFlap applies a value to a function wrapped in a ReaderOption.
// This is the reverse of MonadAp.
//
//go:inline
func MonadFlap[E, A, B any](fab ReaderOption[E, func(A) B], a A) ReaderOption[E, B] {
return functor.MonadFlap(MonadMap[E, func(A) B, B], fab, a)
}
// Flap returns a function that applies a value to a function wrapped in a ReaderOption.
// This is the curried version of MonadFlap.
//
//go:inline
func Flap[E, B, A any](a A) Operator[E, func(A) B, B] {
return functor.Flap(Map[E, func(A) B, B], a)
}
//go:inline
func MonadAlt[E, A any](fa ReaderOption[E, A], that ReaderOption[E, A]) ReaderOption[E, A] {
return MonadFold(fa, that, Of[E, A])
}
//go:inline
func Alt[E, A any](that ReaderOption[E, A]) Operator[E, A, A] {
return Fold(that, Of[E, A])
}

View File

@@ -22,6 +22,7 @@ import (
F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/internal/utils"
O "github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/reader"
"github.com/stretchr/testify/assert"
)
@@ -135,15 +136,9 @@ func TestFromPredicate(t *testing.T) {
}
func TestFold(t *testing.T) {
onNone := func() Reader[MyContext, string] {
return func(ctx MyContext) string {
return "none"
}
}
onNone := reader.Of[MyContext]("none")
onSome := func(x int) Reader[MyContext, string] {
return func(ctx MyContext) string {
return fmt.Sprintf("%d", x)
}
return reader.Of[MyContext](fmt.Sprintf("%d", x))
}
// Test with Some
@@ -156,11 +151,7 @@ func TestFold(t *testing.T) {
}
func TestGetOrElse(t *testing.T) {
defaultValue := func() Reader[MyContext, int] {
return func(ctx MyContext) int {
return 0
}
}
defaultValue := reader.Of[MyContext](0)
// Test with Some
g1 := GetOrElse(defaultValue)(Of[MyContext](42))