No dobra… po prostu będzie to przykład wykorzystania popularnych narzędzi by stworzyć FizzBuzz bez pisania ifów. W ogólności problem rozbija się o dwie brakujące metody. Pierwsza dostarczająca „zoraclowanego” String-a, czyli takiego gdzie pusty String będzie oznaczać null. Druga to cykl. Pierwszą metodę weźmiemy z Apache Commons Lang, a drugą z Guavy. Cały pomysł bazuje na ostatniej JUG-owej prezentacji Pawła Szulca, który pokazał to coś w Haskell-u. Mi się to spodobało i poniżej wersja w javie.

Listing 1. FizzBuzz bez ifów

public class FizzBuzz {

	public static void main(String[] args) {
		Iterator<String> fizz = Iterables.cycle("", "", "Fizz").iterator();
		Iterator<String> buzz = Iterables.cycle("", "", "", "", "Buzz").iterator();

		OfInt ints = IntStream.iterate(1, n -> n + 1).iterator();
		Iterator<Optional<String>> strings = Stream.generate(() -> StringOptional.of(fizz.next() + buzz.next()))
				.iterator();

		Stream.generate(() -> {
			Optional<String> s = strings.next();
			Integer i = ints.next();
			return s.orElse(i.toString());
		}).limit(32).forEach(System.out::println);

	}
}

class StringOptional {

	public static Optional<String> of(String v) {
		return ofNullable(defaultIfEmpty(v, null));
	}
}

Na początek klasa StringOptional, która będzie nam zamykać wywołanie StringUtils.defaultIfEmpty tak by ładnie produkowało Optional.

I teraz metoda main. Na początek tworzymy dwa cykle pierwszy reprezentuje częstość dla fizz, drugi dla buzz. W przypadku uogólnionym można pokusić się o zaimplementowanie tego w oparciu o metodę w rodzaju Collections.fill i listy o określonej długości. Przy czym należy pamiętać, że java jest ewaluowana zachłannie i dla dużych wartości całość się wywali.

Następnie tworzymy dwa iteratory z nieskończonych Stream-ów. Pierwszy generuje kolejne inty, a drugi w oparciu o konkatenację wartości z cykli oraz naszą magiczną klasę StringOptional odpowiednią wersję Optional.

Ostatnia linijka składa nam to wszystko do kupy. Tworzymy Stream, który będzie budowany według logiki jeżeli optional.empty to wypisz numer inaczej wypisz zawartość. Przy czym warunek jest weryfikowany na poziomie typu Optional, a ten jest determinowany w metodzie ofNullable, zatem pochodzi „z języka”.

Co autor miał na myśli?

Powyższy kod to przykład na to jak odpowiednie użycie typów pozwala na uproszczenie algorytmu. Dobór poszczególnych elementów na początku pozwolił na uproszczenie algorytmu. Co więcej kod ten można zrealizować jeszcze prościej:

Listing 2. FizzBuzz uproszczony

public class FizzBuzz {

	public static void main(String[] args) {
		Iterator<String> fizzBuzz =
				cycle("", "", "Fizz", "", "Buzz", "Fizz", "", "", "Fizz", "Buzz", "", "Fizz", "", "", "FizzBuzz")
						.iterator();

		OfInt ints = iterate(1, n -> n + 1).iterator();
		Iterator<Optional<String>> strings = generate(() -> ofNullable(defaultIfEmpty(fizzBuzz.next(), null)))
				.iterator();

		generate(() -> strings.next().orElse(ints.next().toString()))
				.limit(32).forEach(System.out::println);

	}
}

Ale w tym przypadku pierwsza linijka z fizzBuzz wprowadza trochę zagmatwania ponieważ specyfikacja którą reprezentuje jest tak naprawdę „dwuwymiarowa” tzn. ograniczona przez dwa warunki. W tej wersji one „giną” w postaci jednego dość rozwlekłego warunku.

Odpowiednio dobrane typy pozwalają na specyfikowanie algorytmu w oparciu o nie właśnie, a nie jakieś arbitralne zewnętrzne warunki. W dodatku pozwalają na lepsze przetestowanie, bo testujemy implementację typu, oraz dają większe bezpieczeństwo końcowego rozwiązania ponieważ aplikujemy funkcje, których dziedzina i przeciwdziedzina są ograniczone do konkretnych typów (a mapowanie A→B zazwyczaj może być realizowane w jeden konkretny sposób).