Six Ways to Functional FizzBuzz with Vavr

Sometime last year I stumbled over the excellent post of twenty ways to do FizzBuzz in JavaScript. I asked myself, how some functional solutions in Java 8 and Vavr (formerly called Javaslang) could look. In this article I present 5 different solutions using functional data structures, higher order functions and property checking. All these solutions adhere to the test harness I introduced in my last article.

Before We Get Started

My motivation behind this article was to experiment with different functional approaches to get used to a more functional thinking. The presented solutions not necessarily line up in a linear procession from bad to good. They are just different ways to skin the same cat and have different trade-offs, which is exactly what I want to explore. If you know another solution or want to discuss a presented one, please leave a comment.

Before we start with the actual functional solutions we will look at an imperative one and discuss its responsibilities. Further, we do some functional reasoning and introduce the stream data structure which we will use extensively. Last, the properties a FizzBuzz function should fulfill are discussed.

Imperative Solution and Functional Reasoning

A simple imperative solution exactly states how to count from 1 to 100, how to compute the modulo, and how to decide what is printed when. It is like following a recipe.

public void fizzBuzzFrom1to100(){
    for(int i = 1; i <= 100; i++){
        if (i % 3 == 0 && i % 5 == 0) {
            System.out.println("FizzBuzz");
        } else if (i % 3 == 0) {
            System.out.println("Fizz");
        } else if (i % 5 == 0) {
            System.out.println("Buzz");
        } else {
            System.out.println(i);
        }
    }
}

From an outside perspective, this method does not exhibit any behavior other than printing to the console. It is quite tedious to test because in order to verify the computed results we need to mock System.out and count the calls. But in order to do the mocking we need to know the internals.

When we already risk a look inside the method we see that it is responsible for computing and displaying the result. We can factor the logic of computing into an own function and use a secondary method on an outer layer to print the result.

public List<String> fizzBuzz(int amount) {
    List<String> ret = new ArrayList<>();
    for (int i = 1; i <= amount; i++) {
        if (i % 3 == 0 && i % 5 == 0) {
            ret.add("FizzBuzz");
        } else if (i % 3 == 0) {
            ret.add("Fizz");
        } else if (i % 5 == 0) {
            ret.add("Buzz");
        } else {
            ret.add(String.valueOf(i));
        }
    }
    return ret;
}

public void printList(List<String> list){
    for(String element: list){
        System.out.println(element);
    }
}

printList(fizzBuzz(100));

Testing the fizzBuzz function becomes easy because everything the function does is represented by the returned list. We can check any element in the list for its value.

Given a particular input, the fizzBuzz function will always produce the same output. We could replace function application fizzBuzz(100) with the computed list without changing the meaning of our program. That is, the fizzBuzz function is pure.

The reasoning is, that a method with side effects can always be factored into a pure function and two side-effecting methods supplying input and coping with output. That is, we push the side effects to the boundaries of our application. All presented solutions follow this principle by shifting the responsibility of iteration and displaying to Vavr’s stream data structure. The shell code of these functions looks as follows.

public static void main(String... args){
    fizzBuzzStream(100).forEach(System.out::println);
}

private static Stream<String> fizzBuzzStream(int amount){
    return Stream.from(1)
        .map(fizzBuzz())
        .take(amount);
}

In the next section we have a deeper look on streams, but let me point out the important parts of this code. First, in the fizzBuzzStream function we create a stream of integers which counts from 1 on. Then, we map these integers with the help of a fizzBuzz function which computes a FizzBuzz element for a given number. This is the part most solutions will implement. Next, we convert the infinite stream to a finite one by taking amount elements. In the main method these elements are printed onto the console.

Notice how the stream hides details like iteration and creation. We operate on a very declarative level and become very concise in expressing our program. Also notice that the side effect of printing the elements is the last action that is performed. Effectively it is the most outer layer in our program.

Vavr Stream

The functional data structure Stream may be Empty or a Cons. A Cons consists of a head element and a lazy computed tail Stream. An infinite Stream does not contain an Empty tail.

We write Stream.cons(1, () -> Stream.empty()) to define a stream which contains 1 as head and an empty tail. Following this notation, defining an infinite stream of 1s may look like this: Stream.cons(1, () -> Stream.cons(1,...)). However, this is not valid syntax in Java and would be rather tedious. Therefore, we employ a recursive function which computes the next 1 on demand.

Stream<Integer> ones() {
    return Stream.cons(1, () -> ones());
}

Java evaluates method arguments before a method is called. In case of an infinite stream this is tricked with a Supplier in order to prevent a stack overflow. This also allows a memory efficient representation of a stream because just the head is kept in memory. This differs from a List which contains all elements and from a Java 8 stream which is a composed aggregate operation on a sequence of data elements from some source.

Test Harness

There are three invariants, a stream of FizzBuzz elements should satisfy:

  • Every third element should start with Fizz
  • Every Multiple of 5 must end with Buzz
  • Every non-third and non-fifth Element should remain a number

In property based testing (PBT), each invariant is combined with a sample generator forming a property. For example, in case of the Fizz invariant, a generator creates multiples of 3. For each generated sample, the invariant is checked. If one check yields false, the property is said to be falsified. Otherwise, it is called satisfied.

For a more thorough introduction and a build-up of the FizzBuzz test harness have a look at my article on property based testing with Vavr.

Little Mike and Evelyn are happy that they coded a functional solution to FizzBuzz with Vavr

6 Ways to FizzBuzz

The first three solutions are about writing pure functions computing FizzBuzz elements. At their core, each of these functions maps a number to a word. This information is exposed by their type: Function<Integer, String>. In fact, any of the three implementations could be used to replace any other one. That is, they are referentially transparent.

In the fourth and fifth solution we have a look at how functions on streams can be employed to create a FizzBuzz solution. We always stay in the stream context and the return type is Stream<String>.

The sixth and last solution explores another approach contributed by Pierre-Yves.

1. A Simple Function

This is the computational core of the imperative solution. Depending on the modulo operations, the corresponding FizzBuzz element is returned.

static Function<Integer, String> fizzBuzz(){
    return i -> {
        if (i % 3 == 0 && i % 5==0) {
            return "FizzBuzz";
        } else if (i % 3 == 0) {
            return "Fizz";
        } else if (i % 5 == 0) {
            return "Buzz";
        } else {
           return i.toString();
        }
   }
}

What I like about this solution is, that it is pretty straight forward and that it shows that a pure function can be factored out of a side-effecting method. However, this solution is still quite inflexible and looks like an imperative solution. For example, if we want to add another case for replacing every multiple of 4 by Bizz we need to add another clause.

2. Pattern Matching

Continue reading %Six Ways to Functional FizzBuzz with Vavr%


Source: Sitepoint