Simplify your code: Functional core, imperative shell

Command–Query Separation, CQRS, and Security

  • Several comments relate the article’s idea to Command–Query Separation (CQS): commands mutate state, queries return data but don’t have effects.
  • Debate centers on whether this separation encourages unsafe “commands without precondition checks.”
    • One side argues that if validation is not colocated with commands (for reuse/perf reasons), internal helper commands may be callable with unvalidated input, creating security issues.
    • Others counter that CQS never forbids validation inside commands; using it in a less-safe way is a misuse, not a flaw of the pattern.
  • CQRS (separate read/write models) is distinguished from CQS and from “functional core, imperative shell” (FCIS); these operate at different levels and can be combined.

Pipelines, Readability, and Handling Time

  • Long chains like email.bulkSend(generateExpiryEmails(...)) split opinion.
    • Some find them hard to debug and prefer stepwise variables; others prefer pipelines (Elixir, Clojure, JS, Python generators) for composability and reuse.
  • Time handling (Date.now()) is criticized: using global time makes tests brittle; injecting a clock abstraction is recommended. Others think mocking time is sufficient and Date.now() is fine in many cases.

Transactions, Side Effects, and Monads

  • There is skepticism that all “transactional” code can be neatly functional: open/close, acquire/release, long-lived or partial transactions feel inherently imperative.
  • Others point to Haskell’s IO/STM model as an example where a functional core describes effects and the runtime/IO monad is the imperative shell.

Generic Core vs Functional Core; What Is “Good Code”?

  • Another philosophy raised is “generic core, specific shell”: core modules are highly reusable and stable; outer layers encode volatile business specifics.
  • Some see this as orthogonal to FCIS; others think the two “core/shell” notions are being mixed.
  • Broader discussion notes there is no universally accepted definition of “good code”; DDD, OO, and FP advocates often disagree, so these slogans are seen as heuristics, not laws.

Database Access, Performance, and Realism of Examples

  • The example db.getUsers() then filtering in memory is widely criticized as unrealistic and inefficient; many argue filtering should be pushed into the DB/query.
  • Defenders note it’s a toy example constrained by a one-page format, and that lazy query APIs (ORMs, LINQ, Haskell laziness) can make such code compile into efficient queries.
  • Concern remains that naive application of FCIS/CQS-style APIs can hide serious performance pitfalls.

Dependency Injection of Functions, Testing, and Errors

  • A detailed example shows passing functions (e.g., userFn, senderFn) into a pure bulkSend core to isolate IO and make unit testing trivial—no mocks or spies needed.
  • This is likened to dependency inversion/template method, but with single-function interfaces instead of large object interfaces.
  • Result/Option/expected types, and pipelines that short-circuit on errors, are favored over exceptions for clearer control flow in a functional core.

Architectural Patterns and Language Ecosystem

  • FCIS is linked to hexagonal/onion architectures, “mechanism vs policy,” and “clean architecture”; all advocate pushing side effects and policy decisions to the edges.
  • Haskell, Elixir, Clojure, Python+libraries, and C# LINQ are cited as making FCIS-like structuring natural, though misuse (e.g., huge monad stacks) can still lead to messy cores.