If you haven't read the original challenge, go ahead and give it a look, think about how you would solve it, then come back. This post will give spoilers and (one of many) answers.
To recap though, we want to make a classic FizzBuzz without using if
(or ternaries or anything like that).
Let's give this a first run using that fantastic buzz-word, Functional Programming. But first...
What is Functional Programming?
There's the highlights that most everyone talks about:
- Pure Functions (aka no side-effects, only input and output)
- Function composition
- Higher order functions (functions that take functions as parameters)
- Avoid shared state
But most of those things can be done (or mimicked) in a "non-functional" programming style (and are just downright good ideas IMHO).
So what really is the heart of functional?
You may or may not know that functional programming is rooted in the Lambda Calculus. Which is a model where functions are the basic building block of computation.
That's it. In functional programming, all we mean is that functions are what do the program. The looping, the conditionals, the control flow, the data: all functions.
How to use it to control logic
It's nice to say that functions are all that the program needs. But what does that mean. In a practical way; how do we write code that does that?
It all starts with defining the elements we need using the language of input => output
. And for changes in behavior we lean on changes in implementation instead of built-in statements like if
.
So what are the behaviors of if?
- When
true
, evaluate thethen
code - When
false
, evaluate theelse
code
Two behaviors, so let's make two functions:
const functionalTrue = (onTrue, onFalse) => onTrue;
const functionalFalse = (onTrue, onFalse) => onFalse;
A little simple, but promising. In our functional solution the booleans are the conditionals. To prove that out, let's see what some boolean operations look like; starting with and
:
const and = (lhs, rhs) => (onTrue, onFalse) => lhs(rhs(onTrue, onFalse), onFalse);
// lhs and rhs for left-hand side and right-hand side
Let's work through this right to left:
- If the left-hand side (lhs) is
false
, then there isn't a point checking the right-hand side (rhs); just return theonFalse
. - If the left-hand side (lhs) is
true
, then also check the right-hand side (rhs); simply passing the parameters unchanged.
In a non-functional approach, this is basically what we've done:
function and(lhs, rhs) {
if (lhs) {
if (rhs) {
return true;
} else {
return false;
}
} else {
return false;
}
}
And we can do a similar thing for or
:
const or = (lhs, rhs) => (onTrue, onFalse) => lhs(onTrue, rhs(onTrue, onFalse));
In fact for any truth table, just directly transcribe it into lhs(rhs(firstCase, secondCase), rhs(thirdCase, fourthCase))
(and remove any rhs
calls where the two parameters are the same). So for xor
(exclusive or), the truth table looks something like this:
lhs | rhs | => | xor |
---|---|---|---|
true | true | => | false |
true | false | => | true |
false | true | => | true |
false | false | => | false |
and the function looks like this:
const xor = (lhs, rhs) => (onTrue, onFalse) => lhs(rhs(onFalse, onTrue), rhs(onTrue, onFalse));
Getting our functional boolean out of a comparison
So we have booleans; but how do we get them from a divisibility check like 42 % 3 == 0
?
There's multiple tricks to potentially doing this (in fact I think some of the submitted solutions used better ones than I did), but the one I will use is to make an array long enough so it will always be longer than the range produced by a modulo, with the first element being my functional true
and the rest filled with false
, then simply selecting the element from that array that the modulo result gives.
const isDivisible = (dividend, divisor) => [functionalTrue, ...Array(divisor).fill(functionalFalse)][dividend % divisor];
So if the dividend
is a multiple of divisor
(dividend % divisor == 0
) then I will select the first element, which is true
(otherwise I'll select a false
element). Just what I need to check if something is a multiple of 3 or 5!
Making the Fizz Buzz and the Buzz Fizz
We have all the basic building blocks we need now. First, let's check if it's divisible by three:
const divisible_by_three = isDivisible(n, 3);
Easy. Next check if it is divisible by five:
const divisible_by_five = isDivisible(n, 5);
And lastly, let's extend that little truth table trick into our final output:
divisible_by_three | divisible_by_five | => | fizz_buzz |
---|---|---|---|
true | true | => | FizzBuzz |
true | false | => | Fizz |
false | true | => | Buzz |
false | false | => | n |
which can be directly transcribed into our function:
divisible_by_three(divisible_by_five("FizzBuzz", "Fizz"), divisible_by_five("Buzz", n));
Putting it all together we have our final FizzBuzz solution without using a single conditional!
const functionalTrue = (onTrue, onFalse) => onTrue;
const functionalFalse = (onTrue, onFalse) => onFalse;
const isDivisible = (dividend, divisor) => [functionalTrue, ...Array(divisor).fill(functionalFalse)][dividend % divisor];
const functionalFizzBuzz = (n) => {
const divisible_by_three = isDivisible(n, 3);
const divisible_by_five = isDivisible(n, 5);
return divisible_by_three(divisible_by_five("FizzBuzz", "Fizz"), divisible_by_five("Buzz", n));
};
Follow me for the next post in this mini-series: the Object-oriented approach! I'm also planning a final article comparing FP and OOP using these two solutions.
Top comments (1)
Same for c# - much shorter and fun hmemcpy.com/2020/03/your-csharp-is...