---
name: fp-go-pipe-flow
description: >-
  Use this skill when composing fp-go v2 functions with Pipe and Flow:
  building point-free pipelines, choosing Pipe vs Flow, returning pipelines
  from functions instead of package-level vars, Predicate and Endomorphism
  helpers, the generic reader monad (reader.Reader[R, A] with Ask, Asks, Map,
  Chain), do-notation (Do, Bind, ApS, Let, LetTo) and unit tests for
  pipelines. Trigger on mentions of Pipe, Flow, PipeN/FlowN, point-free style,
  kleisli, reader monad, do-notation, Bind, ApS, or refactoring nested calls
  or imperative Go into a pipeline. For building lenses use fp-go-lens; for
  context.Context use fp-go-context.
---

# fp-go Pipe and Flow Patterns

All imports **must** come from `github.com/IBM/fp-go/v2`, never from
`github.com/IBM/fp-go` (the v1 path).

---

## Before You Generate

fp-go is low-frequency in training data, so signatures are easy to misremember.
For any combinator not shown below, look it up via the fp-go MCP server's
`search_examples` / `get_example` tools (see the **fp-go-mcp** skill) instead of
guessing. After writing code, run `go build ./...` and `go vet ./...` and fix any
type-parameter or argument-order errors before presenting it.

---

## Core Concepts

### Pipe — Data-First Composition

`Pipe` takes an initial value and threads it through a sequence of functions.
Use it when you already have a value to start from.

```go
import F "github.com/IBM/fp-go/v2/function"

// PipeN threads a value through N functions
result := F.Pipe3(initialValue, step1, step2, step3)
```

The number suffix matches the number of transformation steps. `Pipe1`–`Pipe20` and `Flow1`–`Flow20` are generated; there is nothing above 20.

### Flow — Function-First Composition

`Flow` composes N functions into a single function that awaits its input.
Use it to build reusable pipeline functions, especially as arguments to `Map`,
`Chain`, or `TraverseArray`.

```go
// FlowN returns func(T0) TN
pipeline := F.Flow3(step1, step2, step3)
result := pipeline(initialValue)
```

**Rule of thumb: prefer `Pipe` when you have the starting value; use `Flow`
when you are building a reusable function.**

---

## Prefer Functions over Variables

Go does not eliminate dead variables, but unused functions are zero-cost.
Always wrap a `Pipe`/`Flow` result in a named function rather than storing it
in a package-level `var`.

```go
// WRONG — var is allocated even if never called
var processUser = F.Flow2(getName, strings.ToUpper)

// CORRECT — zero cost until called; also more composable
func processUser() func(User) string {
    return F.Flow2(getName, strings.ToUpper)
}
```

Use `var` only for lenses and pre-bound combinator helpers (like `lens.Get`
assigned to a named getter), not for full pipeline results.

---

## Point-Free Style

Avoid explicit argument names wherever a named combinator or `Flow` can express
the same thing.

```go
// WRONG — explicit argument
func isAdult(u User) bool { return getAge(u) > 18 }

// CORRECT — point-free, returns typed Predicate
func isAdult() P.Predicate[User] {
    return F.Flow2(getAge, N.MoreThan(18))
}
```

### Type Aliases to Use

| Type | Package | Meaning |
|------|---------|---------|
| `P.Predicate[A]` | `github.com/IBM/fp-go/v2/predicate` | `func(A) bool` |
| `EM.Endomorphism[A]` | `github.com/IBM/fp-go/v2/endomorphism` | `func(A) A` |

Use these as return types for functions that act as predicates or
self-transformations — they communicate intent and enable direct use in
combinators like `A.Filter`, `A.Map`, `F.Ternary`.

```go
import (
    F "github.com/IBM/fp-go/v2/function"
    N "github.com/IBM/fp-go/v2/number"
    P "github.com/IBM/fp-go/v2/predicate"
    EM "github.com/IBM/fp-go/v2/endomorphism"
    A "github.com/IBM/fp-go/v2/array"
)

// Predicate — point-free using N.MoreThan
func isAdult() P.Predicate[User] {
    return F.Flow2(getAge, N.MoreThan(18))
}

// Endomorphism — self-transformation
func doubleAll() EM.Endomorphism[[]int] {
    return A.Map[int, int](N.Mul(2))
}
```

### Numeric Combinators

Prefer `N.MoreThan`, `N.LessThan`, `N.Mul`, `N.Add` etc. over inline
comparisons or arithmetic in lambdas:

```go
N.MoreThan(18)   // func(int) bool   — x > 18
N.LessThan(100)  // func(int) bool   — x < 100
N.Mul(2)         // func(int) int    — x * 2
N.Add(1)         // func(int) int    — x + 1
```

---

## Pure Pipelines vs the Reader Monad

**Only use the reader monad when the computation genuinely needs an environment
(context, config, DB, logger, etc.).** For pure transformations that don't
need external input, use `Flow` or `Pipe` directly — no reader wrapping needed.

```go
// WRONG — forces reader monad on a pure computation
func adultNames(users []User) RD.Reader[Env, string] {
    return F.Pipe1(
        RD.Of[Env](users),
        RD.Map[Env](pureTransform),
    )
}

// CORRECT — pure; no environment needed
func adultNames() func([]User) string {
    return F.Flow2(
        A.FilterMap(toAdultName()),
        A.Intercalate(S.Monoid)(","),
    )
}
```

### Per-Element Filter+Map: Use `A.FilterMap`

When filtering and then extracting a field, combine both into a single pass
with `A.FilterMap` and `O.FromPredicate`:

```go
import (
    F "github.com/IBM/fp-go/v2/function"
    A "github.com/IBM/fp-go/v2/array"
    O "github.com/IBM/fp-go/v2/option"
    N "github.com/IBM/fp-go/v2/number"
    P "github.com/IBM/fp-go/v2/predicate"
    S "github.com/IBM/fp-go/v2/string"
)

// isAdult — point-free predicate
func isAdult() P.Predicate[User] {
    return F.Flow2(getAge, N.MoreThan(18))
}

// toAdultName — User -> Option[string]: Some(name) if adult, None otherwise
func toAdultName() func(User) O.Option[string] {
    return F.Flow2(
        O.FromPredicate(isAdult()),  // User -> Option[User]
        O.Map(getName),              // Option[User] -> Option[string]
    )
}

// adultNames — pure pipeline, no reader monad needed
func adultNames() func([]User) string {
    return F.Flow2(
        A.FilterMap(toAdultName()),       // []User -> []string
        A.Intercalate(S.Monoid)(","),     // []string -> string
    )
}
```

---

## Reader Monad

The reader monad `Reader[R, A]` is `func(R) A` — a computation that reads
from an environment `R` and produces `A`. Only reach for it when the
computation needs to thread an environment (a config struct, a repository,
…). For `context.Context` plus IO and errors use `context/readerioresult`
(`RIO`) instead — see the **fp-go-context** skill.

```go
import (
    F  "github.com/IBM/fp-go/v2/function"
    RD "github.com/IBM/fp-go/v2/reader"
)

type Env struct {
    Users map[string]User
}

// Leaf accessor (or a generated lens' .Get)
func getUsers(e Env) map[string]User { return e.Users }

// lookupUser is curried: func(id string) func(map[string]User) User

// Kleisli arrow: string -> Reader[Env, User], built from a pure projection
func fetchUser(id string) RD.Reader[Env, User] {
    return RD.Asks(F.Flow2(getUsers, lookupUser(id)))
}
```

### When to Use `reader.Map` vs Full `Pipe` with Reader Operations

- **`reader.Map`** inside `Flow` — when the step is pure and the environment
  does not need to appear explicitly. This is the "abbreviation" pattern.
- **`Pipe` with `reader.Chain`, `reader.Bind`, `reader.ApS`** — when the
  sequence needs the environment (e.g. calls another kleisli arrow) or when
  do-notation makes the data flow clearer.

```go
// reader.Map inside Flow — no env name, clean point-free.
// NOTE: RD.Map returns an Operator over Reader values, so the PRECEDING step in the
// Flow must already produce a Reader. A plain func([]User) []string composed with
// RD.Map does not type-check.
func renderUsers() func(string) RD.Reader[Env, string] {
    return F.Flow2(
        fetchTeam,                   // string -> Reader[Env, []User]
        RD.Map[Env](F.Flow2(         // Reader[Env, []User] -> Reader[Env, string]
            A.Map(getName),
            A.Intercalate(S.Monoid)(","),
        )),
    )
}

// Pipe with reader monad — env access required
func enrichedUser(id string) RD.Reader[Env, EnrichedUser] {
    return F.Pipe3(
        fetchUser(id),
        RD.Chain(fetchProfile),
        RD.Chain(fetchPermissions),
        RD.Map[Env](combineToEnriched),
    )
}
```

---

## Do-Notation: `Do` / `Bind` / `ApS` / `Let`

Do-notation is the idiomatic way to assemble multiple reader (or IO/result)
computations into a named-field record. Always use it inside a `Pipe`.

```go
import (
    F  "github.com/IBM/fp-go/v2/function"
    L  "github.com/IBM/fp-go/v2/optics/lens"
    RD "github.com/IBM/fp-go/v2/reader"
)

// Lenses — generated (`// fp-go:Lens`) or built once with L.MakeLens.
// lens.Set already has the setter shape func(T) func(S) S — no hand-written setters.
var (
    userIDLens = L.MakeLens(
        func(s RequestState) string { return s.UserID },
        func(s RequestState, v string) RequestState { s.UserID = v; return s },
    )
    profileLens = L.MakeLens(
        func(s RequestState) Profile { return s.Profile },
        func(s RequestState, v Profile) RequestState { s.Profile = v; return s },
    )
    permsLens = L.MakeLens(
        func(s RequestState) Perms { return s.Perms },
        func(s RequestState, v Perms) RequestState { s.Perms = v; return s },
    )
)

// Kleisli arrows — named functions, never inline:
//   fetchProfile: func(userID string) Reader[Env, Profile]
//   fetchPerms:   func(p Profile)     Reader[Env, Perms]

// Pipeline — returned as a function, not a var
func buildRequestState(userID string) RD.Reader[Env, RequestState] {
    return F.Pipe3(
        RD.Do[Env](RequestState{}),
        RD.LetTo[Env](userIDLens.Set, userID),
        RD.Bind(profileLens.Set, F.Flow2(userIDLens.Get, fetchProfile)),
        RD.Bind(permsLens.Set, F.Flow2(profileLens.Get, fetchPerms)),
    )
}
```

`F.Flow2(lens.Get, kleisli)` is the point-free way to feed one field of the
accumulated state into the next step.

### `Bind` vs `ApS` vs `Let`

| Combinator | When to use |
|-----------|-------------|
| `Bind(setter, kleisli)` | Result depends on accumulated state (sequential) |
| `ApS(setter, reader)` | Result is independent of other fields |
| `Let(setter, pureFunc)` | Pure transformation of accumulated state, no reader needed |
| `LetTo(setter, value)` | Attach a constant value to state |

Use `ApS` when values can be computed independently; `Bind` when a later step
depends on an earlier one. Mixing them in the same pipeline is normal. A `Bind`
whose Kleisli ignores the state (`func(_ S) M[T] { return m }`) is always an
`ApS(setter, m)`.

---

## Lenses for Struct Field Access

Never access struct fields with inline functions inside a `Pipe`. Create a
lens (preferably generated with `// fp-go:Lens`, see the **fp-go-lens** skill)
or a named leaf accessor so the pipeline stays point-free.

```go
import (
    L "github.com/IBM/fp-go/v2/optics/lens"
)

var hostLens = L.MakeLens(
    func(c Config) string { return c.Host },
    func(c Config, v string) Config { c.Host = v; return c },
)

// Assign lens.Get to a named var — then pass it anywhere point-free
var getHost = hostLens.Get   // func(Config) string
var getPort = portLens.Get   // func(Config) int
```

Use `RD.ApSL(lens, reader)` / `RD.BindL(lens, kleisli)` as do-notation variants
that take a lens directly instead of a setter function.

---

## Unit Tests

Generate a `_test.go` for every non-trivial pipeline or flow.

```go
func TestAdultNames(t *testing.T) {
    users := []User{{Name: "Alice", Age: 25}, {Name: "Bob", Age: 16}}
    assert.Equal(t, "Alice", adultNames()(users))
}

func TestBuildRequestState(t *testing.T) {
    env := Env{Users: map[string]User{"user-42": {ID: "user-42"}}}
    state := buildRequestState("user-42")(env)
    assert.Equal(t, "user-42", state.UserID)
}
```

### Testing Guidelines

- For pure `Flow`/`Pipe` functions: call the returned function with a concrete
  value and assert with `assert.Equal`.
- For reader pipelines: call the reader with a concrete environment struct.
- For `IOResult`/`ReaderIOResult`: call the innermost IO thunk and compare
  with `R.Of(expected)`; run a `ReaderIOResult` with `t.Context()`.
- Prefer table-driven tests for pipelines with multiple input/output pairs.
- Do not mock the environment — pass a real (but lightweight) struct.

---

## Common Import Aliases

These follow the canonical alias table in the **fp-go** skill.

```go
import (
    F   "github.com/IBM/fp-go/v2/function"
    A   "github.com/IBM/fp-go/v2/array"
    O   "github.com/IBM/fp-go/v2/option"
    E   "github.com/IBM/fp-go/v2/either"
    R   "github.com/IBM/fp-go/v2/result"
    IOR "github.com/IBM/fp-go/v2/ioresult"
    RD  "github.com/IBM/fp-go/v2/reader"
    RIO "github.com/IBM/fp-go/v2/context/readerioresult"
    L   "github.com/IBM/fp-go/v2/optics/lens"
    N   "github.com/IBM/fp-go/v2/number"
    S   "github.com/IBM/fp-go/v2/string"
    P   "github.com/IBM/fp-go/v2/predicate"
    EM  "github.com/IBM/fp-go/v2/endomorphism"
)
```

---

## Quick Reference

| Goal | Pattern |
|------|---------|
| Thread a value through N steps | `F.PipeN(value, f1, f2, …)` |
| Build a reusable function | `F.FlowN(f1, f2, …)` |
| Point-free numeric predicate | `F.Flow2(getField, N.MoreThan(n))` returning `P.Predicate[T]` |
| Filter+map in one pass | `A.FilterMap(F.Flow2(O.FromPredicate(pred), O.Map(f)))` |
| Lift a pure function into Reader | `RD.Map[Env](pureFunc)` |
| Chain kleisli arrows | `RD.Chain(kleisliFunc)` |
| Start do-notation block | `RD.Do[Env](emptyStruct)` |
| Add dependent field | `RD.Bind(lens.Set, F.Flow2(otherLens.Get, kleisliFunc))` |
| Add independent field | `RD.ApS(lens.Set, readerValue)` |
| Add pure derived field | `RD.Let[Env](lens.Set, F.Flow2(otherLens.Get, pureFunc))` |
| Lens getter in pipeline | `var getX = xLens.Get` |
| Do-notation with lens | `RD.ApSL(lens, readerValue)` |
| Access full environment | `RD.Ask[Env]()` |
| Access field of environment | `RD.Asks(getX)` |
| Read a `context.Context` value | `RIO.AskValue[V](key)` → `Option[V]` (not `ctx.Value(key).(V)`) |
| Scope a value / timeout to a step | `RIO.WithValue[A](key, v)`, `RIO.WithTimeout[A](d)` as the last `Pipe` step |
