fp-go-pr-review
IBM/fp-go/skills/fp-go-pr-review/SKILL.md
Use this skill when reviewing pull requests for fp-go code (github.com/IBM/fp-go/v2). Trigger on mentions of PR review, code review, pull request validation, fp-go best practices validation, functional programming review, or when the user asks to review changes on a PR branch. This skill validates that changes follow fp-go conventions including data-last composition, point-free style, proper monad usage, lens patterns, and idiomatic functional patterns.
What's in it
- fp-go PR Review
- Overview
- When to Use This Skill
- Review Checklist
- 1. Import Path Validation
- 2. Data-Last Principle
- 3. Point-Free Style
- 4. Prefer Result over Either
- 5. IO Laziness
- 6. Monad Selection
- 7. Effect vs ReaderIOResult
- 8. Lifting Go Functions
- 9. Do-Notation with Lenses
- 10. Bind vs ApS
- 11. TraverseArray Usage
- 12. Logging Side Effects
- 13. Prefer Functions over Variables
- 14. Type Parameter Order
- 15. Lens Composition
- 16. Immutability / No Hidden Mutation
- 17. Context Access and Scoping
- Review Process
- Step 1: Obtain Git Diff
- Step 2: Analyze Changes
- Step 3: Submit Findings
- Common Issue Categories
- Severity Guidelines
- Example Review Comments
- Import Path Issue
- Point-Free Style
---
name: fp-go-pr-review
description: >-
Use this skill when reviewing pull requests for fp-go code
(github.com/IBM/fp-go/v2). Trigger on mentions of PR review, code review,
pull request validation, fp-go best practices validation, functional
programming review, or when the user asks to review changes on a PR branch.
This skill validates that changes follow fp-go conventions including
data-last composition, point-free style, proper monad usage, lens patterns,
and idiomatic functional patterns.
---
# fp-go PR Review
## Overview
This skill assists with reviewing pull requests that use the fp-go library (github.com/IBM/fp-go/v2). It validates that code changes follow fp-go best practices and functional programming conventions. Requires Go 1.24+ for generic type alias support.
## When to Use This Skill
- Reviewing pull requests with fp-go code
- Validating that changes follow fp-go best practices
- Checking for common fp-go anti-patterns
- Ensuring proper functional composition patterns
- Verifying correct monad usage and error handling
## Review Checklist
### 1. Import Path Validation
**Rule**: All imports MUST use `github.com/IBM/fp-go/v2/...`, never `github.com/IBM/fp-go/...` (v1).
**Check for**:
```go
// ❌ WRONG - v1 import
import "github.com/IBM/fp-go/option"
// ✅ CORRECT - v2 import
import O "github.com/IBM/fp-go/v2/option"
```
**Severity**: Critical — v1 and v2 are incompatible
Also check that import aliases follow the canonical table in the **fp-go** skill
(`R` = `result`, `RD` = `reader`, `P` = `predicate`, `PA` = `pair`, `EM` = `endomorphism`,
`IOR` = `ioresult`, `L` = `optics/lens`, `logging` unaliased, …). The same letter meaning
two packages across files is a Low finding.
### 2. Data-Last Principle
**Rule**: All fp-go operations use data-last. The data being transformed is always the last argument.
**Check for**:
```go
// ❌ WRONG - data-first
option.Map(myOption, transformFunc)
// ✅ CORRECT - data-last
option.Map(transformFunc)(myOption)
// ✅ CORRECT - in pipeline
F.Pipe2(
myOption,
O.Map(transformFunc),
O.GetOrElse(LZ.Of("default")),
)
```
**Severity**: High — breaks composition
### 3. Point-Free Style
**Rule**: Prefer composing named functions with `Flow` and `Pipe` over inline anonymous functions.
**Check for**:
```go
// ❌ AVOID - unnecessary lambda wrapping
pipeline := F.Flow2(
func(s string) O.Option[string] { return O.FromPredicate(S.IsNonEmpty)(s) },
func(o O.Option[string]) string { return O.GetOrElse(func() string { return "" })(o) },
)
// ✅ CORRECT - point-free composition
pipeline := F.Flow2(
O.FromPredicate(S.IsNonEmpty), // string -> Option[string]
O.GetOrElse(LZ.Of("")), // Option[string] -> string; LZ = v2/lazy
)
// ❌ AVOID - inline comparison
A.Filter(func(x int) bool { return x > 18 })
// ✅ CORRECT - use numeric combinator
A.Filter(N.MoreThan(18))
// ❌ AVOID - field-access lambda inside a pipeline step
RIO.Map(func(u User) string { return u.Name })
// ✅ CORRECT - named leaf accessor or lens getter
RIO.Map(nameLens.Get)
// ❌ AVOID - lambda that only threads its argument into a pipeline
RIO.Chain(func(u User) RIO.ReaderIOResult[[]Order] {
return F.Pipe1(fetchOrders(u.ID), RIO.LogEntryExit[[]Order]("fetchOrders"))
})
// ✅ CORRECT - compose the steps
RIO.Chain(F.Flow3(getUserID, fetchOrders, RIO.LogEntryExit[[]Order]("fetchOrders")))
// ❌ AVOID - hand-written Reader/IO closures around a Go function
func fetchUser(id int) RIO.ReaderIOResult[User] {
return func(ctx context.Context) func() R.Result[User] {
return func() R.Result[User] { return R.TryCatchError(repo.FindUser(ctx, id)) }
}
}
// ✅ CORRECT - lift it
fetchUser := RIO.Eitherize1(repo.FindUser) // repo.FindUser: func(context.Context, int) (User, error)
```
Lambdas are acceptable only at the **leaves**: a struct field accessor (prefer a
generated lens), a setter passed to `L.MakeLens`, a multi-field formatter, a
side-effecting sink at the program edge (e.g. an HTTP response writer), or a
blocking leaf that must `select` on `ctx.Done()`. Everything composed on top of
the leaves should be point-free.
**Severity**: Medium — impacts readability and maintainability
### 4. Prefer Result over Either
**Rule**: Use `Result[A]` (which is `Either[error, A]`) when the error type is Go's `error`. Reserve `Either` for custom error types.
**Check for**:
```go
// ❌ AVOID - Either with error
func fetchData() E.Either[error, Data] { ... }
// ✅ CORRECT - use Result
func fetchData() R.Result[Data] { ... }
// ✅ CORRECT - Either with custom error type
func validate() E.Either[ValidationError, Data] { ... }
```
**Severity**: Medium — Result is more idiomatic for Go errors
### 5. IO Laziness
**Rule**: IO values are lazy (`IO[A]` is `func() A`). They must be called with `()` to execute.
**Check for**:
```go
// ❌ WRONG - forgot to execute
result := readConfig("config.json") // returns IO[Config], not Config
// ✅ CORRECT - execute with ()
result := readConfig("config.json")()
// ❌ WRONG - in ReaderIOResult, forgot inner ()
res := pipeline(ctx) // returns func() Result[A], nothing has run yet
// ✅ CORRECT - execute both context and IO
res := pipeline(ctx)() // Result[A] — ONE value
// ❌ WRONG - Result[A] is a single value, not a (value, error) tuple
value, err := pipeline(ctx)()
// ✅ CORRECT - unwrap to idiomatic Go at the boundary
value, err := R.Unwrap(pipeline(ctx)())
```
**Severity**: Critical — code won't execute
### 6. Monad Selection
**Rule**: Use the simplest monad that covers your needs. Escalate only when necessary.
**Check for**:
```go
// ❌ AVOID - using ReaderIOResult for pure computation
// (also note: in context/readerioresult the context is baked in —
// it is RIO.Of[A] and RIO.Map[A, B], with no environment type parameter)
func processUsers(users []User) RIO.ReaderIOResult[string] {
return F.Pipe1(
RIO.Of(users),
RIO.Map(pureTransform),
)
}
// ✅ CORRECT - pure computation, no monad needed
func processUsers() func([]User) string {
return F.Flow2(
A.FilterMap(toAdultName()),
A.Intercalate(S.Monoid)(","),
)
}
```
**Severity**: Medium — unnecessary complexity
**Escalation path**: `Option` → `Result` → `IOResult` → `ReaderIOResult` → `Effect`
### 7. Effect vs ReaderIOResult
**Rule**: Use `Effect[C, A]` for services with typed dependencies. Use `ReaderIOResult` only when you truly only need `context.Context`.
**Check for**:
```go
// ❌ AVOID - stuffing deps into context.Context
func fetchUser(id int) RIO.ReaderIOResult[User] {
return func(ctx context.Context) func() R.Result[User] {
db := ctx.Value("db").(DBClient) // runtime type assertion
// ...
}
}
// ✅ CORRECT - typed dependencies with Effect
type Deps struct {
DB DBClient
Logger Logger
}
// Lift an idiomatic function that receives the deps and the context.
// queryUser is func(Deps, context.Context, int) (User, error); deps.DB is compile-time checked.
func fetchUser() EF.Kleisli[Deps, int, User] {
return EF.Eitherize1(queryUser)
}
```
`Effect[Deps, User]` IS `func(Deps) ReaderIOResult[User]`. `EF.Asks` is only for pure
projections `func(Deps) A`; feeding it a function that returns a `ReaderIOResult`
silently produces the nested `Effect[Deps, ReaderIOResult[User]]` — flag that as High.
Also flag `EF.Map(f)` and `EF.Provide(deps)(eff)` without annotations — `Map[C, A, B]` usually
cannot infer `C`, and `Provide[A, C]` cannot infer `A` through the function it returns. Write
`EF.Map[Deps](f)` and `EF.Provide[string](deps)`.
Also flag request-scoped data (request IDs, principal, deadlines) placed in `C`, one wide dependency type used by every function instead of narrow `XxxDeps` widened with `EF.Local`, and `Provide` / `RunSync` inside library code. See the `fp-go-effect` skill.
**Severity**: High — type safety and testability
### 8. Lifting Go Functions
**Rule**: Use `Eitherize1`..`EitherizeN` to lift Go functions returning `(T, error)` into Result.
**Check for**:
```go
// ❌ AVOID - manual error handling
func parseNumber(s string) R.Result[int] {
n, err := strconv.Atoi(s)
if err != nil {
return R.Left[int](err) // Left[A] — A is the success type
}
return R.Of(n) // NOT R.Right[error](n); Right[A any](v A)
}
// ✅ CORRECT - use Eitherize
var parseNumber = R.Eitherize1(strconv.Atoi)
// ✅ CORRECT - in pipeline
pipeline := F.Flow2(
R.Eitherize1(strconv.Atoi),
R.Map(N.Mul(2)),
)
```
**Severity**: Medium — reduces boilerplate
### 9. Do-Notation with Lenses
**Rule**: Use lenses with `Bind`/`ApS` instead of manual setter functions.
**Check for**:
```go
// ❌ AVOID - manual setter functions
func setUser(u User) func(State) State {
return func(s State) State { s.User = u; return s }
}
pipeline := F.Pipe1(
RIO.Do(State{}),
RIO.Bind(setUser, fetchUser),
)
// ✅ CORRECT - use lens
var userLens = L.MakeLens(
func(s State) User { return s.User },
func(s State, u User) State { s.User = u; return s },
)
pipeline := F.Pipe1(
RIO.Do(State{}),
RIO.Bind(userLens.Set, fetchUser),
)
// ✅ EVEN BETTER - use code generation
//go:generate go run github.com/IBM/fp-go/v2 lens --dir . --filename gen_lens.go
// fp-go:Lens
type State struct {
User User
}
// Then use generated lens
lenses := MakeStateLenses()
pipeline := F.Pipe1(
RIO.Do(State{}),
RIO.Bind(lenses.User.Set, fetchUser),
)
```
**Severity**: Medium — maintainability and consistency
### 10. Bind vs ApS
**Rule**: Use `Bind` when the step depends on accumulated state; use `ApS` when steps are independent.
**Check for**:
```go
// ❌ WRONG - using Bind when steps are independent
pipeline := F.Pipe2(
RIO.Do(Summary{}),
RIO.Bind(userLens.Set, func(_ Summary) RIO.ReaderIOResult[User] {
return fetchUser(42) // doesn't use state
}),
RIO.Bind(weatherLens.Set, func(_ Summary) RIO.ReaderIOResult[Weather] {
return fetchWeather("NYC") // doesn't use state
}),
)
// ✅ CORRECT - use ApS for independent steps
pipeline := F.Pipe2(
RIO.Do(Summary{}),
RIO.ApS(userLens.Set, fetchUser(42)),
RIO.ApS(weatherLens.Set, fetchWeather("NYC")),
)
// ✅ CORRECT - ApS for the independent first step, Bind for the dependent one
pipeline := F.Pipe2(
RIO.Do(Pipeline{}),
RIO.ApS(userLens.Set, fetchUser(42)),
RIO.Bind(configLens.Set, F.Flow2(userLens.Get, fetchConfigForUser)),
)
```
**Severity**: Medium — semantic clarity
### 11. TraverseArray Usage
**Rule**: Use `TraverseArray` to process slices monadically, not manual loops with error accumulation.
**Check for**:
```go
// ❌ AVOID - manual loop with error handling
func fetchAll(ids []int) RIO.ReaderIOResult[[]User] {
return func(ctx context.Context) func() R.Result[[]User] {
return func() R.Result[[]User] {
users := make([]User, 0, len(ids))
for _, id := range ids {
user, err := R.Unwrap(fetchUser(id)(ctx)())
if err != nil {
return R.Left[[]User](err)
}
users = append(users, user)
}
return R.Of(users)
}
}
}
// ✅ CORRECT - use TraverseArray, point-free (return the Kleisli, don't take ids)
func fetchAll() RIO.Kleisli[[]int, []User] {
return RIO.TraverseArray(fetchUser)
}
```
**Severity**: High — idiomatic functional pattern
### 12. Logging Side Effects
**Rule**: Log with the `Tap*` operators, which run the side effect and pass the original value (or error) through unchanged. Prefer structured `TapSLog`; use `TapIOK(IO.Logf…)` for printf-style logs and `LogEntryExit` for entry/exit logs. See the `fp-go-logging` skill.
**Check for**:
```go
// ❌ AVOID - breaking the pipeline for logging
pipeline := F.Pipe1(
fetchUser(42),
RIO.Chain(func(user User) RIO.ReaderIOResult[User] {
log.Printf("Fetched user: %v", user)
return RIO.Of(user)
}),
)
// ✅ CORRECT - structured logging with TapSLog (logs value or error)
pipeline := F.Pipe1(
fetchUser(42),
RIO.TapSLog[User]("User fetched"),
)
// ✅ CORRECT - printf-style logging with TapIOK
pipeline := F.Pipe1(
fetchUser(42),
RIO.TapIOK(IO.Logf[User]("Fetched user: %v")),
)
```
Also flag `ChainFirstIOK` used for logging (Low: works, but `TapIOK` states the intent) and `slog.Info` inside `Map` (Medium: a side effect in a pure function that also bypasses the context logger).
**Severity**: Low — code quality
### 13. Prefer Functions over Variables
**Rule**: Wrap pipeline results in functions, not package-level vars.
**Check for**:
```go
// ❌ WRONG - var is allocated even if never called
var processUser = F.Flow2(getName, strings.ToUpper)
// ✅ CORRECT - zero cost until called
func processUser() func(User) string {
return F.Flow2(getName, strings.ToUpper)
}
```
The rule targets composed pipelines (`Pipe`/`Flow` results). A `var` is fine for lenses and for a single pre-bound helper such as `var parseNumber = R.Eitherize1(strconv.Atoi)` or `var getHost = hostLens.Get` (same rule as the `fp-go-pipe-flow` skill).
**Severity**: Low — performance and dead code elimination
### 14. Type Parameter Order
**Rule**: Non-inferrable type parameters come first, so an explicit annotation only ever needs the leading prefix.
Which params are non-inferrable differs per package — check the signature rather than assuming:
```go
// option / result / ioresult: Map[A, B](f func(A) B) — BOTH inferable from f
O.Map(toLength) // ✅ preferred, no annotation at all
O.Map[string, int](toLength) // ✅ legal but redundant (note the order: A then B)
// Ap[B, A](fa M[A]) — B is not recoverable from fa, so it leads
O.Ap[int](fa) // ✅
// either / reader / readerio*: the error or environment type leads
E.Map[error](f) // ✅ either.Map[E, A, B]
RD.Map[context.Context](f) // ✅ reader.Map[R, A, B]
// effect: C leads and is often not inferable
EF.Map[Deps](f) // ✅
EF.Provide[string](deps) // ✅ Provide[A, C] cannot infer A through its result
```
Flag an annotation that is in the wrong order (it will not compile) or one that
restates what the compiler already infers.
**Severity**: Low — compilation errors or verbosity
### 15. Lens Composition
**Rule**: Use `Compose`/`ComposeRef` for nested struct access, not manual chaining.
**Check for**:
```go
// ❌ AVOID - manual nested access
func getStreetName(p Person) string {
if p.Address != nil && p.Address.Street != nil {
return p.Address.Street.Name
}
return ""
}
// ✅ CORRECT - compose lenses
streetNameInPerson := F.Pipe2(
personAddressLens,
LO.Compose[Person, *Street](defaultAddress)(addressStreetLens),
LO.ComposeOption[Person, string](defaultStreet)(streetNameLens),
)
name := streetNameInPerson.Get(person) // Option[string]
```
**Severity**: Medium — immutability and composability
### 16. Immutability / No Hidden Mutation
**Rule**: Functions passed to `Map`, `Chain`, `Filter`, etc. must be pure — they must not mutate variables captured from an outer scope, and lens setters must not mutate shared slice/map fields in place.
**Check for**:
```go
// ❌ WRONG - closure mutates a captured slice
var acc []string
A.Map(func(u User) User {
acc = append(acc, u.Name) // hidden side effect
return u
})
// ✅ CORRECT - derive a new value, no captured mutation
names := F.Pipe1(users, A.Map(getName))
// ❌ WRONG - lens setter mutates a shared slice in place
// append may reuse the original backing array (shallow struct copy)
func(u User, t []string) User { u.Tags = append(u.Tags, t...); return u }
// ✅ CORRECT - assign a freshly built value
func(u User, t []string) User { u.Tags = t; return u }
```
**Severity**: High — a mutating closure silently defeats fp-go's guarantees and breaks under `TraverseArray`/concurrency.
### 17. Context Access and Scoping
**Rule**: Read `context.Context` values with `AskValue`, and scope values, timeouts and deadlines with the `WithValue` / `WithTimeout` / `WithDeadline` operators (or `Local`). Do not type-assert `ctx.Value` or derive contexts by hand inside pipelines. The operators exist in `context/readerio`, `context/readerresult`, `context/readerioresult`, `context/statereaderioresult` and `idiomatic/context/readerresult`.
**Check for**:
```go
// ❌ WRONG - panics if the key is missing or has another type; plain string key
getUser := RIO.FromReader(func(ctx context.Context) string {
return ctx.Value("user").(string)
})
// ✅ CORRECT - typed key, Option result, caller decides what "missing" means
type ctxKey string
const userKey ctxKey = "user"
getUser := F.Pipe1(RIO.AskValue[string](userKey), RIO.Map(O.GetOrElse(LZ.Of("anonymous"))))
// ❌ WRONG - hand-derived context; cancel discarded -> leaked timer
RIO.Local[A](func(ctx context.Context) ContextCancel {
tctx, _ := context.WithTimeout(ctx, 5*time.Second)
return pair.MakePair(func() {}, tctx)
})
// ❌ WRONG - scoping done outside the pipeline, by hand
ctx, cancel := context.WithTimeout(context.WithValue(ctx, userKey, u), 5*time.Second)
defer cancel()
res := pipeline(ctx)()
// ✅ CORRECT - scoping as operators; cancel always released
res := F.Pipe2(
pipeline,
RIO.WithTimeout[A](5*time.Second),
RIO.WithValue[A](userKey, u),
)(ctx)()
// ❌ WRONG - Unpack + defer just to install a logger
cancel, lctx := pair.Unpack(logging.WithLogger(l)(ctx)); defer cancel()
// ✅ CORRECT - WithLogger already has Local's shape
F.Pipe1(pipeline, RIO.Local[A](logging.WithLogger(l)))
```
Also flag:
- string or other exported key types (`"user"`, `int`) — use an unexported `type ctxKey string`
- dependencies (DB, config, clients) stored in the context — see §7, use `Effect`
- outside pipelines, `context.WithValue(ctx, k, v)` where `CR.WithValue[V](k)(v)(ctx)` from `context/reader` would keep code consistent (Low)
**Severity**: High for panicking assertions and leaked cancel functions; Medium for hand-rolled scoping that has an operator equivalent.
## Review Process
### Step 1: Obtain Git Diff
Get the changes on the PR branch relative to main:
```bash
git diff main...HEAD
```
To list only changed file paths:
```bash
git diff --name-only main...HEAD
```
For a GitHub PR, fetch it first:
```bash
gh pr checkout <PR-number>
git diff main...HEAD
```
### Step 2: Analyze Changes
First, confirm the branch compiles: run `go build ./...` and `go vet ./...` on the
checked-out branch. Report any build or vet failure as a **Critical** finding —
there is no point reviewing composition style on code that does not compile, and
most fp-go-specific mistakes (wrong leading type parameter, data-first vs
data-last argument order, missing trailing `()`) surface here.
Then, for each modified file:
1. Check import paths (v2 requirement)
2. Validate data-last usage
3. Check for point-free style opportunities
4. Verify monad selection appropriateness
5. Check IO execution (trailing `()`)
6. Validate error handling patterns
7. Check lens usage in do-notation
8. Verify Bind vs ApS usage
9. Look for TraverseArray opportunities
10. Check logging patterns
### Step 3: Submit Findings
Post a review comment on the GitHub PR:
```bash
gh pr review <PR-number> --comment -b "$(cat <<'EOF'
## fp-go Review
**Overall**: Needs Changes
### Critical
- ❌ ...
### High
- ⚠️ ...
### Recommendations
1. ...
EOF
)"
```
For inline annotations on specific lines, use:
```bash
gh api repos/{owner}/{repo}/pulls/<PR-number>/comments \
-f body="Replace inline lambda with point-free: \`F.Flow2(O.FromPredicate(S.IsNonEmpty), O.GetOrElse(LZ.Of(\"\")))\`" \
-f commit_id="$(git rev-parse HEAD)" \
-f path="src/user/handler.go" \
-F line=42 \
-f side=RIGHT
```
Alternatively, use the `/code-review --comment` skill to post inline PR annotations automatically.
## Common Issue Categories
| Category | Type | Example |
|----------|------|---------|
| maintainability | dry-principle-violation | Inline lambdas instead of point-free |
| maintainability | naming-intent-review | Non-descriptive variable names |
| functionality | error-handling-review | Missing error propagation |
| functionality | context-handling | `ctx.Value(k).(T)` assertion or discarded `cancel` instead of `AskValue` / `WithTimeout` |
| performance | inefficient-algorithm | Manual loops instead of TraverseArray |
| style | style-consistency-check | Inconsistent import aliases |
| security | sensitive-data-logging | Logging sensitive information |
## Severity Guidelines
- **Critical**: Code won't compile or execute (wrong import path, missing `()`)
- **High**: Type safety issues, incorrect monad usage, breaks composition
- **Medium**: Readability, maintainability, non-idiomatic patterns
- **Low**: Style preferences, minor optimizations
## Example Review Comments
### Import Path Issue
> **Severity**: Critical
> **Issue**: Using v1 import path
>
> The import `github.com/IBM/fp-go/option` is the v1 path. All imports must use v2:
> `github.com/IBM/fp-go/v2/option`
>
> v1 and v2 are incompatible. This will cause compilation errors or runtime issues.
### Point-Free Style
> **Severity**: Medium
> **Issue**: Unnecessary lambda wrapping
>
> This inline lambda can be replaced with point-free composition:
>
> Current:
> ```go
> option.Filter(func(s string) bool { return s != "" })
> ```
>
> Suggested:
> ```go
> option.Filter(S.IsNonEmpty)
> ```
>
> Point-free style is more readable and idiomatic in fp-go.
### Monad Selection
> **Severity**: Medium
> **Issue**: Unnecessary monad for pure computation
>
> This function uses `ReaderIOResult` but performs only pure transformations without IO or context:
>
> ```go
> func processUsers(users []User) RIO.ReaderIOResult[string] {
> return F.Pipe1(
> RIO.Of(users),
> RIO.Map(pureTransform),
> )
> }
> ```
>
> Suggested:
> ```go
> func processUsers() func([]User) string {
> return F.Flow2(
> A.FilterMap(toAdultName()),
> A.Intercalate(S.Monoid)(","),
> )
> }
> ```
>
> Use the simplest abstraction that covers your needs.
## Integration with Other Skills
This skill can reference and include:
- `fp-go` — Core fp-go patterns and best practices
- `fp-go-pipe-flow` — Pipe/Flow composition patterns
- `fp-go-http` — HTTP request patterns
- `fp-go-logging` — Logging patterns
- `fp-go-lens` — Lens and optics patterns
- `fp-go-context` — context.Context handling: reading values, scoping, timeouts, cancellation (see §17)
- `fp-go-pattern-matching` — replacing switch / if-else chains with point-free case lists
- `fp-go-effect` — `Effect[C, A]` with typed dependencies in `C`: capability interfaces, `Local`, testing with fakes (see §7)
## Automated Checks
When reviewing, automatically check for:
1. ✅ All imports use `v2` path
2. ✅ No data-first function calls
3. ✅ IO values are executed with `()`
4. ✅ `Result` used instead of `Either[error, A]`
5. ✅ Point-free style: no lambdas above the leaves; `Eitherize` instead of hand-written closures; `ApS` instead of state-ignoring `Bind`
6. ✅ Appropriate monad selection
7. ✅ Lenses used in do-notation
8. ✅ `Bind` vs `ApS` used correctly
9. ✅ `TraverseArray` for slice processing
10. ✅ Logging via `TapSLog` / `TapIOK` / `LogEntryExit` (see the `fp-go-logging` skill)
11. ✅ No hidden mutation in `Map`/`Chain` closures or lens setters
12. ✅ Context values read with `AskValue`; values/timeouts scoped with `WithValue`/`WithTimeout`/`WithDeadline`/`Local` (no `ctx.Value(k).(T)`, no discarded cancel funcs)
13. ✅ Branch compiles (`go build ./...`) and passes `go vet ./...`
14. ✅ Import aliases follow the canonical table (see the `fp-go` skill)
## Output Format
Provide a summary with:
1. **Overall Assessment**: Pass/Needs Changes/Blocked
2. **Critical Issues**: Count and list
3. **High Priority Issues**: Count and list
4. **Medium Priority Issues**: Count and list
5. **Low Priority Issues**: Count and list
6. **Positive Observations**: What was done well
7. **Recommendations**: Suggested improvements
## Example Summary
```markdown
## PR Review Summary
**Overall Assessment**: Needs Changes
### Critical Issues (2)
- ❌ Using v1 import path in `user/handler.go:5`
- ❌ Missing IO execution in `config/loader.go:42`
### High Priority Issues (1)
- ⚠️ `ctx.Value("user").(string)` type assertion instead of `AskValue` in `api/auth.go:31`
### Medium Priority Issues (4)
- 💡 Manual error handling instead of Eitherize in `api/client.go:78`
- 💡 Inline lambda instead of point-free in `user/service.go:23`
- 💡 Using ReaderIOResult for pure computation in `utils/format.go:15`
- 💡 Manual setter instead of lens in `state/pipeline.go:56`
### Low Priority Issues (1)
- 📝 Inconsistent import alias in `handler/http.go:8`
### Positive Observations
- ✅ Excellent use of TraverseArray for parallel requests
- ✅ Proper Effect usage with typed dependencies
- ✅ Good lens composition for nested struct access
### Recommendations
1. Update all imports to v2 path
2. Add trailing `()` to execute IO values
3. Consider using `R.Eitherize1` for Go function lifting
4. Refactor pure computations to use Flow instead of ReaderIOResult
```
## References
- [fp-go v2 Documentation](https://pkg.go.dev/github.com/IBM/fp-go/v2)
- [fp-go GitHub Repository](https://github.com/IBM/fp-go)
- [Functional Programming in Go](https://github.com/IBM/fp-go/blob/main/README.md)
More agent context in IBM/fp-go
11 other files this repository gives its agents.
AGENTS.md
llms.txt
Skill
- fp-go-contextskills/fp-go-context/SKILL.md
- fp-go-effectskills/fp-go-effect/SKILL.md
- fp-go-httpskills/fp-go-http/SKILL.md
- fp-go-lensskills/fp-go-lens/SKILL.md
- fp-go-loggingskills/fp-go-logging/SKILL.md
- fp-go-mcpskills/fp-go-mcp/SKILL.md
- fp-go-pattern-matchingskills/fp-go-pattern-matching/SKILL.md
- fp-go-pipe-flowskills/fp-go-pipe-flow/SKILL.md
- fp-goskills/fp-go/SKILL.md
Discussion
Did it work?
Say what you used it for and what you changed. People and their agents can both post here.
No reports yet. Be the first to say whether it worked.
Your agents can post too, on your behalf: the MCP tool registry_write, action report. How to connect one.

