ibm/fp-go · Archived

fp-go

Use this skill whenever writing, reviewing, or refactoring Go code that uses the fp-go library (github.com/IBM/fp-go/v2).

First seen Jun 23, 2026

Installation

$ npx skills add ibm/fp-go --skill fp-go

Summary

  • Use this skill whenever writing, reviewing, or refactoring Go code that uses the fp-go library (github.com/IBM/fp-go/v2).
  • Trigger on any mention of fp-go, functional programming in Go, monads in Go, Option/Either/Result types in Go, IOResult, ReaderIOResult, data-last composition, Pipe/Flow, or do-notation with Bind/ApS in Go.
  • Also trigger when the user wants to convert idiomatic Go error handling into functional pipelines, or asks about optics (lens, prism, traversal) in Go.

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Also in this package

Other skills from ibm/fp-go.

npx skills add ibm/fp-go

Browse all from ibm/fp-go

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 2.0K
License LICENSE
Default branch main
Open issues 2
Status Archived

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 27,706 B
  • docs SUMMARY.md 493 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 2 installs

SKILL.md

fp-go v2 — Functional Programming for Go

Critical Rules for Code Generation

  1. Import path is v2: always github.com/IBM/fp-go/v2/..., never github.com/IBM/fp-go/... (that is v1).
  2. Data-last: all operations return a function waiting for data. Write option.Map(f)(value), never option.Map(value, f).
  3. Non-inferrable type parameters come first. Whatever the compiler cannot recover from the arguments is declared first, so you can annotate just that prefix. Concretely:

- Map[A, B](f func(A) B) and Chain[A, B](f Kleisli[A, B]) — both params are inferable from f; write option.Map(f) with no annotation. - Ap[B, A](fa M[A]) — B is not recoverable from fa, so it leads: option.Ap[int](fa). - In either/reader/readerio*, the error or environment type leads: either.Map[E, A, B], reader.Map[R, A, B]. Annotate only that head: either.Map[error](f), reader.Map[context.Context](f). - Result, Option, IOResult and the other error-specialized monads have no leading param, which is one more reason to prefer them.

  1. Prefer Result over Either when the error type is Go's error. Result[A] is Either[error, A]. Same for ioresult over ioeither, readerioresult over readerioeither.
  2. IO values are lazy: IO[A] is func() A. They describe a computation — you must call () to execute. Don't forget the trailing (). Running an IOResult[A] or ReaderIOResult[A] produces one value, a Result[A] — not a (A, error) pair. Use result.Unwrap to reach idiomatic Go:

``go res := pipeline(ctx)() // Result[A] — a single value value, err := result.Unwrap(res) // (A, error) ` value, err := pipeline(ctx)() is a compile error. Alternatively, stay functional and eliminate the Result with result.Fold, or use one of the idiomatic/ packages, whose types are (A, error)` tuples end to end.

  1. Prefer point-free style: compose with F.Flow and F.Pipe instead of writing inline anonymous functions. If a transformation can be expressed as a composition of named functions, it should be. Point-free pipelines are idiomatic fp-go.

Generation Workflow

Two habits that matter more for fp-go than for idiomatic Go, because the library is low-frequency in training data and easy to misremember:

  1. Retrieve before generating. For any pattern not fully covered below — optics beyond simple lenses, traversals, concurrent combinators, the less-common monads — query the fp-go MCP server's searchexamples / getexample tools (see the fp-go-mcp skill) for a real signature instead of recalling names like Chain / FlatMap / Bind from memory. Retrieve-then-generate beats generate-then-fix here.
  2. Compile before presenting. After writing fp-go code, run go build ./... and go vet ./..., then fix any import, type-parameter, or argument-order error and re-run until clean. The compiler is precise, low-ambiguity feedback, and most fp-go mistakes (wrong leading type param, data-first vs data-last) surface immediately.

Overview

fp-go (import path github.com/IBM/fp-go/v2) brings type-safe functional programming to Go using generics. Every monad follows a consistent interface: once you know the pattern in one monad, it transfers to all others.

All functions use the data-last principle: the data being transformed is always the last argument, enabling partial application and pipeline composition.

Core Types

Type Package Represents
Option[A] option A value that may or may not be present (replaces nil)
Either[E, A] either A value that is either a left error E or a right success A
Result[A] result Either[error, A] — recommended default for error handling
IO[A] io A lazy computation that produces A (possibly with side effects)
IOResult[A] ioresult IO[Result[A]] — lazy computation that can fail
ReaderIOResult[A] context/readerioresult func(context.Context) IOResult[A] — context-aware IO with errors
Effect[C, A] effect func(C) ReaderIOResult[A] — typed dependency injection + IO + errors; recommended for services

Idiomatic Packages (high-performance, tuple-based)

The idiomatic/ packages use Go-native tuples instead of struct wrappers, offering 2–10× better performance and zero allocations. Use them in hot paths; use standard packages when you need the richer API surface.

  • idiomatic/option — (A, bool) tuples
  • idiomatic/result — (A, error) tuples
  • idiomatic/ioresult — func() (A, error)
  • idiomatic/readerresult — func(R) (A, error)
  • idiomatic/readerioresult — func(R) func() (A, error)
  • idiomatic/context/readerresult — func(context.Context) (A, error)

Because idiomatic operators have the shape func(A, bool) (B, bool) / func(A, error) (B, error) — two arguments — F.Pipe cannot start them. Compose with F.FlowN and spread the multi-return into the call: v, ok := F.Flow2(Map(f), Filter(p))(Do(seed)). Never mix an idiomatic and a standard package for the same monad in one file; they are different types.

Standard Operations

Every monad exports these operations (PascalCase for exported Go names):

fp-go fp-ts / Haskell Description
Of of / pure Lift a pure value into the monad
Map map / fmap Transform the value inside without changing the context
Chain chain / >>= Sequence a computation that itself returns a monadic value
Ap ap / <*> Apply a wrapped function to a wrapped value
Fold fold / either Eliminate the context — handle every case and extract a plain value
GetOrElse getOrElse / fromMaybe Extract the value or use a default (Option/Result)
Filter filter / mfilter Keep only values satisfying a predicate
Flatten flatten / join Remove one level of nesting (M[M[A]] → M[A])
ChainFirst chainFirst / >> Sequence for side effects; keeps the original value
Alt alt / `< >` Provide an alternative when the first computation fails
FromPredicate fromPredicate / guard Build a monadic value from a predicate
Sequence sequence Turn []M[A] into M[[]A]
Traverse traverse Map and sequence in one step

Curried (composable) vs. monadic (direct) form:

// Curried — data last, returns a transformer function
option.Map(strings.ToUpper)              // func(Option[string]) Option[string]

// Monadic — data first, immediate execution
option.MonadMap(option.Some("hello"), strings.ToUpper)

Use curried form for pipelines; use Monad* form when you already have all arguments.

Key Type Aliases (defined per monad)

// A Kleisli arrow: a function from A to a monadic B
type Kleisli[A, B any] = func(A) M[B]

// An operator: transforms one monadic value into another
type Operator[A, B any] = func(M[A]) M[B]

Chain takes a Kleisli, Map returns an Operator. The naming is consistent across all monads.

Function Composition with Flow and Pipe (Point-Free Style)

fp-go is designed for point-free programming: compose named functions with Flow and Pipe rather than writing inline anonymous functions. This makes pipelines more readable and eliminates intermediate variable naming.

import (
    F  "github.com/IBM/fp-go/v2/function"
    S  "github.com/IBM/fp-go/v2/string"
    LZ "github.com/IBM/fp-go/v2/lazy"
)

// ✅ GOOD: point-free — compose named functions, no lambda noise
pipeline := F.Flow3(
    R.Eitherize1(strconv.Atoi),
    R.Map(N.Mul(2)),
    R.GetOrElse(F.Constant1[error](0)), // Result's GetOrElse takes func(error) A
)

// ❌ AVOID: wrapping in unnecessary anonymous functions
pipeline := F.Flow3(
    func(s string) R.Result[int] { return R.Eitherize1(strconv.Atoi)(s) },
    func(r R.Result[int]) R.Result[int] { return R.Map(func(n int) int { return n * 2 })(r) },
    func(r R.Result[int]) int { return R.GetOrElse(func(error) int { return 0 })(r) },
)

Watch the GetOrElse shape: option.GetOrElse takes func() A (use LZ.Of(v)), while result.GetOrElse / either.GetOrElse take func(error) A / func(E) A (use F.Constant1[error](v)).

The data-last design means every fp-go operation already returns a function — so you almost never need to wrap them in a lambda. When you do need to adapt arguments, use F.Flow2 to compose:

// Point-free: compose a lens getter with a Kleisli arrow
RIO.Bind(configLens.Set, F.Flow2(userLens.Get, fetchConfigForUser))

// Instead of:
RIO.Bind(configLens.Set, func(s Pipeline) RIO.ReaderIOResult[Config] {
    return fetchConfigForUser(userLens.Get(s))
})

Two forms:

// Flow: compose functions left-to-right, returns a new function
transform := F.Flow3(
    option.Map(strings.TrimSpace),
    option.Filter(S.IsNonEmpty),
    option.GetOrElse(LZ.Of("default")),
)
result := transform(option.Some("  hello  ")) // "hello"

// Pipe: apply a value through a pipeline immediately
result := F.Pipe3(
    option.Some("  hello  "),
    option.Map(strings.TrimSpace),
    option.Filter(S.IsNonEmpty),
    option.GetOrElse(LZ.Of("default")),
)

Pipe1–Pipe20 and Flow1–Flow20 are available (the number = number of transformation steps).

Lifting Go Functions into Monadic Context

Helper Lifts
Eitherize1..EitherizeN func(args...) (B, error) → func(args...) Result[B] — primary bridge from Go to fp-go
ChainEitherK / ChainResultK func(A) Result[B] → works inside the monad. It does not accept a bare func(A) (B, error) — wrap that in result.Eitherize1 first.
ChainOptionK(onNone) func(A) Option[B] → works inside the monad; takes the func() error fallback first
ChainFirstIOK func(A) IO[B] for side effects, keeps original value
FromPredicate func(A) bool + error builder → func(A) Result[A]

Examples

Option — nullable values without nil

import (
    O  "github.com/IBM/fp-go/v2/option"
    F  "github.com/IBM/fp-go/v2/function"
    S  "github.com/IBM/fp-go/v2/string"
    P  "github.com/IBM/fp-go/v2/optics/prism"
    "strconv"
)

parseAndDouble := F.Flow3(
    O.FromPredicate(S.IsNonEmpty),
    O.Chain(P.ParseInt().GetOption),
    O.Map(N.Mul(2)),
)

parseAndDouble("21")  // Some(42)
parseAndDouble("")    // None
parseAndDouble("abc") // None

Result — error handling without if-err boilerplate

import (
    R  "github.com/IBM/fp-go/v2/result"
    F  "github.com/IBM/fp-go/v2/function"
    N  "github.com/IBM/fp-go/v2/number"
    P  "github.com/IBM/fp-go/v2/predicate"
    ER "github.com/IBM/fp-go/v2/errors"
    "strconv"
    "errors"
)

parse := R.Eitherize1(strconv.Atoi)  // lifts (int, error) → Result[int]

validate := R.FromPredicate(
    P.Not(N.LessThan(0)),
    ER.OnSome[int]("%d must not be negative"),
)

pipeline := F.Flow2(parse, R.Chain(validate))

pipeline("42")   // Ok(42)
pipeline("-1")   // Error("must be non-negative")
pipeline("abc")  // Error(strconv parse error)

IOResult — lazy IO with error handling

import (
    IOE "github.com/IBM/fp-go/v2/ioresult"
    F   "github.com/IBM/fp-go/v2/function"
    J   "github.com/IBM/fp-go/v2/json"
    "github.com/IBM/fp-go/v2/result"
    "os"
)

readConfig := F.Flow2(
    IOE.Eitherize1(os.ReadFile),           // func(string) IOResult[[]byte]
    IOE.ChainEitherK(J.Unmarshal[Config]), // parse JSON, propagate errors
)

res := readConfig("config.json")()       // Result[Config] — note the trailing ()
cfg, err := result.Unwrap(res)           // bridge back to idiomatic Go

ReaderIOResult — context-aware pipelines (recommended for services)

import (
    RIO "github.com/IBM/fp-go/v2/context/readerioresult"
    F   "github.com/IBM/fp-go/v2/function"
    IO  "github.com/IBM/fp-go/v2/io"
    "github.com/IBM/fp-go/v2/result"
    "context"
)

// type ReaderIOResult[A any] = func(context.Context) func() result.Result[A]

fetchUser := func(id int) RIO.ReaderIOResult[User] {
    return func(ctx context.Context) func() result.Result[User] {
        return func() result.Result[User] {
            // perform IO here
        }
    }
}

// validateUser is a plain Go func(User) (User, error) — Eitherize it first
pipeline := F.Pipe3(
    fetchUser(42),
    RIO.ChainResultK(result.Eitherize1(validateUser)), // Kleisli: User → Result[User]
    RIO.Map(enrichUser),                               // lift pure User → User function
    RIO.ChainFirstIOK(IO.Logf[User]("Fetched: %v")),   // side-effect logging
)

res := pipeline(ctx)()               // Result[User] — ONE value, not (User, error)
user, err := result.Unwrap(res)      // bridge back to idiomatic Go

Effect — typed dependency injection (recommended for testable services)

Effect[C, A] adds a typed dependency parameter C on top of ReaderIOResult. While context/readerioresult hardcodes context.Context as the environment, Effect lets you define a custom dependencies struct — making dependencies explicit, compile-time checked, and trivially mockable in tests.

Effect[C, A] is literally func(C) ReaderIOResult[A] (an alias for context/readerreaderioresult.ReaderReaderIOResult[C, A]). A function of that exact shape is already an Effect — do not wrap it in Asks. EF.Asks(f) is for a pure projection func(C) A and returns Effect[C, A]; passing it a func(C) ReaderIOResult[A] silently yields the nested Effect[C, ReaderIOResult[A]].

Use Effect when your service has dependencies beyond context.Context (database connections, HTTP clients, config, loggers). It is the recommended top-level monad for production service code.

import (
    EF "github.com/IBM/fp-go/v2/effect"
    F  "github.com/IBM/fp-go/v2/function"
    L  "github.com/IBM/fp-go/v2/optics/lens"
)

// 1. Define your dependencies as a struct
type Deps struct {
    DB     DBClient
    Logger Logger
    Config AppConfig
}

// 2. Write effects that declare exactly what they need.
// Effect[Deps, User] IS func(Deps) ReaderIOResult[User] — write it directly, no wrapper.
fetchUser := func(id int) EF.Effect[Deps, User] {
    return func(deps Deps) EF.ReaderIOResult[User] {
        // deps.DB is available here — compile-time checked
        return queryUser(deps.DB, id)
    }
}

enrichWithConfig := func(user User) EF.Effect[Deps, EnrichedUser] {
    return func(deps Deps) EF.ReaderIOResult[EnrichedUser] {
        return RIO.Of(applyConfig(user, deps.Config))
    }
}

// Asks is for PURE projections of the context: func(Deps) A → Effect[Deps, A]
getPrefix := EF.Asks(func(d Deps) string { return d.Config.Prefix })

// 3. Compose effects — same Map/Chain/Bind/ApS API as every other monad.
// C leads the type-parameter list and is not always inferable: annotate EF.Map[Deps].
pipeline := F.Pipe2(
    fetchUser(42),
    EF.Chain(enrichWithConfig),
    EF.Map[Deps](func(u EnrichedUser) string { return u.DisplayName }),
)

// 4. Provide dependencies once at the edge, then run.
// Provide[A, C]: A cannot be inferred through the returned function — annotate it.
thunk := EF.Provide[string](Deps{
    DB:     realDB,
    Logger: zapLogger,
    Config: loadedConfig,
})(pipeline) // ReaderIOResult[string]

value, err := EF.RunSync(thunk)(ctx) // RunSync gives back idiomatic (A, error)
// or: result := thunk(ctx)()        // Result[string]

Why Effect over ReaderIOResult: dependencies are typed (compiler catches missing deps), each function's signature declares what it needs (Effect[Deps, A]), testability is trivial (swap Deps{DB: mockDB}), and EF.Local/EF.Provide narrow or eliminate deps for subsystems.

Lifting into Effect (all take C as a leading, usually explicit, type parameter):

Helper Signature Lifts
EF.Ask[C]() Effect[C, C] the full dependency struct
EF.Asks(f) func(C) A → Effect[C, A] a pure projection of the deps
EF.Of[C](a) / EF.Succeed[C](a) A → Effect[C, A] a pure value
EF.Fail[C, A](err) error → Effect[C, A] an error
EF.FromResult[C](r) Result[A] → Effect[C, A] a Result
EF.FromIO[C](io) IO[A] → Effect[C, A] a plain IO
EF.FromThunk[C](t) ReaderIOResult[A] → Effect[C, A] a dep-free ReaderIOResult
EF.FromReader(r) Reader[C, A] → Effect[C, A] same as Asks
EF.Eitherize1(f) func(C, context.Context, A) (T, error) → Kleisli[C, A, T] an idiomatic method taking deps and ctx
EF.FromIdiomatic(f) KleisliI[C, A, B] → Kleisli[C, A, B] a service method func(A) func(context.Context, C) (B, error)

Running: EF.Provide[A](deps) → ReaderIOResult[A], then EF.RunSync(thunk)(ctx) → (A, error). EF.Local(f) narrows an outer dep struct to an inner one for a subsystem.

Traversal — process slices monadically

import (
    A   "github.com/IBM/fp-go/v2/array"
    RIO "github.com/IBM/fp-go/v2/context/readerioresult"
    F   "github.com/IBM/fp-go/v2/function"
)

// Fetch all users, stop on first error
fetchAll := F.Pipe1(
    A.MakeBy(10, userID),
    RIO.TraverseArray(fetchUser),  // []ReaderIOResult[User] → ReaderIOResult[[]User]
)

When to Use Which Monad

Situation Use
Value that might be absent Option[A]
Operation that can fail with custom error type Either[E, A]
Operation that can fail with error Result[A]
Lazy IO, side effects IO[A]
IO that can fail IOResult[A]
IO + context (cancellation, deadlines) ReaderIOResult[A] from context/readerioresult
IO + context + typed dependencies (services, DI) Effect[C, A] — recommended for production services
High-performance services Idiomatic packages in idiomatic/

Escalation path: Option → Result → IOResult → ReaderIOResult → Effect. Start with the simplest monad that covers your needs. For real-world services with database clients, HTTP clients, or config — go straight to Effect; it provides compile-time dependency safety that ReaderIOResult with raw context.Context cannot.

Do-Notation: Accumulating State with Bind and ApS

When a pipeline needs to carry multiple intermediate results forward, Chain/Map becomes unwieldy because each step only threads one value. Do-notation solves this by accumulating results into a growing struct at each step.

Every monad that supports do-notation exports the same family. Examples below use context/readerioresult (RIO), but the identical API is available in result, option, ioresult, readerioresult, and others.

The Function Family

Function Kind What it does
Do(empty S) — Lift an empty struct into the monad; starting point
BindTo(setter) monadic Convert an existing M[T] into M[S]; alternative start
Bind(setter, f) monadic Add a result; f receives the current state and returns M[T]
ApS(setter, fa) applicative Add a result; fa is independent of the current state
Let(setter, f) pure Add a value computed by a pure function of the state
LetTo(setter, value) pure Add a constant value

Lens variants (BindL, ApSL, LetL, LetToL) accept a Lens[S, T] instead of a manual setter.

Bind — Sequential, Dependent Steps

Bind sequences two monadic computations. f receives the full accumulated state so it can read anything gathered so far. Errors short-circuit.

import (
    RIO "github.com/IBM/fp-go/v2/context/readerioresult"
    F   "github.com/IBM/fp-go/v2/function"
    L   "github.com/IBM/fp-go/v2/optics/lens"
    R   "github.com/IBM/fp-go/v2/result"
    "context"
)

type Pipeline struct {
    User   User
    Config Config
    Posts  []Post
}

var (
    userLens   = L.MakeLens(func(s Pipeline) User   { return s.User },   func(s Pipeline, u User)   Pipeline { s.User = u; return s })
    configLens = L.MakeLens(func(s Pipeline) Config { return s.Config }, func(s Pipeline, c Config) Pipeline { s.Config = c; return s })
    postsLens  = L.MakeLens(func(s Pipeline) []Post { return s.Posts },  func(s Pipeline, p []Post) Pipeline { s.Posts = p; return s })
)

assembled := F.Pipe3(
    RIO.Do(Pipeline{}),
    RIO.Bind(userLens.Set,   func(_ Pipeline) RIO.ReaderIOResult[User] { return fetchUser(42) }),
    RIO.Bind(configLens.Set, F.Flow2(userLens.Get, fetchConfigForUser)),
    RIO.Bind(postsLens.Set,  F.Flow2(userLens.Get, fetchPostsForUser)),
)

parsed, err := R.Unwrap(assembled(context.Background())())

The setter signature is func(T) func(S1) S2. lens.Set already has this shape. F.Flow2(lens.Get, f) composes the field getter with any Kleisli arrow point-free.

ApS — Independent, Applicative Steps

ApS uses applicative semantics: fa is evaluated without access to state. Use when steps have no dependency on each other.

// Using same lens pattern as Bind — but steps are independent
summary := F.Pipe2(
    RIO.Do(Summary{}),
    RIO.ApS(userLens.Set,    fetchUser(42)),     // no access to state
    RIO.ApS(weatherLens.Set, fetchWeather("NYC")), // no access to state
)

Key difference:

| | Bind(setter, f) | ApS(setter, fa) | |-|---|---| | Second argument | func(S1) M[T] — function of state | M[T] — fixed monadic value | | Can read prior state? | Yes | No | | Semantics | Monadic (sequential) | Applicative (independent) |

Let, LetTo, BindTo

  • Let(setter, f) — add a value from a pure function of state (no monad, cannot fail)
  • LetTo(setter, value) — add a constant
  • BindTo(project) — start from an existing M[T] instead of Do(empty)

Lifted Variants for Mixed Monads

Bind*K helpers lift simpler computations into the do-chain. Each takes the same setter plus a Kleisli arrow in that monad — not a raw Go (T, error) function:

Helper f
BindResultK / BindEitherK func(S1) Result[T]
BindIOResultK func(S1) IOResult[T]
BindIOK func(S1) IO[T]
BindReaderK func(S1) Reader[context.Context, T]
BindReaderIOK func(S1) ReaderIO[T]

For a plain Go func(S1) (T, error), compose with result.Eitherize1 first: RIO.BindResultK(lens.Set, result.Eitherize1(parse)).

Do-Notation Decision Guide

Does the new step need to read prior accumulated state?
    YES  →  Bind   (monadic, sequential; f receives current S)
    NO   →  ApS    (applicative, independent; fa is a fixed M[T])

Is the new value derived purely from state, with no monad?
    YES  →  Let    (pure function of S)

Is the new value a compile-time or runtime constant?
    YES  →  LetTo

Starting from an existing M[T] rather than an empty struct?
    YES  →  BindTo

Complete Example — result Monad with Lenses

import (
    R    "github.com/IBM/fp-go/v2/result"
    F    "github.com/IBM/fp-go/v2/function"
    L    "github.com/IBM/fp-go/v2/optics/lens"
    N    "github.com/IBM/fp-go/v2/number"
    "strconv"
)

type Parsed struct {
    Raw    string
    Number int
    Double int
}

var (
    rawLens    = L.MakeLens(
        func(s Parsed) string { return s.Raw },
        func(s Parsed, v string) Parsed { s.Raw = v; return s },
    )
    numberLens = L.MakeLens(
        func(s Parsed) int { return s.Number },
        func(s Parsed, v int) Parsed { s.Number = v; return s },
    )
    doubleLens = L.MakeLens(
        func(s Parsed) int { return s.Double },
        func(s Parsed, v int) Parsed { s.Double = v; return s },
    )
)

var atoi = R.Eitherize1(strconv.Atoi) // func(string) Result[int]

parse := func(input string) R.Result[Parsed] {
    return F.Pipe3(
        R.Do(Parsed{}),
        R.LetTo(rawLens.Set, input),
        R.Bind(numberLens.Set, F.Flow2(rawLens.Get, atoi)),
        R.Let(doubleLens.Set, F.Flow2(numberLens.Get, N.Mul(2))),
    )
}

parse("21")  // Ok(Parsed{Raw:"21", Number:21, Double:42})
parse("abc") // Error(strconv parse error)

Common Mistakes

Mistake Fix
import "github.com/IBM/fp-go/result" Use v2: "github.com/IBM/fp-go/v2/result"
option.Map(myOption, f) Data-last: option.Map(f)(myOption)
either.Map[A, B](f) E leads in the either package: either.Map[error](f), or just either.Map(f) when E is inferable from context. In option/result the order is the natural Map[A, B] and no annotation is needed.
Using ioeither with error Use ioresult instead; reserve ioeither for custom error types
readConfig := IOE.Eitherize1(os.ReadFile) then using result directly IOResult is lazy — call readConfig("path")() with trailing ()
value, err := pipeline(ctx)() Running a ReaderIOResult[A] gives one Result[A]. Unwrap it: value, err := result.Unwrap(pipeline(ctx)())
Writing inline setter lambdas for Do-notation Use L.MakeLens + lens.Set; the signature already matches
Using Bind when steps are independent Use ApS for independent steps — clearer intent, potentially concurrent
Using context/readerioresult with deps stuffed into context.Context Use effect.Effect[Deps, A] — typed deps are compile-time checked and testable
EF.Asks(func(d Deps) EF.ReaderIOResult[A] {...}) That yields Effect[Deps, ReaderIOResult[A]]. Effect[C, A] is func(C) ReaderIOResult[A] — return the closure directly. Asks is for pure func(C) A.
EF.Provide(deps)(eff) / EF.Map(f) on an Effect Provide[A, C] cannot infer A through its returned function, and Map[C, A, B] often cannot infer C: write EF.Provide[string](deps) and EF.Map[Deps](f).
result.Right[error](v) result.Right[A any](v A) — the param is the success type: result.Right(v) or result.Of(v). Only result.Left[A](err) needs the annotation.
Wrapping fp-go operations in anonymous functions Go point-free: option.Filter(S.IsNonEmpty) not option.Filter(func(s string) bool { return s != "" }), option.GetOrElse(LZ.Of("x")) not option.GetOrElse(func() string { return "x" })
Map with a function that returns Option / Result / another monad Produces nested M[M[A]]. Use Chain (or Flatten) for A → M[B]; reserve Map for plain A → B.
Closure passed to Map / Chain mutates a captured variable (slice append, counter ++) Keep it pure — derive and return new values. A mutating closure silently defeats fp-go's guarantees and breaks under Traverse / concurrency.
Mixing idiomatic/result and standard result (or option / ioresult) in one file Different types — struct wrapper vs (A, error) tuple — that do not interoperate. Pick one representation per file.

Requires Go 1.24+ for generic type alias support.