Design patterns·topic 9 of 10
Functional ideas that went mainstream
Most of these arrived in ordinary object-oriented codebases without the theory attached, and they are now said in reviews by people who have never written a line of Haskell. This is what they mean when they are.
Read in order · tick what you already know
- 01
you called it twice with the same arguments, got the same answer, and nothing anywhere else changed.
Pure function
- 02
the function returned a number and also wrote a row, and the second half is not in the signature anywhere.
Side effect
- 03
you replaced the call with the value it returned and the program behaved identically, which is not true of most calls.
Referential transparency
- 04
all the decisions are in functions with no I/O, and a thin outer layer does the reading and writing around them.
Functional core, imperative shell
- 05
nothing that was handed out can be changed, so you stopped copying defensively and stopped worrying about who else holds it.
Immutability
- 06
the copy was free until somebody wrote to it, and only then did the data actually get duplicated.
Copy-on-write
- 07
you added an item and got a new list back, and the old one is still there and still valid.
Persistent data structure
- 08
the new version copied five nodes rather than ten thousand elements, because everything unchanged is the same objects.
Structural sharing
- 09
you put the function in a variable, passed it to something and returned it from somewhere else, like any other value.
First-class function
- 10
the function's argument is another function, and what it actually does depends entirely on what you passed.
Higher-order function
- 11
the loop with an accumulator and two conditions became three named steps, and the intermediate variable disappeared.
Map, filter, reduce
- 12
you built one function out of three others, and the intermediate values never got names.
Function composition
- 13
the two-argument function became a function of one argument that returns a function of one argument.
Currying
- 14
you fixed the first argument once and passed the resulting one-argument function around instead.
Partial application
- 15
you built a pipeline over a million rows, took the first ten, and only about ten rows were ever processed.
Lazy evaluation
- 16
the recursion went a million deep and the stack never grew, because the call was the very last thing the function did.
Tail call optimisation
- 17
the type says it is exactly one of four things, and the compiler will not let you forget the fourth.
Algebraic data type
- 18
one expression takes the value apart, names its pieces and picks a branch, all in the same line.
Pattern matching
- 19
you added a fifth case to the type and the build failed in nine places, which is exactly what you wanted.
Exhaustiveness checking
- 20
the type says the value might not be there, so you cannot use it without saying what happens when it is not.
Option type
- 21
the failure came back as a return value with the reason attached, and no exception was ever thrown.
Result type
- 22
each step either continues the happy path or diverts to the failure track, and no step checks whether the last one worked.
Railway-oriented programming
- 23
you had a value in a box, a function returning another box, and needed one box out rather than a box in a box.
Monad
- 24
each function takes the next one and returns a new one, so authentication wrapped logging wrapped the actual handler.
Middleware
- 25
one version says what the result should be and the other lists the steps, and only one of them can be optimised for you.
Declarative vs imperative