# fp-go fp-go is a comprehensive functional programming library for Go, bringing type-safe monads, functors, applicatives, optics, and composable abstractions inspired by fp-ts and Haskell to the Go ecosystem. Created by IBM and licensed under Apache-2.0, it provides data types and functions that make it easy to write maintainable and testable code using functional programming patterns. Version 2 requires Go 1.24+ and leverages generic type aliases for a cleaner API. The library follows the "data last" principle for natural function composition and provides both struct-based packages for maximum type safety and idiomatic tuple-based packages for high performance. Key concepts include `Option` for nullable values, `Either`/`Result` for error handling, `IO` for lazy side effects, `Reader` for dependency injection, `IOResult` for effectful error handling, and `Optics` (lens, prism, traversal) for immutable data manipulation. ## Result - Type-Safe Error Handling The Result monad represents computations that may fail with an error. It's an alias for `Either[error, A]` where Left contains an error and Right contains the success value. This provides explicit, composable error handling. ```go import ( "errors" "fmt" "github.com/IBM/fp-go/v2/result" F "github.com/IBM/fp-go/v2/function" ) // Create Result values success := result.Right[int]("value") // Right("value") failure := result.Left[string](errors.New("error")) // Left(error) // Create from idiomatic Go (value, error) tuple fromTuple := result.TryCatchError(42, nil) // Right(42) // Create from predicate isEven := func(n int) bool { return n%2 == 0 } fromPred := result.FromPredicate( isEven, func(n int) error { return fmt.Errorf("%d is odd", n) }, ) result1 := fromPred(4) // Right(4) result2 := fromPred(3) // Left("3 is odd") // Create from nullable pointer fromPtr := result.FromNillable[string](errors.New("nil value")) var ptr *string = nil nilResult := fromPtr(ptr) // Left(error) // Map transforms the success value doubled := F.Pipe1( result.Right[int](21), result.Map(func(x int) int { return x * 2 }), ) // Right(42) // Chain sequences computations that may fail divide := func(a, b int) result.Result[int] { if b == 0 { return result.Left[int](errors.New("division by zero")) } return result.Right[int](a / b) } computed := F.Pipe2( result.Right[int](100), result.Chain(func(x int) result.Result[int] { return divide(x, 5) }), result.Map(func(x int) string { return fmt.Sprintf("Result: %d", x) }), ) // Right("Result: 20") // Fold extracts value by handling both cases output := result.Fold( func(err error) string { return "Error: " + err.Error() }, func(n int) string { return fmt.Sprintf("Value: %d", n) }, )(result.Right[int](42)) // "Value: 42" // GetOrElse provides default for errors value := result.GetOrElse(func(err error) int { return 0 })( result.Left[int](errors.New("fail")), ) // 0 // Unwrap converts to idiomatic Go tuple val, err := result.Unwrap(result.Right[int](42)) // 42, nil // OrElse recovers from specific errors recovered := F.Pipe1( result.Left[int](errors.New("not found")), result.OrElse(func(err error) result.Result[int] { if err.Error() == "not found" { return result.Right[int](0) // default value } return result.Left[int](err) // propagate }), ) // Right(0) ``` ## IOResult - Lazy IO with Error Handling IOResult combines IO (lazy side effects) with Result (error handling). It represents a synchronous computation that may fail. Operations are deferred until explicitly executed. ```go import ( "errors" "os" "github.com/IBM/fp-go/v2/ioresult" F "github.com/IBM/fp-go/v2/function" ) // Create IOResult values success := ioresult.Right[int]("hello") // IOResult that returns Right("hello") failure := ioresult.Left[string](errors.New("fail")) // IOResult that returns Left(error) // Lift pure value into IOResult pure := ioresult.Of("value") // IOResult returning Right("value") // Convert (value, error) function to IOResult readFile := ioresult.TryCatchError(func() ([]byte, error) { return os.ReadFile("config.json") }) // Map transforms the success value (lazily) transformed := F.Pipe1( ioresult.Right[int](21), ioresult.Map(func(x int) int { return x * 2 }), ) // Execute: transformed() returns Right(42) // Chain sequences IO operations fetchAndProcess := F.Pipe2( ioresult.Right[int](1), ioresult.Chain(func(id int) ioresult.IOResult[string] { // Simulate fetching data return ioresult.Right[string](fmt.Sprintf("User-%d", id)) }), ioresult.Map(func(s string) string { return "Processed: " + s }), ) // Execute: fetchAndProcess() returns Right("Processed: User-1") // ChainFirst executes side effect but preserves original value logged := F.Pipe1( ioresult.Right[int](42), ioresult.ChainFirst(func(x int) ioresult.IOResult[any] { fmt.Println("Value:", x) // side effect return ioresult.Right[any](nil) }), ) // Execute: logged() prints "Value: 42" and returns Right(42) // Fold converts IOResult to IO toIO := ioresult.Fold( func(err error) func() string { return func() string { return "Error: " + err.Error() } }, func(val int) func() string { return func() string { return fmt.Sprintf("Success: %d", val) } }, )(ioresult.Right[int](42)) // Execute: toIO() returns "Success: 42" // GetOrElse extracts value with fallback getValue := ioresult.GetOrElse(func(err error) func() int { return func() int { return 0 } })(ioresult.Left[int](errors.New("fail"))) // Execute: getValue() returns 0 // Memoize caches the result expensive := ioresult.Memoize(ioresult.TryCatchError(func() (int, error) { // Expensive computation only runs once return 42, nil })) // Multiple calls to expensive() return cached result // Retry failed operations retried := F.Pipe1( ioresult.Right[int](1), ioresult.Delay[int](100*time.Millisecond), // Add delay ) ``` ## Effect - Composable Effect System Effect is the core type for managing dependencies, errors, and side effects. It combines Reader (dependency injection), IO (side effects), and Result (error handling) into a single composable type. ```go import ( "context" "fmt" "github.com/IBM/fp-go/v2/effect" F "github.com/IBM/fp-go/v2/function" ) // Define your context/dependency type type AppContext struct { DBHost string APIKey string LogLevel string } // Create successful effect success := effect.Succeed[AppContext](42) // Create failed effect failed := effect.Fail[AppContext, int](errors.New("failed")) // Map transforms the success value doubled := effect.Map[AppContext](func(x int) int { return x * 2 })(effect.Succeed[AppContext](21)) // Chain sequences dependent computations fetchUser := func(id int) effect.Effect[AppContext, string] { return effect.Succeed[AppContext](fmt.Sprintf("User-%d", id)) } pipeline := F.Pipe2( effect.Succeed[AppContext](1), effect.Chain[AppContext](fetchUser), effect.Map[AppContext](func(name string) string { return "Hello, " + name }), ) // Tap executes side effect but preserves value withLogging := effect.Tap[AppContext, int, any](func(x int) effect.Effect[AppContext, any] { fmt.Printf("Processing: %d\n", x) return effect.Succeed[AppContext, any](nil) })(effect.Succeed[AppContext](42)) // ChainIOK integrates pure IO operations withIO := effect.ChainIOK[AppContext](func(n int) func() string { return func() string { return fmt.Sprintf("Value: %d", n) } })(effect.Succeed[AppContext](42)) // ChainResultK integrates Result operations withResult := effect.ChainResultK[AppContext](func(n int) result.Result[string] { if n > 0 { return result.Right[string](fmt.Sprintf("Positive: %d", n)) } return result.Left[string](errors.New("not positive")) })(effect.Succeed[AppContext](42)) // Ternary for conditional logic conditional := effect.Ternary[AppContext, int, string]( func(x int) bool { return x > 10 }, func(x int) effect.Effect[AppContext, string] { return effect.Succeed[AppContext]("large") }, func(x int) effect.Effect[AppContext, string] { return effect.Succeed[AppContext]("small") }, ) // Suspend for lazy/recursive effects var factorial func(int) effect.Effect[AppContext, int] factorial = func(n int) effect.Effect[AppContext, int] { if n <= 1 { return effect.Succeed[AppContext](1) } return effect.Suspend(func() effect.Effect[AppContext, int] { return effect.Map[AppContext](func(x int) int { return x * n })(factorial(n - 1)) }) } // Running effects: Provide context, then execute ctx := AppContext{DBHost: "localhost", APIKey: "secret", LogLevel: "info"} // Provide converts Effect[C, A] to ReaderIOResult[A] thunk := effect.Provide[int](ctx)(effect.Succeed[AppContext](42)) // RunSync executes and returns (value, error) value, err := effect.RunSync(thunk)(context.Background()) // value == 42, err == nil // Complete example result, err := effect.RunSync( effect.Provide[string](ctx)(pipeline), )(context.Background()) // result == "Hello, User-1", err == nil ``` ## Function Composition - Pipe and Flow The function package provides utilities for composing functions in a point-free style, following the data-last principle. ```go import ( F "github.com/IBM/fp-go/v2/function" A "github.com/IBM/fp-go/v2/array" N "github.com/IBM/fp-go/v2/number" "github.com/IBM/fp-go/v2/result" ) // Pipe applies transformations to a value (left to right) result1 := F.Pipe3( 10, func(x int) int { return x * 2 }, // 20 func(x int) int { return x + 5 }, // 25 func(x int) string { return fmt.Sprintf("Value: %d", x) }, ) // "Value: 25" // Flow creates a composed function (left to right) processNumber := F.Flow3( func(x int) int { return x * 2 }, func(x int) int { return x + 10 }, func(x int) string { return fmt.Sprintf("Result: %d", x) }, ) result2 := processNumber(5) // "Result: 20" // Compose creates a composed function (right to left, mathematical notation) composed := F.Compose( func(x int) string { return fmt.Sprintf("Final: %d", x) }, func(x int) int { return x * 2 }, ) result3 := composed(21) // "Final: 42" // Real-world pipeline example type User struct { Name string Age int Email string } validateAge := result.FromPredicate( func(u User) bool { return u.Age >= 18 }, func(u User) error { return errors.New("must be 18+") }, ) validateEmail := result.FromPredicate( func(u User) bool { return strings.Contains(u.Email, "@") }, func(u User) error { return errors.New("invalid email") }, ) validateUser := F.Flow2( validateAge, result.Chain(validateEmail), ) user := User{Name: "Alice", Age: 25, Email: "alice@example.com"} validationResult := validateUser(user) // Right(User{...}) // Array pipeline numbers := []int{-2, -1, 0, 1, 2, 3, 4, 5} pipeline := F.Flow3( A.Filter(N.MoreThan(0)), // [1, 2, 3, 4, 5] A.Map(func(x int) int { return x * 2 }), // [2, 4, 6, 8, 10] A.Reduce(func(acc, x int) int { return acc + x }, 0), // 30 ) sum := pipeline(numbers) // 30 // Partial application with data-last getOrZero := result.GetOrElse(func(err error) int { return 0 }) getOrEmpty := result.GetOrElse(func(err error) string { return "" }) value1 := getOrZero(result.Right[int](42)) // 42 value2 := getOrZero(result.Left[int](err)) // 0 ``` ## Optics - Lens for Immutable Updates Lenses provide a composable way to focus on and update nested data structures immutably. ```go import ( "github.com/IBM/fp-go/v2/optics/lens" F "github.com/IBM/fp-go/v2/function" ) // Define your data structures type Address struct { Street string City string Zip string } type Person struct { Name string Age int Address Address } // Create lenses for each field nameLens := lens.MakeLens( func(p Person) string { return p.Name }, func(p Person, name string) Person { p.Name = name; return p }, ) ageLens := lens.MakeLens( func(p Person) int { return p.Age }, func(p Person, age int) Person { p.Age = age; return p }, ) addressLens := lens.MakeLens( func(p Person) Address { return p.Address }, func(p Person, addr Address) Person { p.Address = addr; return p }, ) streetLens := lens.MakeLens( func(a Address) string { return a.Street }, func(a Address, street string) Address { a.Street = street; return a }, ) // Get values using lenses person := Person{ Name: "Alice", Age: 30, Address: Address{Street: "123 Main St", City: "Boston", Zip: "02101"}, } name := nameLens.Get(person) // "Alice" age := ageLens.Get(person) // 30 // Set values immutably updated := nameLens.Set("Bob")(person) // Person{Name: "Bob", Age: 30, Address: ...} // Modify values with a function older := ageLens.Modify(func(age int) int { return age + 1 })(person) // Person{Name: "Alice", Age: 31, Address: ...} // Compose lenses for nested access personStreetLens := lens.Compose(addressLens, streetLens) street := personStreetLens.Get(person) // "123 Main St" movedPerson := personStreetLens.Set("456 Oak Ave")(person) // Person{..., Address: Address{Street: "456 Oak Ave", ...}} // Use with Result for validated updates incrementAge := result.BindL(ageLens, func(age int) result.Result[int] { if age >= 150 { return result.Left[int](errors.New("age too high")) } return result.Right[int](age + 1) }) validPerson := F.Pipe1( result.Right[Person](person), incrementAge, ) // Right(Person{Age: 31, ...}) // Use with lens helpers in result package setName := result.LetToL(nameLens, "Charlie") doubled := result.LetL(ageLens, func(age int) int { return age * 2 }) transformed := F.Pipe2( result.Right[Person](person), setName, doubled, ) // Right(Person{Name: "Charlie", Age: 60, ...}) ``` ## Do Notation - Sequential Binding Do notation provides a clean way to sequence multiple Result operations and build up a context. ```go import ( "github.com/IBM/fp-go/v2/result" F "github.com/IBM/fp-go/v2/function" ) // Define a context struct to accumulate values type OrderContext struct { UserID int ProductID int Quantity int Price float64 Total float64 } // Simulated async operations fetchUser := func(id int) result.Result[int] { if id <= 0 { return result.Left[int](errors.New("invalid user")) } return result.Right[int](id) } fetchProduct := func(id int) result.Result[int] { if id <= 0 { return result.Left[int](errors.New("invalid product")) } return result.Right[int](id) } fetchPrice := func(productID int) result.Result[float64] { prices := map[int]float64{1: 10.0, 2: 25.0, 3: 50.0} if price, ok := prices[productID]; ok { return result.Right[float64](price) } return result.Left[float64](errors.New("product not found")) } // Build order using Do notation order := F.Pipe6( // Start with empty context result.Do(OrderContext{}), // Bind user ID result.Bind( func(userID int) func(OrderContext) OrderContext { return func(ctx OrderContext) OrderContext { ctx.UserID = userID return ctx } }, func(ctx OrderContext) result.Result[int] { return fetchUser(1) }, ), // Bind product ID result.Bind( func(productID int) func(OrderContext) OrderContext { return func(ctx OrderContext) OrderContext { ctx.ProductID = productID return ctx } }, func(ctx OrderContext) result.Result[int] { return fetchProduct(2) }, ), // Let (pure computation) for quantity result.Let( func(qty int) func(OrderContext) OrderContext { return func(ctx OrderContext) OrderContext { ctx.Quantity = qty return ctx } }, func(ctx OrderContext) int { return 3 }, ), // Bind price based on product result.Bind( func(price float64) func(OrderContext) OrderContext { return func(ctx OrderContext) OrderContext { ctx.Price = price return ctx } }, func(ctx OrderContext) result.Result[float64] { return fetchPrice(ctx.ProductID) }, ), // Let for calculated total result.Let( func(total float64) func(OrderContext) OrderContext { return func(ctx OrderContext) OrderContext { ctx.Total = total return ctx } }, func(ctx OrderContext) float64 { return float64(ctx.Quantity) * ctx.Price }, ), ) // order is Right(OrderContext{UserID: 1, ProductID: 2, Quantity: 3, Price: 25.0, Total: 75.0}) ``` ## Idiomatic Result - High-Performance Tuple-Based The idiomatic packages use Go's native `(value, error)` tuple pattern for 2-10x better performance with zero allocations. ```go import ( result "github.com/IBM/fp-go/v2/idiomatic/result" F "github.com/IBM/fp-go/v2/function" ) // Idiomatic result uses (value, error) tuples directly // Kleisli is func(A) (B, error) // Operator is func(A, error) (B, error) // Create values - returns (T, error) tuple success, err := result.Right[error](42) // (42, nil) failure, err := result.Left[int](errors.New("fail")) // (0, error) // Map transforms success value doubled, err := result.Map(func(x int) int { return x * 2 })(42, nil) // (84, nil) // Chain sequences operations that may fail validate := func(x int) (int, error) { if x < 0 { return 0, errors.New("negative") } return x, nil } chained, err := result.Chain(validate)(42, nil) // (42, nil) // Fold extracts value handling both cases output := result.Fold( func(err error) string { return "Error" }, func(n int) string { return fmt.Sprintf("Value: %d", n) }, )(42, nil) // "Value: 42" // FromPredicate creates result from condition isPositive := result.FromPredicate( func(n int) bool { return n > 0 }, func(n int) error { return fmt.Errorf("%d not positive", n) }, ) pos, err := isPositive(42) // (42, nil) neg, err := isPositive(-1) // (0, error) // Works with standard Go functions parseResult, err := result.TryCatchError(strconv.Atoi("42")) // (42, nil) // Compose with flow pipeline := F.Flow2( result.Map(func(x int) int { return x * 2 }), result.Chain(func(x int) (string, error) { return fmt.Sprintf("Result: %d", x), nil }), ) final, err := pipeline(21, nil) // ("Result: 42", nil) ``` ## CircuitBreaker - Resilience Pattern The circuit breaker pattern prevents cascading failures by stopping requests to failing services. ```go import ( "time" "github.com/IBM/fp-go/v2/circuitbreaker" "github.com/IBM/fp-go/v2/retry" ) // Create initial closed state with failure threshold initialState := circuitbreaker.ClosedState{ FailureCount: 0, MaxFailures: 5, } // Create IORef to hold mutable circuit state (thread-safe) circuitRef := circuitbreaker.MakeClosedIORef(initialState) // Define retry policy for open state policy := retry.ExponentialBackoff( 1*time.Second, // initial delay 2.0, // multiplier 10, // max retries ) // Create circuit breaker that wraps operations // When closed: requests pass through // When open: requests fail fast // Half-open: one canary request allowed to test recovery // Check circuit state ref := circuitRef() state := ref.Get()() if circuitbreaker.IsClosed(state) { // Circuit is closed, requests allowed fmt.Println("Circuit closed - accepting requests") } else if circuitbreaker.IsOpen(state) { // Circuit is open, requests blocked fmt.Println("Circuit open - rejecting requests") } ``` ## Summary fp-go provides a comprehensive functional programming toolkit for Go developers seeking type-safe, composable abstractions. The primary use cases include building reliable data pipelines with explicit error handling using `Result` and `IOResult`, managing complex dependencies through the `Effect` system, and performing immutable data transformations with optics. The library excels in scenarios requiring composition of multiple fallible operations, clean separation of pure and effectful code, and testable business logic through dependency injection. Integration patterns typically involve wrapping existing Go code with `TryCatchError` to lift `(value, error)` tuples into the functional world, using `Pipe` and `Flow` for building transformation pipelines, and leveraging the `Effect` type for complex applications requiring dependency injection. For performance-critical paths, the idiomatic packages provide the same functional API using Go's native tuple patterns, offering 2-10x speedup with zero allocations while maintaining composability.