1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-11-23 22:14:53 +02:00

doc: improve documentation

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
This commit is contained in:
Dr. Carsten Leue
2025-11-06 11:20:50 +01:00
parent 0d3a8634b1
commit 0f9f89f16d
2 changed files with 517 additions and 138 deletions

346
README.md
View File

@@ -1,207 +1,311 @@
# Functional programming library for golang
# fp-go: Functional Programming Library for Go
**🚧 Work in progress! 🚧** Despite major version 1 because of <https://github.com/semantic-release/semantic-release/issues/1507>. Trying to not make breaking changes, but devil is in the details.
[![Go Reference](https://pkg.go.dev/badge/github.com/IBM/fp-go.svg)](https://pkg.go.dev/github.com/IBM/fp-go)
**🚧 Work in progress! 🚧** Despite major version 1 (due to [semantic-release limitations](https://github.com/semantic-release/semantic-release/issues/1507)), we're working to minimize breaking changes.
![logo](resources/images/logo.png)
This library is strongly influenced by the awesome [fp-ts](https://github.com/gcanti/fp-ts).
A comprehensive functional programming library for Go, strongly influenced by the excellent [fp-ts](https://github.com/gcanti/fp-ts) library for TypeScript.
## Getting started
## 📚 Table of Contents
- [Getting Started](#-getting-started)
- [Design Goals](#-design-goals)
- [Core Concepts](#-core-concepts)
- [Comparison to Idiomatic Go](#comparison-to-idiomatic-go)
- [Implementation Notes](#implementation-notes)
- [Common Operations](#common-operations)
- [Resources](#-resources)
## 🚀 Getting Started
### Installation
```bash
go get github.com/IBM/fp-go
```
Refer to the [samples](./samples/).
### Quick Example
Find API documentation [here](https://pkg.go.dev/github.com/IBM/fp-go)
```go
import (
"errors"
"github.com/IBM/fp-go/either"
"github.com/IBM/fp-go/function"
)
## Design Goal
// Pure function that can fail
func divide(a, b int) either.Either[error, int] {
if b == 0 {
return either.Left[int](errors.New("division by zero"))
}
return either.Right[error](a / b)
}
This library aims to provide a set of data types and functions that make it easy and fun to write maintainable and testable code in golang. It encourages the following patterns:
// Compose operations safely
result := function.Pipe2(
divide(10, 2),
either.Map(func(x int) int { return x * 2 }),
either.GetOrElse(func() int { return 0 }),
)
// result = 10
```
- write many small, testable and pure functions, i.e. functions that produce output only depending on their input and that do not execute side effects
- offer helpers to isolate side effects into lazily executed functions (IO)
- expose a consistent set of composition to create new functions from existing ones
- for each data type there exists a small set of composition functions
- these functions are called the same across all data types, so you only have to learn a small number of function names
- the semantic of functions of the same name is consistent across all data types
### Resources
### How does this play with the [🧘🏽 Zen Of Go](https://the-zen-of-go.netlify.app/)?
- 📖 [API Documentation](https://pkg.go.dev/github.com/IBM/fp-go)
- 💡 [Code Samples](./samples/)
- 🆕 [V2 Documentation](./v2/README.md) (requires Go 1.24+)
#### 🧘🏽 Each package fulfills a single purpose
## 🎯 Design Goals
✔️ Each of the top level packages (e.g. Option, Either, ReaderIOEither, ...) fulfills the purpose of defining the respective data type and implementing the set of common operations for this data type.
This library aims to provide a set of data types and functions that make it easy and fun to write maintainable and testable code in Go. It encourages the following patterns:
#### 🧘🏽 Handle errors explicitly
### Core Principles
✔️ The library makes a clear distinction between that operations that cannot fail by design and operations that can fail. Failure is represented via the `Either` type and errors are handled explicitly by using `Either`'s monadic set of operations.
- **Pure Functions**: Write many small, testable, and pure functions that produce output only depending on their input and execute no side effects
- **Side Effect Isolation**: Isolate side effects into lazily executed functions using the `IO` monad
- **Consistent Composition**: Expose a consistent set of composition functions across all data types
- Each data type has a small set of composition functions
- Functions are named consistently across all data types
- Semantics of same-named functions are consistent across data types
#### 🧘🏽 Return early rather than nesting deeply
### 🧘🏽 Alignment with the Zen of Go
✔️ We recommend to implement simple, small functions that implement one feature and that would typically not invoke other functions. Interaction with other functions is done by function composition and the composition makes sure to run one function after the other. In the error case the `Either` monad makes sure to skip the error path.
This library respects and aligns with [The Zen of Go](https://the-zen-of-go.netlify.app/):
#### 🧘🏽 Leave concurrency to the caller
| Principle | Alignment | Explanation |
|-----------|-----------|-------------|
| 🧘🏽 Each package fulfills a single purpose | ✔️ | Each top-level package (Option, Either, ReaderIOEither, etc.) defines one data type and its operations |
| 🧘🏽 Handle errors explicitly | ✔️ | Clear distinction between operations that can/cannot fail; failures represented via `Either` type |
| 🧘🏽 Return early rather than nesting deeply | ✔️ | Small, focused functions composed together; `Either` monad handles error paths automatically |
| 🧘🏽 Leave concurrency to the caller | ✔️ | Pure functions are synchronous; I/O operations are asynchronous by default |
| 🧘🏽 Before you launch a goroutine, know when it will stop | 🤷🏽 | Library doesn't start goroutines; Task monad supports cancellation via context |
| 🧘🏽 Avoid package level state | ✔️ | No package-level state anywhere |
| 🧘🏽 Simplicity matters | ✔️ | Small, consistent interface across data types; focus on business logic |
| 🧘🏽 Write tests to lock in behaviour | 🟡 | Programming pattern encourages testing; library has growing test coverage |
| 🧘🏽 If you think it's slow, first prove it with a benchmark | ✔️ | Performance claims should be backed by benchmarks |
| 🧘🏽 Moderation is a virtue | ✔️ | No custom goroutines or expensive synchronization; atomic counters for coordination |
| 🧘🏽 Maintainability counts | ✔️ | Small, concise operations; pure functions with clear type signatures |
✔️ All pure are synchronous by default. The I/O operations are asynchronous per default.
## 💡 Core Concepts
#### 🧘🏽 Before you launch a goroutine, know when it will stop
### Data Types
🤷🏽 This is left to the user of the library since the library itself will not start goroutines on its own. The Task monad offers support for cancellation via the golang context, though.
The library provides several key functional data types:
#### 🧘🏽 Avoid package level state
- **`Option[A]`**: Represents an optional value (Some or None)
- **`Either[E, A]`**: Represents a value that can be one of two types (Left for errors, Right for success)
- **`IO[A]`**: Represents a lazy computation that produces a value
- **`IOEither[E, A]`**: Represents a lazy computation that can fail
- **`Reader[R, A]`**: Represents a computation that depends on an environment
- **`ReaderIOEither[R, E, A]`**: Combines Reader, IO, and Either for effectful computations with dependencies
- **`Task[A]`**: Represents an asynchronous computation
- **`State[S, A]`**: Represents a stateful computation
✔️ No package level state anywhere, this would be a significant anti-pattern
### Monadic Operations
#### 🧘🏽 Simplicity matters
All data types support common monadic operations:
✔️ The library is simple in the sense that it offers a small, consistent interface to a variety of data types. Users can concentrate on implementing business logic rather than dealing with low level data structures.
#### 🧘🏽 Write tests to lock in the behaviour of your package’s API
🟡 The programming pattern suggested by this library encourages writing test cases. The library itself also has a growing number of tests, but not enough, yet. TBD
#### 🧘🏽 If you think it’s slow, first prove it with a benchmark
✔️ Absolutely. If you think the function composition offered by this library is too slow, please provide a benchmark.
#### 🧘🏽 Moderation is a virtue
✔️ The library does not implement its own goroutines and also does not require any expensive synchronization primitives. Coordination of IO operations is implemented via atomic counters without additional primitives.
#### 🧘🏽 Maintainability counts
✔️ Code that consumes this library is easy to maintain because of the small and concise set of operations exposed. Also the suggested programming paradigm to decompose an application into small functions increases maintainability, because these functions are easy to understand and if they are pure, it's often sufficient to look at the type signature to understand the purpose.
The library itself also comprises many small functions, but it's admittedly harder to maintain than code that uses it. However this asymmetry is intended because it offloads complexity from users into a central component.
- **`Map`**: Transform the value inside a context
- **`Chain`** (FlatMap): Transform and flatten nested contexts
- **`Ap`**: Apply a function in a context to a value in a context
- **`Of`**: Wrap a value in a context
- **`Fold`**: Extract a value from a context
## Comparison to Idiomatic Go
In this section we discuss how the functional APIs differ from idiomatic go function signatures and how to convert back and forth.
This section explains how functional APIs differ from idiomatic Go and how to convert between them.
### Pure functions
### Pure Functions
Pure functions are functions that take input parameters and that compute an output without changing any global state and without mutating the input parameters. They will always return the same output for the same input.
Pure functions take input parameters and compute output without changing global state or mutating inputs. They always return the same output for the same input.
#### Without Errors
If your pure function does not return an error, the idiomatic signature is just fine and no changes are required.
If your pure function doesn't return an error, the idiomatic signature works as-is:
```go
func add(a, b int) int {
return a + b
}
```
#### With Errors
If your pure function can return an error, then it will have a `(T, error)` return value in idiomatic go. In functional style the return value is [Either[error, T]](https://pkg.go.dev/github.com/IBM/fp-go/either) because function composition is easier with such a return type. Use the `EitherizeXXX` methods in ["github.com/IBM/fp-go/either"](https://pkg.go.dev/github.com/IBM/fp-go/either) to convert from idiomatic to functional style and `UneitherizeXXX` to convert from functional to idiomatic style.
**Idiomatic Go:**
```go
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
```
### Effectful functions
**Functional Style:**
```go
func divide(a, b int) either.Either[error, int] {
if b == 0 {
return either.Left[int](errors.New("division by zero"))
}
return either.Right[error](a / b)
}
```
An effectful function (or function with a side effect) is one that changes data outside the scope of the function or that does not always produce the same output for the same input (because it depends on some external, mutable state). There is no special way in idiomatic go to identify such a function other than documentation. In functional style we represent them as functions that do not take an input but that produce an output. The base type for these functions is [IO[T]](https://pkg.go.dev/github.com/IBM/fp-go/io) because in many cases such functions represent `I/O` operations.
**Conversion:**
- Use `either.EitherizeXXX` to convert from idiomatic to functional style
- Use `either.UneitherizeXXX` to convert from functional to idiomatic style
### Effectful Functions
An effectful function changes data outside its scope or doesn't always produce the same output for the same input.
#### Without Errors
If your effectful function does not return an error, the functional signature is [IO[T]](https://pkg.go.dev/github.com/IBM/fp-go/io)
**Functional signature:** `IO[T]`
```go
func getCurrentTime() io.IO[time.Time] {
return func() time.Time {
return time.Now()
}
}
```
#### With Errors
If your effectful function can return an error, the functional signature is [IOEither[error, T]](https://pkg.go.dev/github.com/IBM/fp-go/ioeither). Use `EitherizeXXX` from ["github.com/IBM/fp-go/ioeither"](https://pkg.go.dev/github.com/IBM/fp-go/ioeither) to convert an idiomatic go function to functional style.
**Functional signature:** `IOEither[error, T]`
```go
func readFile(path string) ioeither.IOEither[error, []byte] {
return func() either.Either[error, []byte] {
data, err := os.ReadFile(path)
if err != nil {
return either.Left[[]byte](err)
}
return either.Right[error](data)
}
}
```
**Conversion:**
- Use `ioeither.EitherizeXXX` to convert idiomatic Go functions to functional style
### Go Context
Functions that take a [context](https://pkg.go.dev/context) are per definition effectful because they depend on the context parameter that is designed to be mutable (it can e.g. be used to cancel a running operation). Furthermore in idiomatic go the parameter is typically passed as the first parameter to a function.
Functions that take a `context.Context` are effectful because they depend on mutable context.
In functional style we isolate the [context](https://pkg.go.dev/context) and represent the nature of the effectful function as an [IOEither[error, T]](https://pkg.go.dev/github.com/IBM/fp-go/ioeither). The resulting type is [ReaderIOEither[T]](https://pkg.go.dev/github.com/IBM/fp-go/context/readerioeither), a function taking a [context](https://pkg.go.dev/context) that returns a function without parameters returning an [Either[error, T]](https://pkg.go.dev/github.com/IBM/fp-go/either). Use the `EitherizeXXX` methods from ["github.com/IBM/fp-go/context/readerioeither"](https://pkg.go.dev/github.com/IBM/fp-go/context/readerioeither) to convert an idiomatic go function with a [context](https://pkg.go.dev/context) to functional style.
**Idiomatic Go:**
```go
func fetchData(ctx context.Context, url string) ([]byte, error) {
// implementation
}
```
**Functional Style:**
```go
func fetchData(url string) readerioeither.ReaderIOEither[context.Context, error, []byte] {
return func(ctx context.Context) ioeither.IOEither[error, []byte] {
return func() either.Either[error, []byte] {
// implementation
}
}
}
```
**Conversion:**
- Use `readerioeither.EitherizeXXX` to convert idiomatic Go functions with context to functional style
## Implementation Notes
### Generics
All monadic operations are implemented via generics, i.e. they offer a type safe way to compose operations. This allows for convenient IDE support and also gives confidence about the correctness of the composition at compile time.
All monadic operations use Go generics for type safety:
Downside is that this will result in different versions of each operation per type, these versions are generated by the golang compiler at build time (unlike type erasure in languages such as Java of TypeScript). This might lead to large binaries for codebases with many different types. If this is a concern, you can always implement type erasure on top, i.e. use the monadic operations with the `any` type as if generics were not supported. You loose type safety, but this might result in smaller binaries.
-**Pros**: Type-safe composition, IDE support, compile-time correctness
- ⚠️ **Cons**: May result in larger binaries (different versions per type)
- 💡 **Tip**: For binary size concerns, use type erasure with `any` type
### Ordering of Generic Type Parameters
In go we need to specify all type parameters of a function on the global function definition, even if the function returns a higher order function and some of the type parameters are only applicable to the higher order function. So the following is not possible:
Go requires all type parameters on the global function definition. Parameters that cannot be auto-detected come first:
```go
func Map[A, B any](f func(A) B) [R, E any]func(fa ReaderIOEither[R, E, A]) ReaderIOEither[R, E, B]
// Map: B cannot be auto-detected, so it comes first
func Map[R, E, A, B any](f func(A) B) func(ReaderIOEither[R, E, A]) ReaderIOEither[R, E, B]
// Ap: B cannot be auto-detected from the argument
func Ap[B, R, E, A any](fa ReaderIOEither[R, E, A]) func(ReaderIOEither[R, E, func(A) B]) ReaderIOEither[R, E, B]
```
Note that the parameters `R` and `E` are not needed by the first level of `Map` but only by the resulting higher order function. Instead we need to specify the following:
This ordering maximizes type inference where possible.
```go
func Map[R, E, A, B any](f func(A) B) func(fa ReaderIOEither[R, E, A]) ReaderIOEither[R, E, B]
```
### Use of the ~ Operator
which overspecifies `Map` on the global scope. As a result the go compiler will not be able to auto-detect these parameters, it can only auto detect `A` and `B` since they appear in the argument of `Map`. We need to explicitly pass values for these type parameters when `Map` is being used.
Because of this limitation the order of parameters on a function matters. We want to make sure that we define those parameters that cannot be auto-detected, first, and the parameters that can be auto-detected, last. This can lead to inconsistencies in parameter ordering, but we believe that the gain in convenience is worth it. The parameter order of `Ap` is e.g. different from that of `Map`:
```go
func Ap[B, R, E, A any](fa ReaderIOEither[R, E, A]) func(fab ReaderIOEither[R, E, func(A) B]) ReaderIOEither[R, E, B]
```
because `R`, `E` and `A` can be determined from the argument to `Ap` but `B` cannot.
### Use of the [~ Operator](https://go.googlesource.com/proposal/+/master/design/47781-parameterized-go-ast.md)
The FP library attempts to be easy to consume and one aspect of this is the definition of higher level type definitions instead of having to use their low level equivalent. It is e.g. more convenient and readable to use
```go
ReaderIOEither[R, E, A]
```
than
```go
func(R) func() Either.Either[E, A]
```
although both are logically equivalent. At the time of this writing the go type system does not support generic type aliases, only generic type definition, i.e. it is not possible to write:
```go
type ReaderIOEither[R, E, A any] = RD.Reader[R, IOE.IOEither[E, A]]
```
only
Go doesn't support generic type aliases (until Go 1.24), only type definitions. The `~` operator allows generic implementations to work with compatible types:
```go
type ReaderIOEither[R, E, A any] RD.Reader[R, IOE.IOEither[E, A]]
```
This makes a big difference, because in the second case the type `ReaderIOEither[R, E, A any]` is considered a completely new type, not compatible to its right hand side, so it's not just a shortcut but a fully new type.
**Generic Subpackages:**
- Each higher-level type has a `generic` subpackage with fully generic implementations
- These are for library extensions, not end-users
- Main packages specialize generic implementations for convenience
From the implementation perspective however there is no reason to restrict the implementation to the new type, it can be generic for all compatible types. The way to express this in go is the [~](https://go.googlesource.com/proposal/+/master/design/47781-parameterized-go-ast.md) operator. This comes with some quite complicated type declarations in some cases, which undermines the goal of the library to be easy to use.
### Higher Kinded Types (HKT)
For that reason there exist sub-packages called `Generic` for all higher level types. These packages contain the fully generic implementation of the operations, preferring abstraction over usability. These packages are not meant to be used by end-users but are meant to be used by library extensions. The implementation for the convenient higher level types specializes the generic implementation for the particular higher level type, i.e. this layer does not contain any business logic but only *type magic*.
Go doesn't support HKT natively. This library addresses this by:
### Higher Kinded Types
- Introducing HKTs as individual types (e.g., `HKTA` for `HKT[A]`)
- Implementing generic algorithms in the `internal` package
- Keeping complexity hidden from end-users
Go does not support higher kinded types (HKT). Such types occur if a generic type itself is parametrized by another generic type. Example:
## Common Operations
The `Map` operation for `ReaderIOEither` is defined as:
### Map/Chain/Ap/Flap
| Operator | Parameter | Monad | Result | Use Case |
| -------- | ---------------- | --------------- | -------- | -------- |
| Map | `func(A) B` | `HKT[A]` | `HKT[B]` | Transform value in context |
| Chain | `func(A) HKT[B]` | `HKT[A]` | `HKT[B]` | Transform and flatten |
| Ap | `HKT[A]` | `HKT[func(A)B]` | `HKT[B]` | Apply function in context |
| Flap | `A` | `HKT[func(A)B]` | `HKT[B]` | Apply value to function in context |
### Example: Chaining Operations
```go
func Map[R, E, A, B any](f func(A) B) func(fa ReaderIOEither[R, E, A]) ReaderIOEither[R, E, B]
import (
"github.com/IBM/fp-go/either"
"github.com/IBM/fp-go/function"
)
result := function.Pipe3(
either.Right[error](10),
either.Map(func(x int) int { return x * 2 }),
either.Chain(func(x int) either.Either[error, int] {
if x > 15 {
return either.Right[error](x)
}
return either.Left[int](errors.New("too small"))
}),
either.GetOrElse(func() int { return 0 }),
)
```
and in fact the equivalent operations for all other monads follow the same pattern, we could try to introduce a new type for `ReaderIOEither` (without a parameter) as a HKT, e.g. like so (made-up syntax, does not work in go):
## 📚 Resources
```go
func Map[HKT, R, E, A, B any](f func(A) B) func(HKT[R, E, A]) HKT[R, E, B]
```
- [API Documentation](https://pkg.go.dev/github.com/IBM/fp-go)
- [Code Samples](./samples/)
- [V2 Documentation](./v2/README.md) - New features in Go 1.24+
- [fp-ts](https://github.com/gcanti/fp-ts) - Original TypeScript inspiration
this would be the completely generic method signature for all possible monads. In particular in many cases it is possible to compose functions independent of the concrete knowledge of the actual `HKT`. From the perspective of a library this is the ideal situation because then a particular algorithm only has to be implemented and tested once.
## 🤝 Contributing
This FP library addresses this by introducing the HKTs as individual types, e.g. `HKT[A]` would be represented as a new generic type `HKTA`. This loses the correlation to the type `A` but allows to implement generic algorithms, at the price of readability.
Contributions are welcome! Please feel free to submit issues or pull requests.
For that reason these implementations are kept in the `internal` package. These are meant to be used by the library itself or by extensions, not by end users.
## 📄 License
## Map/Ap/Flap
The following table lists the relationship between some selected operators
| Operator | Parameter | Monad | Result |
| -------- | ---------------- | --------------- | -------- |
| Map | `func(A) B` | `HKT[A]` | `HKT[B]` |
| Chain | `func(A) HKT[B]` | `HKT[A]` | `HKT[B]` |
| Ap | `HKT[A]` | `HKT[func(A)B]` | `HKT[B]` |
| Flap | `A` | `HKT[func(A)B]` | `HKT[B]` |
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

View File

@@ -1,43 +1,318 @@
# Functional programming library for golang V2
# fp-go V2: Enhanced Functional Programming for Go 1.24+
Go 1.24 introduces [generic type aliases](https://github.com/golang/go/issues/46477) which are leveraged by V2.
[![Go Reference](https://pkg.go.dev/badge/github.com/IBM/fp-go/v2.svg)](https://pkg.go.dev/github.com/IBM/fp-go/v2)
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.
## 📚 Table of Contents
- [Requirements](#-requirements)
- [Breaking Changes](#-breaking-changes)
- [Key Improvements](#-key-improvements)
- [Migration Guide](#-migration-guide)
- [Installation](#-installation)
- [What's New](#-whats-new)
## 🔧 Requirements
- **Go 1.24 or later** (for generic type alias support)
## ⚠️ Breaking Changes
- use of [generic type aliases](https://github.com/golang/go/issues/46477) which requires [go1.24](https://tip.golang.org/doc/go1.24)
- order of generic type arguments adjusted such that types that _cannot_ be inferred by the method argument come first, e.g. in the `Ap` methods
- monadic operations for `Pair` operate on the second argument, to be compatible with the [Haskell](https://hackage.haskell.org/package/TypeCompose-0.9.14/docs/Data-Pair.html) definition
### 1. Generic Type Aliases
## Simplifications
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.
- use type aliases to get rid of namespace imports for type declarations, e.g. instead of
**V1:**
```go
type ReaderIOEither[R, E, A any] RD.Reader[R, IOE.IOEither[E, A]]
```
**V2:**
```go
type ReaderIOEither[R, E, A any] = RD.Reader[R, IOE.IOEither[E, A]]
```
### 2. Generic Type Parameter Ordering
Type parameters that **cannot** be inferred from function arguments now come first, improving type inference.
**V1:**
```go
// Ap in V1 - less intuitive ordering
func Ap[R, E, A, B any](fa ReaderIOEither[R, E, A]) func(ReaderIOEither[R, E, func(A) B]) ReaderIOEither[R, E, B]
```
**V2:**
```go
// Ap in V2 - B comes first as it cannot be inferred
func Ap[B, R, E, A any](fa ReaderIOEither[R, E, A]) func(ReaderIOEither[R, E, func(A) B]) ReaderIOEither[R, E, B]
```
This change allows the Go compiler to infer more types automatically, reducing the need for explicit type parameters.
### 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).
**V1:**
```go
// Operations on first element
pair := MakePair(1, "hello")
result := Map(func(x int) int { return x * 2 })(pair) // Pair(2, "hello")
```
**V2:**
```go
// Operations on second element (Haskell-compatible)
pair := MakePair(1, "hello")
result := Map(func(s string) string { return s + "!" })(pair) // Pair(1, "hello!")
```
## ✨ Key Improvements
### 1. Simplified Type Declarations
Generic type aliases eliminate the need for namespace imports in type declarations.
**V1 Approach:**
```go
import (
ET "github.com/IBM/fp-go/v2/either"
ET "github.com/IBM/fp-go/either"
OPT "github.com/IBM/fp-go/option"
)
func doSth() ET.Either[error, string] {
...
func processData(input string) ET.Either[error, OPT.Option[int]] {
// implementation
}
```
you can declare your type once
**V2 Approach:**
```go
import (
"github.com/IBM/fp-go/v2/either"
"github.com/IBM/fp-go/v2/option"
)
// Define type aliases once
type Either[A any] = either.Either[error, A]
type Option[A any] = option.Option[A]
// Use them throughout your codebase
func processData(input string) Either[Option[int]] {
// implementation
}
```
### 2. No More `generic` Subpackages
The library implementation no longer requires separate `generic` subpackages, making the codebase simpler and easier to understand.
**V1 Structure:**
```
either/
either.go
generic/
either.go // Generic implementation
```
**V2 Structure:**
```
either/
either.go // Single, clean implementation
```
### 3. Better Type Inference
The reordered type parameters allow the Go compiler to infer more types automatically:
**V1:**
```go
// Often need explicit type parameters
result := Map[Context, error, int, string](transform)(value)
```
**V2:**
```go
// Compiler can infer more types
result := Map(transform)(value) // Cleaner!
```
## 🚀 Migration Guide
### Step 1: Update Go Version
Ensure you're using Go 1.24 or later:
```bash
go version # Should show go1.24 or higher
```
### Step 2: Update Import Paths
Change all import paths from `github.com/IBM/fp-go` to `github.com/IBM/fp-go/v2`:
**Before:**
```go
import (
"github.com/IBM/fp-go/either"
"github.com/IBM/fp-go/option"
)
```
**After:**
```go
import (
"github.com/IBM/fp-go/v2/either"
"github.com/IBM/fp-go/v2/option"
)
```
### Step 3: Remove `generic` Subpackage Imports
If you were using generic subpackages, remove them:
**Before:**
```go
import (
E "github.com/IBM/fp-go/either/generic"
)
```
**After:**
```go
import (
"github.com/IBM/fp-go/v2/either"
)
type Either[A any] = either.Either[error, A]
```
and then use it across your codebase
### Step 4: Update Type Parameter Order
Review functions like `Ap` where type parameter order has changed. The compiler will help identify these:
**Before:**
```go
result := Ap[Context, error, int, string](value)(funcInContext)
```
**After:**
```go
result := Ap[string, Context, error, int](value)(funcInContext)
// Or better yet, let the compiler infer:
result := Ap(value)(funcInContext)
```
### Step 5: Update Pair Operations
If you're using `Pair`, update operations to work on the second element:
**Before (V1):**
```go
pair := MakePair(42, "data")
// Map operates on first element
result := Map(func(x int) int { return x * 2 })(pair)
```
**After (V2):**
```go
pair := MakePair(42, "data")
// Map operates on second element
result := Map(func(s string) string { return s + "!" })(pair)
```
### Step 6: Simplify Type Aliases
Create project-wide type aliases for common patterns:
```go
func doSth() Either[string] {
...
// types.go - Define once, use everywhere
package myapp
import (
"github.com/IBM/fp-go/v2/either"
"github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/ioeither"
)
type Either[A any] = either.Either[error, 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
```
## 🆕 What's New
### Cleaner API Surface
The elimination of `generic` subpackages means:
- Fewer imports to manage
- Simpler package structure
- Easier to navigate documentation
- More intuitive API
### Example: Before and After
**V1 Complex Example:**
```go
import (
ET "github.com/IBM/fp-go/either"
EG "github.com/IBM/fp-go/either/generic"
IOET "github.com/IBM/fp-go/ioeither"
IOEG "github.com/IBM/fp-go/ioeither/generic"
)
func process() IOET.IOEither[error, string] {
return IOEG.Map[error, int, string](
func(x int) string { return fmt.Sprintf("%d", x) },
)(fetchData())
}
```
- library implementation does no longer need to use the `generic` subpackage, this simplifies reading and understanding of the code
**V2 Simplified Example:**
```go
import (
"github.com/IBM/fp-go/v2/either"
"github.com/IBM/fp-go/v2/ioeither"
)
type IOEither[A any] = ioeither.IOEither[error, A]
func process() IOEither[string] {
return ioeither.Map(
func(x int) string { return fmt.Sprintf("%d", x) },
)(fetchData())
}
```
## 📚 Additional Resources
- [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)
## 🤔 Should I Migrate?
**Migrate to V2 if:**
- ✅ You can use Go 1.24+
- ✅ You want cleaner, more maintainable code
- ✅ You want better type inference
- ✅ You're starting a new project
**Stay on V1 if:**
- ⚠️ You're locked to Go < 1.24
- Migration effort outweighs benefits for your project
- You need stability in production (V2 is newer)
## 🐛 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.