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 andDate.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 purebulkSendcore 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.