IBM/fp-go
{ "createdAt": "2023-07-05T14:18:19Z", "defaultBranch": "main", "description": "functional programming library for golang", "fullName": "IBM/fp-go", "homepage": "", "language": "Go", "name": "fp-go", "pushedAt": "2025-11-24T18:45:41Z", "stargazersCount": 1878, "topics": [ "functional-programming", "go", "golang", "library", "monad", "utility" ], "updatedAt": "2025-11-25T21:35:58Z", "url": "https://github.com/IBM/fp-go"}fp-go: Functional Programming Library for Go
Section titled “fp-go: Functional Programming Library for Go”🚧 Work in progress! 🚧 Despite major version 1 (due to semantic-release limitations), we’re working to minimize breaking changes.
![logo]!(resources/images/logo.png)
A comprehensive functional programming library for Go, strongly influenced by the excellent fp-ts library for TypeScript.
📚 Table of Contents
Section titled “📚 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
Section titled “🚀 Getting Started”Installation
Section titled “Installation”go get github.com/IBM/fp-goQuick Example
Section titled “Quick Example”import ( "errors" "github.com/IBM/fp-go/either" "github.com/IBM/fp-go/function")
// Pure function that can failfunc 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 safelyresult := function.Pipe2( divide(10, 2), either.Map(func(x int) int { return x * 2 }), either.GetOrElse(func() int { return 0 }),)// result = 10Resources
Section titled “Resources”- 📖 API Documentation
- 💡 [Code Samples]!(./samples/)
- 🆕 [V2 Documentation]!(./v2/README.md) (requires Go 1.24+)
🎯 Design Goals
Section titled “🎯 Design Goals”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:
Core Principles
Section titled “Core Principles”- 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
IOmonad - 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
🧘🏽 Alignment with the Zen of Go
Section titled “🧘🏽 Alignment with the Zen of Go”This library respects and aligns with The Zen of Go:
| 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 |
💡 Core Concepts
Section titled “💡 Core Concepts”Data Types
Section titled “Data Types”The library provides several key functional data types:
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 valueIOEither[E, A]: Represents a lazy computation that can failReader[R, A]: Represents a computation that depends on an environmentReaderIOEither[R, E, A]: Combines Reader, IO, and Either for effectful computations with dependenciesTask[A]: Represents an asynchronous computationState[S, A]: Represents a stateful computation
Monadic Operations
Section titled “Monadic Operations”All data types support common monadic operations:
Map: Transform the value inside a contextChain(FlatMap): Transform and flatten nested contextsAp: Apply a function in a context to a value in a contextOf: Wrap a value in a contextFold: Extract a value from a context
Comparison to Idiomatic Go
Section titled “Comparison to Idiomatic Go”This section explains how functional APIs differ from idiomatic Go and how to convert between them.
Pure Functions
Section titled “Pure Functions”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
Section titled “Without Errors”If your pure function doesn’t return an error, the idiomatic signature works as-is:
func add(a, b int) int { return a + b}With Errors
Section titled “With Errors”Idiomatic Go:
func divide(a, b int) (int, error) { if b == 0 { return 0, errors.New("division by zero") } return a / b, nil}Functional Style:
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)}Conversion:
- Use
either.EitherizeXXXto convert from idiomatic to functional style - Use
either.UneitherizeXXXto convert from functional to idiomatic style
Effectful Functions
Section titled “Effectful Functions”An effectful function changes data outside its scope or doesn’t always produce the same output for the same input.
Without Errors
Section titled “Without Errors”Functional signature: IO[T]
func getCurrentTime() io.IO[time.Time] { return func() time.Time { return time.Now() }}With Errors
Section titled “With Errors”Functional signature: IOEither[error, T]
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.EitherizeXXXto convert idiomatic Go functions to functional style
Go Context
Section titled “Go Context”Functions that take a context.Context are effectful because they depend on mutable context.
Idiomatic Go:
func fetchData(ctx context.Context, url string) ([]byte, error) { // implementation}Functional Style:
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.EitherizeXXXto convert idiomatic Go functions with context to functional style
Implementation Notes
Section titled “Implementation Notes”Generics
Section titled “Generics”All monadic operations use Go generics for type safety:
- ✅ 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
anytype
Ordering of Generic Type Parameters
Section titled “Ordering of Generic Type Parameters”Go requires all type parameters on the global function definition. Parameters that cannot be auto-detected come first:
// Map: B cannot be auto-detected, so it comes firstfunc 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 argumentfunc Ap[B, R, E, A any]!(fa ReaderIOEither[R, E, A]) func(ReaderIOEither[R, E, func(A) B]) ReaderIOEither[R, E, B]This ordering maximizes type inference where possible.
Use of the ~ Operator
Section titled “Use of the ~ Operator”Go doesn’t support generic type aliases (until Go 1.24), only type definitions. The ~ operator allows generic implementations to work with compatible types:
type ReaderIOEither[R, E, A any] RD.Reader[R, IOE.IOEither[E, A]]Generic Subpackages:
- Each higher-level type has a
genericsubpackage with fully generic implementations - These are for library extensions, not end-users
- Main packages specialize generic implementations for convenience
Higher Kinded Types (HKT)
Section titled “Higher Kinded Types (HKT)”Go doesn’t support HKT natively. This library addresses this by:
- Introducing HKTs as individual types (e.g.,
HKTAforHKT[A]) - Implementing generic algorithms in the
internalpackage - Keeping complexity hidden from end-users
Common Operations
Section titled “Common Operations”Map/Chain/Ap/Flap
Section titled “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
Section titled “Example: Chaining Operations”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 }),)📚 Resources
Section titled “📚 Resources”- API Documentation
- [Code Samples]!(./samples/)
- [V2 Documentation]!(./v2/README.md) - New features in Go 1.24+
- fp-ts - Original TypeScript inspiration
🤝 Contributing
Section titled “🤝 Contributing”Contributions are welcome! Please feel free to submit issues or pull requests.
📄 License
Section titled “📄 License”This project is licensed under the Apache License 2.0 - see the LICENSE file for details.