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.
Instrucciones de origen · Vista previa de solo lectura
name
fp-go
description
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.
fp-go v2 — Functional Programming for Go
Critical Rules for Code Generation
Import path is v2: always github.com/IBM/fp-go/v2/..., never github.com/IBM/fp-go/... (that is v1).
Data-last: all operations return a function waiting for data. Write option.Map(f)(value), never option.Map(value, f).
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.
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.
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:
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.
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:
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 search_examples / get_example 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.
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
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.
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 / `<
>`
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 Btype Kleisli[A, B any] = func(A) M[B]
// An operator: transforms one monadic value into anothertype 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.
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] {
returnfunc(ctx context.Context)func() result.Result[User] {
returnfunc() 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 parameterC 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 structtype 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] {
returnfunc(deps Deps) EF.ReaderIOResult[User] {
// deps.DB is available here — compile-time checkedreturn queryUser(deps.DB, id)
}
}
enrichWithConfig := func(user User) EF.Effect[Deps, EnrichedUser] {
returnfunc(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]isfunc(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.