FizzBuzz record edition

Remi Forax forax at univ-mlv.fr
Tue Sep 1 19:59:35 UTC 2020


This is an example i've used to explain how sealed types and records work together.

Let say you want to write FizzBuzz [1],
the output of FizzBuzz is
  1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, FizzBuzz, 16, ...

so an output item is either a Value (1, 2, 3, etc) or a Special words "Fizz", "Buzz" or "FizzBuzz"

In term of model, it gives us
  Item = Value(int value) | Special("Fizz") | Special("Buzz") | Special("FizzBuzz")

The values Value are pure data so they can be represented by a record Value.
Fizz, Buzz and FizzBuzz are constants so they can be represented by an enum named Special.
Item is either a Special or a Value (and not more), so it's an interface sealed so it only accept Value and Special as subtypes.

Now, we need to implement the fizzbuzz logic which associate the right Item for an integer value,
for that we use a static method factory createItem() in Item.
Each Special has a divisor associated (as a field) and we loop over the divisors to find if one
is able to divide the value otherwise we return a Value.

In the main, we loop over the numbers 1 to 100, calls createItem and prints the item.


import static java.util.stream.IntStream.rangeClosed;

import java.util.List;

public interface RecordFizzBuzz {
  sealed interface Item {
    static Item create(int value) {
      return Special.ALL.stream().filter(s -> value % s.divisor == 0)
          .findFirst()
          .<Item>map(s -> s)
          .orElseGet(() -> new Value(value));
    }
  }
  record Value(int value) implements Item {
    @Override
    public String toString() {
      return "" + value;
    }
  }
  enum Special implements Item {
    FizzBuzz(15), Buzz(5), Fizz(3)
    ;
    final int divisor;
    Special(int divisor) {
      this.divisor = divisor;
    }
    final static List<Special> ALL = List.of(values());
  }

  static void main(String[] args) {
    rangeClosed(1, 100).mapToObj(Item::create).forEach(System.out::println);
  }
}

There are two tricks,
- the Stream in Item.create() uses a <Item>map(s -> s) to see a Stream<Special> as a Stream<Iteam>
- the result of Special.values() is stored in an immutable List otherwise each call to values() create a new array.

Everything works nicely in both IntelliJ 2020 and the upcoming Eclipse 2020.9 (4.17).

enjoy,
Rémi

[1] https://en.wikipedia.org/wiki/Fizz_buzz


More information about the amber-dev mailing list