DEV Community

loading...

Unconditional FizzBuzz: a Functional approach

kallmanation profile image Nathan Kallman ・4 min read

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:

  1. Pure Functions (aka no side-effects, only input and output)
  2. Function composition
  3. Higher order functions (functions that take functions as parameters)
  4. 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?

  1. When true, evaluate the then code
  2. When false, evaluate the else code

Two behaviors, so let's make two functions:

const functionalTrue = (onTrue, onFalse) => onTrue;
const functionalFalse = (onTrue, onFalse) => onFalse;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Let's work through this right to left:

  1. If the left-hand side (lhs) is false, then there isn't a point checking the right-hand side (rhs); just return the onFalse.
  2. 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;
  }
}
Enter fullscreen mode Exit fullscreen mode

And we can do a similar thing for or:

const or = (lhs, rhs) => (onTrue, onFalse) => lhs(onTrue, rhs(onTrue, onFalse));
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

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];
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

Easy. Next check if it is divisible by five:

const divisible_by_five = isDivisible(n, 5);
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

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));
};
Enter fullscreen mode Exit fullscreen mode


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.

Discussion (0)

pic
Editor guide