2025-11-06 11:20:50 +01:00
# fp-go: Functional Programming Library for Go
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
[](https://pkg.go.dev/github.com/IBM/fp-go)
2023-07-17 18:03:21 +02:00
2025-11-06 11:20:50 +01:00
**🚧 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.
2023-07-18 16:27:05 +02:00
2025-11-06 11:20:50 +01:00

2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
A comprehensive functional programming library for Go, strongly influenced by the excellent [fp-ts ](https://github.com/gcanti/fp-ts ) library for TypeScript.
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
## 📚 Table of Contents
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
- [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 )
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
## 🚀 Getting Started
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
### Installation
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
```bash
go get github.com/IBM/fp-go
```
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
### Quick Example
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
```go
import (
"errors"
"github.com/IBM/fp-go/either"
"github.com/IBM/fp-go/function"
)
// 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 )
}
// 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
```
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
### Resources
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
- 📖 [API Documentation ](https://pkg.go.dev/github.com/IBM/fp-go )
- 💡 [Code Samples ](./samples/ )
- 🆕 [V2 Documentation ](./v2/README.md ) (requires Go 1.24+)
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
## 🎯 Design Goals
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
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:
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
### Core Principles
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
- **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
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
### 🧘🏽 Alignment with the Zen of Go
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
This library respects and aligns with [The Zen of Go ](https://the-zen-of-go.netlify.app/ ):
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
| 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 |
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
## 💡 Core Concepts
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
### Data Types
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
The library provides several key functional data types:
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
- **`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
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
### Monadic Operations
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
All data types support common monadic operations:
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
- **`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
2023-07-07 22:31:06 +02:00
2023-08-18 09:45:27 +01:00
## Comparison to Idiomatic Go
2023-07-27 23:05:12 +02:00
2025-11-06 11:20:50 +01:00
This section explains how functional APIs differ from idiomatic Go and how to convert between them.
2023-07-27 23:05:12 +02:00
2025-11-06 11:20:50 +01:00
### Pure Functions
2023-07-27 23:05:12 +02:00
2025-11-06 11:20:50 +01:00
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.
2023-07-27 23:05:12 +02:00
#### Without Errors
2025-11-06 11:20:50 +01:00
If your pure function doesn't return an error, the idiomatic signature works as-is:
2023-07-27 23:05:12 +02:00
2025-11-06 11:20:50 +01:00
```go
func add(a, b int) int {
return a + b
}
```
2023-07-27 23:05:12 +02:00
2025-11-06 11:20:50 +01:00
#### With Errors
2023-07-27 23:05:12 +02:00
2025-11-06 11:20:50 +01:00
**Idiomatic Go:**
```go
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
```
2023-07-27 23:05:12 +02:00
2025-11-06 11:20:50 +01:00
**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 )
}
```
2023-07-27 23:05:12 +02:00
2025-11-06 11:20:50 +01:00
**Conversion:**
- Use `either.EitherizeXXX` to convert from idiomatic to functional style
- Use `either.UneitherizeXXX` to convert from functional to idiomatic style
2023-07-27 23:05:12 +02:00
2025-11-06 11:20:50 +01:00
### Effectful Functions
2023-07-27 23:05:12 +02:00
2025-11-06 11:20:50 +01:00
An effectful function changes data outside its scope or doesn't always produce the same output for the same input.
2023-07-27 23:05:12 +02:00
2025-11-06 11:20:50 +01:00
#### Without Errors
2023-07-27 23:05:12 +02:00
2025-11-06 11:20:50 +01:00
**Functional signature:** `IO[T]`
2023-07-27 23:05:12 +02:00
2025-11-06 11:20:50 +01:00
```go
func getCurrentTime() io.IO[time.Time] {
return func() time.Time {
return time.Now()
}
}
```
2023-07-27 23:05:12 +02:00
2025-11-06 11:20:50 +01:00
#### With Errors
2023-07-27 23:05:12 +02:00
2025-11-06 11:20:50 +01:00
**Functional signature:** `IOEither[error, T]`
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
```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 )
}
}
```
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
**Conversion:**
- Use `ioeither.EitherizeXXX` to convert idiomatic Go functions to functional style
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
### Go Context
2023-07-19 16:20:28 +02:00
2025-11-06 11:20:50 +01:00
Functions that take a `context.Context` are effectful because they depend on mutable context.
2023-07-19 16:20:28 +02:00
2025-11-06 11:20:50 +01:00
**Idiomatic Go:**
2023-07-19 16:20:28 +02:00
```go
2025-11-06 11:20:50 +01:00
func fetchData(ctx context.Context, url string) ([]byte, error) {
// implementation
}
2023-07-19 16:20:28 +02:00
```
2025-11-06 11:20:50 +01:00
**Functional Style:**
2023-07-19 16:20:28 +02:00
```go
2025-11-06 11:20:50 +01:00
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
}
}
}
2023-07-19 16:20:28 +02:00
```
2025-11-06 11:20:50 +01:00
**Conversion:**
- Use `readerioeither.EitherizeXXX` to convert idiomatic Go functions with context to functional style
2023-07-19 16:20:28 +02:00
2025-11-06 11:20:50 +01:00
## Implementation Notes
2023-07-19 16:20:28 +02:00
2025-11-06 11:20:50 +01:00
### Generics
2023-07-19 16:20:28 +02:00
2025-11-06 11:20:50 +01:00
All monadic operations use Go generics for type safety:
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
- ✅ **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
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
### Ordering of Generic Type Parameters
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
Go requires all type parameters on the global function definition. Parameters that cannot be auto-detected come first:
2023-07-07 22:31:06 +02:00
```go
2025-11-06 11:20:50 +01:00
// 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]
2023-07-07 22:31:06 +02:00
```
2025-11-06 11:20:50 +01:00
This ordering maximizes type inference where possible.
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
### Use of the ~ Operator
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
Go doesn't support generic type aliases (until Go 1.24), only type definitions. The `~` operator allows generic implementations to work with compatible types:
2023-07-07 22:31:06 +02:00
```go
2023-07-19 16:20:28 +02:00
type ReaderIOEither[R, E, A any] RD.Reader[R, IOE.IOEither[E, A]]
2023-07-07 22:31:06 +02:00
```
2025-11-06 11:20:50 +01:00
**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
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
### Higher Kinded Types (HKT)
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
Go doesn't support HKT natively. This library addresses this by:
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
- Introducing HKTs as individual types (e.g., `HKTA` for `HKT[A]` )
- Implementing generic algorithms in the `internal` package
- Keeping complexity hidden from end-users
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
## Common Operations
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
### Map/Chain/Ap/Flap
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
| 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 |
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
### Example: Chaining Operations
2023-07-07 22:31:06 +02:00
```go
2025-11-06 11:20:50 +01:00
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 }),
)
2023-07-07 22:31:06 +02:00
```
2025-11-06 11:20:50 +01:00
## 📚 Resources
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
- [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
2023-07-07 22:31:06 +02:00
2025-11-06 11:20:50 +01:00
## 🤝 Contributing
2023-10-11 22:23:19 +02:00
2025-11-06 11:20:50 +01:00
Contributions are welcome! Please feel free to submit issues or pull requests.
2023-10-11 22:23:19 +02:00
2025-11-06 11:20:50 +01:00
## 📄 License
2023-10-11 22:23:19 +02:00
2025-11-06 11:20:50 +01:00
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.