Boolean parameters are a plague, responsible for non-composable, monolithic functions that never quite do enough, and countless programming bugs:
- "Oops, meant to pass that as the 2nd boolean flag, not the 1st!"
- "Oops, accidentally inverted the meaning of that boolean in the implementation!"
- "Oops, the function isn't flexible enough for my use case, let's add a 6th boolean flag!"
- "Oops, got the meaning of that boolean flag wrong, time to dig into the source code!"
All boolean parameters should be refactored into functions that effect the change otherwise encoded in the parameter.
This forces you to design a more composable API (try it and you'll see!), as well as reduces potential for user-error.
Please fork this Gist and add more and better examples!
This is a stupid example based on a broken, imperative Java API, but may be more relatable and requires less setup than a more realistic example in a purely functional application.
The setKeepAlive
method accepts a boolean parameter indicating whether or not the socket should be kept alive.
Socket.setKeepAlive(on: Boolean)
socket.setKeepAlive(true)
Instead, we can change this method so you pass a timeout that can choose to keep alive or terminate the socket based on the value of the timeout (note: this is a change in semantics).
Socket.inactive(handler: Time => (Socket => Unit))
val KeepAlive = time => identity
val ShutDown = time => (socket => if (time > 2000) socket.close() else ())
Socket.inactive(ShutDown)
The jQuery.extend
function accepts a boolean flag indicating whether to do a shallow or a deep copy. Guess what 'true' and 'false' mean? Who knows and who cares, it's arbitrary and stupid.
jQuery.extend(true, foo, {bar: "baz"})
There are lots of ways to refactor this, here's one (I don't claim it's the best):
// jQuery.Extend :: (Json -> Json -> Json) -> Json -> Json -> Json
var Shallow = function(a, b) { return b; }
var Deep = function(a, b) { return jQuery.extend(Deep, a, b); }
jQuery.extend(Shallow, foo, {bar: "baz"})
jQuery.extend(Deep, foo, {bar: "baz"})
The function you pass to extend
is a semigroup that combines values of the same field name.
In both examples, the interface has become far more precise. Instead of having to guess at what 'true' and 'false' mean, and having to deal with the multitude of programming bugs that come from "programming by name", we pass logic where we used to pass single bits of information, logic that precisely and clearly defines what we want.
Beyond precision, which cuts down on programming bugs, the interfaces have also become more general-purpose. We can use them to do more things, which will further the reduce the necessity for "one-off" methods (or additional boolean flags) written for special cases.
A boolean is just the simplest nontrivial algebraic data type, with 2 inhabitants. Are you ready to say that the trinary data type
Color = Red | Yellow | Blue
is to be banned also? I agree that boolean blindness is a problem, but removal of all booleans suggests you are advocating OO, whether of the single dispatch variety or of the multiple dispatch variety (in the case of replacing many booleans at once)? Also, some API boundaries do not permit passing of functions, but require parsing (say, of raw text or of command line arguments) and therefore booleans will be necessary before conversion to an appropriate richly typed representation.