agentleFS
luckysluckys/agent-skills/skills/fp-best-practices/SKILL.md

fp-best-practices

Skill5 starsChanged 30 days agoMITView on GitHub

Use when designing pure functions, functional composition, immutable data, Option/Result types, exhaustive variants, or effect boundaries. Review clarity and actual failure cases before adding functional abstractions; preserve the language and error conventions of the project.

The file

Show its rules and text

We split this skill into the rules it gives an agent

39 rules. About 0 are common, 13 are distinctive, and 26 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈13

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 26

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Use this skill for everyday functional programming decisions that shape composability, predictability, and long-term maintainability.

    line 12
  2. Distinctive:

    Use it especially when the task benefits from:

    line 14
  3. Distinctive:

    Write functions for the next reader, not just for the runtime.

    line 24
  4. Distinctive:

    Keep side effects at the edges.

    line 25
  5. Distinctive:

    Prefer small, composable functions over clever all-in-one implementations.

    line 26
  6. Distinctive:

    Use types and names to reveal intent.

    line 27
  7. Distinctive:

    Check types and edge cases.

    line 45
  8. Distinctive:

    Use a pipeline when it clarifies data flow.

    line 67
  9. Very specific:

    Use Maybe/Option when a value may legitimately be absent.

    line 73
  10. Very specific:

    Use Either/Result when an operation may fail and the caller needs the error.

    line 74
  11. Very specific:

    Use Object.freeze, readonly, immutable records, or persistent data structures.

    line 80
  12. Distinctive:

    Prefer pure functions. Extract impure operations to the boundary.

    line 92
  13. Very specific:

    Use pipe (left-to-right) or compose (right-to-left) to express data flow.

    line 94
  14. Distinctive:

    Prefer immutable values; keep any necessary mutation private to its owner or explicit at an effect boundary.

    line 98
  15. Distinctive:

    Prefer transformations that reveal intent.

    line 99
  16. Distinctive:

    Keep functions small — one transformation, one level of abstraction.

    line 100
  17. Distinctive:

    Every function can be tested with just its inputs — no setup needed.

    line 105
  18. Very specific:

    Read references/typed-boundaries.md when choosing stronger types, validating external data, or weighing local mutation against a pipeline.

    line 123
  19. Very specific:

    Read references/core-principles.md for the fundamental FP principles: purity, immutability, referential transparency, and composition.

    line 125
  20. Very specific:

    Read references/naming-and-abstractions.md when naming or abstraction quality is the main issue.

    line 126
  21. Very specific:

    Read references/function-composition.md for pipe, compose, currying, partial application, and point-free style.

    line 127
  22. Very specific:

    Read references/algebraic-data-types.md for Maybe/Option, Either/Result, tagged unions, and making illegal states unrepresentable.

    line 128
  23. Very specific:

    Read references/managing-side-effects.md for IO boundaries, Reader/Writer/State patterns, and effect systems.

    line 129
  24. Very specific:

    Read references/higher-order-functions.md for map, filter, reduce, flatMap, transducers, and functor/monad patterns.

    line 130
  25. Very specific:

    Read references/language-examples.md for an index of language-specific example files and a quick reference table.

    line 131
  26. Very specific:

    Read references/javascript-examples.md for examples in JavaScript (native, Ramda, fp-ts).

    line 132
  27. Very specific:

    Read references/typescript-examples.md for examples in TypeScript (fp-ts, Effect-TS, branded types).

    line 133
  28. Very specific:

    Read references/python-examples.md for examples in Python (functools, itertools, returns, pattern matching 3.10+).

    line 134
  29. Very specific:

    Read references/go-examples.md for examples in Go (first-class functions, generics, functional options, (value, error)).

    line 135
  30. Very specific:

    Read references/rust-examples.md for examples in Rust (Iterator trait, Option/Result, enums, newtype, ?

    line 136
  31. Very specific:

    Read references/haskell-examples.md for examples in Haskell (pure FP reference, typeclasses, IO monad).

    line 137
  32. Very specific:

    Read references/elm-examples.md for examples in Elm (frontend FP, The Elm Architecture, decoders).

    line 138
  33. Very specific:

    Read references/clojure-examples.md for examples in Clojure (threading macros, persistent data structures).

    line 139
  34. Very specific:

    Read references/fsharp-examples.md for examples in F# (computation expressions, discriminated unions, pipelines).

    line 140
  35. Very specific:

    Read references/elixir-examples.md for examples in Elixir (pipe operator, pattern matching, with, OTP).

    line 141
  36. Very specific:

    Use oop-best-practices when the codebase mixes FP and OOP and object design is in question.

    line 145
  37. Very specific:

    Use refactoring-best-practices when introducing FP into an existing imperative codebase.

    line 146
  38. Very specific:

    Use design-patterns-best-practices when evaluating whether a pattern maps to a functional equivalent.

    line 147
  39. Very specific:

    Use tdd-best-practices for test-driving pure functions and effect boundaries.

    line 148

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

FP Best Practices

Use this skill for everyday functional programming decisions that shape composability, predictability, and long-term maintainability.

Use it especially when the task benefits from:

  • clearer separation between pure logic and side effects
  • smaller functions composed into pipelines
  • types that make illegal states unrepresentable
  • data transformations over mutations
  • referential transparency and easier testing

Working Style

  1. Write functions for the next reader, not just for the runtime.
  2. Keep side effects at the edges; keep the core pure.
  3. Prefer small, composable functions over clever all-in-one implementations.
  4. Use types and names to reveal intent.
  5. Let the data structure do the work when it can.

Review Workflow

  1. Identify the transformation.
    • What data goes in? What comes out?
    • What domain concept is this function modeling?
  2. Check purity.
    • Does this function have observable effects (I/O, changes to caller-owned or shared state, randomness)?
    • Can the side effects be pushed to the boundary?
  3. Check composition.
    • Is this function doing one thing?
    • Can it be expressed as a composition of smaller functions?
    • Is it reusable in other pipelines?
  4. Check types and edge cases.
    • Are null/undefined/error cases handled explicitly via ADTs?
    • Is the return type honest about what might fail?
  5. Apply the lightest useful improvement.
    • extract function
    • introduce pipe/compose
    • replace null with Maybe/Option
    • replace throws with Either/Result
    • push side effect to boundary
    • replace loop with map/filter/reduce

Additional Review Lenses

Purity and referential transparency

  • A pure function returns the same result for the same inputs, always.
  • Pure functions are trivially testable, composable, and parallelizable.
  • Impure operations (I/O, logging, random, time) belong at the edges of the system.

Composition over imperative sequencing

  • Use a pipeline when it clarifies data flow; keep named intermediate steps or a local loop when they are easier to understand.
  • Small functions combined with pipe or compose reveal the shape of the transformation.
  • Point-free style removes noise when the data flow is already clear from the pipeline.

Algebraic data types over null and exceptions

  • Use Maybe/Option when a value may legitimately be absent.
  • Use Either/Result when an operation may fail and the caller needs the error.
  • Model domain variants as tagged union types (sum types) rather than optional fields.

Immutability as the default

  • Preserve caller-owned data and shared state. Private accumulators can be observably pure when they do not escape.
  • Use Object.freeze, readonly, immutable records, or persistent data structures.
  • Mutation of caller-owned or shared state is an effect; private state that never escapes can preserve observable purity.

Higher-order functions as vocabulary

  • map transforms each element of a structure without leaving the structure.
  • filter selects elements by predicate.
  • reduce/fold collapses a structure into a single value.
  • flatMap/chain sequences computations that each return a wrapped value.

Day-to-Day Rules

  • Prefer pure functions. Extract impure operations to the boundary.
  • Name functions by the transformation they perform, not the mechanism.
  • Use pipe (left-to-right) or compose (right-to-left) to express data flow.
  • Model meaningful absence with Maybe/Option or the project's native equivalent; preserve established boundary contracts.
  • Replace throw with Either/Result when callers need to handle failures.
  • Curry functions when partial application makes call sites cleaner.
  • Prefer immutable values; keep any necessary mutation private to its owner or explicit at an effect boundary.
  • Prefer transformations that reveal intent; a local loop is valid when a pipeline obscures the algorithm or adds material copying cost.
  • Keep functions small — one transformation, one level of abstraction.
  • Make state that persists between calls explicit; a closure that changes such state affects later observations.

Good Signals

  • Every function can be tested with just its inputs — no setup needed.
  • Pipelines read like a description of the problem.
  • Illegal states cannot be constructed from the types.
  • Side effects are localized to clearly named edge functions.
  • Changing one transformation does not break unrelated pipelines.

Warning Signs

  • Functions that mutate their inputs or external state.
  • null or undefined passed as a meaningful sentinel value.
  • Exceptions used for normal control flow.
  • Long functions that mix data fetching, transformation, and output.
  • Mutable shared state threaded through function parameters.
  • for loops where map/filter/reduce would make intent clearer.
  • Deeply nested callbacks or .then() chains that mix logic with I/O.

References

  • Read references/typed-boundaries.md when choosing stronger types, validating external data, or weighing local mutation against a pipeline.
  • Read references/core-principles.md for the fundamental FP principles: purity, immutability, referential transparency, and composition.
  • Read references/naming-and-abstractions.md when naming or abstraction quality is the main issue.
  • Read references/function-composition.md for pipe, compose, currying, partial application, and point-free style.
  • Read references/algebraic-data-types.md for Maybe/Option, Either/Result, tagged unions, and making illegal states unrepresentable.
  • Read references/managing-side-effects.md for IO boundaries, Reader/Writer/State patterns, and effect systems.
  • Read references/higher-order-functions.md for map, filter, reduce, flatMap, transducers, and functor/monad patterns.
  • Read references/language-examples.md for an index of language-specific example files and a quick reference table.
  • Read references/javascript-examples.md for examples in JavaScript (native, Ramda, fp-ts).
  • Read references/typescript-examples.md for examples in TypeScript (fp-ts, Effect-TS, branded types).
  • Read references/python-examples.md for examples in Python (functools, itertools, returns, pattern matching 3.10+).
  • Read references/go-examples.md for examples in Go (first-class functions, generics, functional options, (value, error)).
  • Read references/rust-examples.md for examples in Rust (Iterator trait, Option/Result, enums, newtype, ? operator).
  • Read references/haskell-examples.md for examples in Haskell (pure FP reference, typeclasses, IO monad).
  • Read references/elm-examples.md for examples in Elm (frontend FP, The Elm Architecture, decoders).
  • Read references/clojure-examples.md for examples in Clojure (threading macros, persistent data structures).
  • Read references/fsharp-examples.md for examples in F# (computation expressions, discriminated unions, pipelines).
  • Read references/elixir-examples.md for examples in Elixir (pipe operator, pattern matching, with, OTP).

Related Skills

  • Use oop-best-practices when the codebase mixes FP and OOP and object design is in question.
  • Use refactoring-best-practices when introducing FP into an existing imperative codebase.
  • Use design-patterns-best-practices when evaluating whether a pattern maps to a functional equivalent.
  • Use tdd-best-practices for test-driving pure functions and effect boundaries.

Source Influences

This skill is synthesized from ideas emphasized in:

  • Mostly Adequate Guide to Functional Programming — Brian Lonsdorf (Professor Frisby)
  • Professor Frisby's Mostly Adequate Guide to Functional Programming
  • Haskell Programming from First Principles — Allen & Moronuki
  • Structure and Interpretation of Computer Programs — Abelson & Sussman
  • Functional Programming in Scala — Chiusano & Bjarnason
  • Domain Modeling Made Functional — Scott Wlaschin
  • Grokking Simplicity — Eric Normand
  • fp-ts, Effect-TS, and Ramda library documentation

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.