
"The most insightful and intuitive guide to clean and simple software. I recommend this to all software developers." - Rob Pacheco, Vision Government Solutions Grokking Simplicity is a friendly, practical guide that will change the way you approach software design and development. Distributed across servers, difficult to test, and resistant to modification—modern software is complex. Grokking Simplicity is a friendly, practical guide that will change the way you approach software design and development. It introduces a unique approach to functional programming that explains why certain features of software are prone to complexity, and teaches you the functional techniques you can use to simplify these systems so that they’re easier to test and debug. Purchase of the print book includes a free eBook in PDF, Kindle, and ePub formats from Manning Publications. About the technology Developers rightly fear the unintended complexity that infects most code. This book shows you how to write software that keeps complexity close to its inherent minimum. As you write software you should distinguish between code that alters your system’s state, and code that does not. Once you learn to make that distinction, you can refactor much of your state-altering “actions” into stateless “calculations.” Your software will be simpler. About the book The book also teaches you to solve the complex timing bugs that inevitably creep into asynchronous and multithreaded code.
I can recommend Grokking Simplicity by Eric Normand. https://www.manning.com/books/grokking-simplicity
Functional programming is more of a spectrum; not an "all or nothing" concept. You can write FP-style programs in non-FP languages. (And you can use non-FP style in FP languages.) Understanding FP will help you write better non-FP code.
Simple example: I have switched from declaring an empty array [], then filling it via a loop to declaring the same array with values from a functional expression (like map). This has two benefits:
1. The array can be declared const, and its contents possibly even declared read-only.
2. TypeScript can automatically infer the type/shape of the array contents.
The other example usually cited is that FP will help you understand recursion better, even in non-FP languages.
FP is a paradigm that can be applied at many levels. You can apply it:
- at the code level: removing logical branching in code (https://youtu.be/GyYME8btMHE?t=1473)
- at the high-level design level: event sourcing (https://youtu.be/8JKjvY4etTY)
Although FP languages make it more convenient to write functional-style code, notice the examples above don't need "functional" programming languages. In fact, many "non-functional" languages have borrowed the best parts of functional languages (first-class (lambda) functions, map, reduce, etc)[1].
One of the most valuable, but misunderstood concepts of FP is immutability (functional purity). Grokking Simplicity[2] states it most elegantly: it's not about avoiding mutation because "mutation is bad." In fact, mutation is usually the desired result, but care must be taken because mutation depends on when and how many times it's called.
So good functional programming isn't about avoiding impure functions; instead it's about giving extra care to them. After all, the purpose of all software is to cause some type of mutation/effect (flip pixels on a screen, save bits to storage, send email, etc). Impure functions like these depend on the time they are called, so they are the most difficult to get right.
---
Here's a short list of my top introductions/explanations of FP:
- Grokking Simplicity: https://www.manning.com/books/grokking-simplicity
- Functional Core, Imperative Shell: https://hw.leftium.com/#/item/18043058
- ScottWlaschin: https://hw.leftium.com/#/item/21879368
- Greg Young on Event Sourcing: https://youtu.be/8JKjvY4etTY
- https://project-awesome.org/stoeffel/awesome-fp-js
[0]: https://youtu.be/nuML9SmdbJ4
- Grokking Simplicity[2] then shifted my thoughts on functional programming: it's not about avoiding mutation because mutation is bad. In fact mutation is usually the desired result, but care must be taken because mutation depends on when and how many times it's called.
Mutation is flipping bits in storage, setting pixels on a screen, sending emails: these effects are desired, but it is crucial to only have these effects happen when we want them to, in the order desired, and exactly the number of times we expect.
Compare to pure functions, which simply calculate a value: these can be called whenever, however many times we wish without any major negative consequences. They are much easier to reason about.
---
However, the two books that rewired my mental model the most were not technical. These books totally altered my overall mindset:
1. Conversations with God[3]. Best answers for biggest questions in life; answered better than any religion; actually resolves all the conflicts between different religions:
- What is the meaning of life?
- How did the universe begin?
- Why do bad things happen to good people?
- What happens after we die?
2. Direct response marketing. (I coined the term "precision marketing" to differentiate from the "brute force marketing" usually conjured when people hear the word "marketing.") Lots of different resources; Start from Zero[4] is a good one. Expert Secrets[5] is a well-written collection of direct response marketing's greatest hits.
[1]: https://hw.leftium.com/#/item/18043058
[2]: https://www.manning.com/books/grokking-simplicity
[3]: https://www.cwg.org/
In fact, many FP language features have been borrowed by non-FP languages to the point people don't consider them "FP" anymore[0]. (Like JS `.map()` and `.reduce()`. Even Excel macros now support first-class functions[1].)
Three features that would make FP thinking in JS easier:
1. immutable tuples and records[2]
2. pipe operator[3]
3. pattern matching[4]
Major epiphany: According to Grokking Simplicity[5], good functional programming isn't about avoiding impure functions; instead it's about giving extra care to them. Impure functions depend on the time they are called, so they are the most difficult to get right. In the end, the purpose of all software is to cause some type of mutation/effect (flip pixels on a screen, save bits to storage, send email, etc).
"Functional Core, Imperative Shell"[6] is probably the most useful FP concept. It is about pushing mutation/errors/impure functions to the outer edges of your code, so the inner code can focus on the "happy path." Complex business logic lives in the inner core; the outer shell is often simple/branchless so unit tests aren't even needed.
[0]: https://hw.leftium.com/#/item/21280429
[1]: https://hw.leftium.com/#/item/26900419
[2]: https://hw.leftium.com/#/item/23924933
[3]: https://hw.leftium.com/#/item/34454184
[4]: https://hw.leftium.com/#/item/16921652
I believe Mike Sperber is talking about Haskell when he says "advanced abstraction", but most regular line-of-business software can reap significant benefits with just simple FP principles: mostly immutable data, record types, sum types, and a vocabulary of small decomposed functions that can parse, transform, and combine this data, together building to larger wholes.
You do need a language with types and first-class functions, and the most common vehicle for that currently is TypeScript. Combine that with a book like Grokking Simplicity by Eric Normand, especially the fundamental idea of separating data, computation, and action, and you have a software system that is supple, simple, and amenable to continuous growth and refactoring over a long period of time.
Grokking Simplicity, IMO, is a severely under-discussed book when it comes to FP. Its only sticking point for me is its lack of static types, but otherwise it is the first and only book that I've seen that distils the functional way of thinking for the working programmer in an accessible way, without having to resort to the all-or-nothing proposition of completely pure FP.