Functional programming library for Go 1.24+, inspired by fp-ts. Uses generic type aliases for a clean, composable API. Provides Option, Either, Result, IO, IOResult, Reader, and ReaderIOResult monads, plus optics (Lens, Prism, Traversal) for immutable data manipulation. Supports Functor, Applicative, and Monad abstractions with do-notation-style
2,020
stars
800
commits
Go
primary language
Sep 8, 2026
updated
π§ Work in progress! π§ Despite major version 1 (due to semantic-release limitations), we're working to minimize breaking changes.

A comprehensive functional programming library for Go, strongly influenced by the excellent fp-ts library for TypeScript.
go get github.com/IBM/fp-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
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:
IO monadThis 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 |
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 computationAll 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 contextThis section explains how functional APIs differ from idiomatic Go and how to convert between them.
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.
If your pure function doesn't return an error, the idiomatic signature works as-is:
func add(a, b int) int {
return a + b
}
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:
either.EitherizeXXX to convert from idiomatic to functional styleeither.UneitherizeXXX to convert from functional to idiomatic styleAn effectful function changes data outside its scope or doesn't always produce the same output for the same input.
Functional signature: IO[T]
func getCurrentTime() io.IO[time.Time] {
return func() time.Time {
return time.Now()
}
}
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:
ioeither.EitherizeXXX to convert idiomatic Go functions to functional styleFunctions 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:
readerioeither.EitherizeXXX to convert idiomatic Go functions with context to functional styleAll monadic operations use Go generics for type safety:
any typeGo 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 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]
This ordering maximizes type inference where possible.
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:
generic subpackage with fully generic implementationsGo doesn't support HKT natively. This library addresses this by:
HKTA for HKT[A])internal package| 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 |
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 }),
)
Contributions are welcome! Please feel free to submit issues or pull requests.
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
Go
100.0%
Functional programming library for Go 1.24+, inspired by fp-ts. Uses generic type aliases for a clean, composable API. Provides Option, Either, Result, IO, IOResult, Reader, and ReaderIOResult monads, plus optics (Lens, Prism, Traversal) for immutable data manipulation. Supports Functor, Applicative, and Monad abstractions with do-notation-style
2,020
stars
800
commits
Go
primary language
Sep 8, 2026
updated
π§ Work in progress! π§ Despite major version 1 (due to semantic-release limitations), we're working to minimize breaking changes.

A comprehensive functional programming library for Go, strongly influenced by the excellent fp-ts library for TypeScript.
go get github.com/IBM/fp-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
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:
IO monadThis 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 |
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 computationAll 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 contextThis section explains how functional APIs differ from idiomatic Go and how to convert between them.
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.
If your pure function doesn't return an error, the idiomatic signature works as-is:
func add(a, b int) int {
return a + b
}
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:
either.EitherizeXXX to convert from idiomatic to functional styleeither.UneitherizeXXX to convert from functional to idiomatic styleAn effectful function changes data outside its scope or doesn't always produce the same output for the same input.
Functional signature: IO[T]
func getCurrentTime() io.IO[time.Time] {
return func() time.Time {
return time.Now()
}
}
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:
ioeither.EitherizeXXX to convert idiomatic Go functions to functional styleFunctions 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:
readerioeither.EitherizeXXX to convert idiomatic Go functions with context to functional styleAll monadic operations use Go generics for type safety:
any typeGo 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 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]
This ordering maximizes type inference where possible.
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:
generic subpackage with fully generic implementationsGo doesn't support HKT natively. This library addresses this by:
HKTA for HKT[A])internal package| 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 |
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 }),
)
Contributions are welcome! Please feel free to submit issues or pull requests.
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
Go
100.0%