Nearly a decade ago, I wrote a post about implementing FizzBuzz in a more functional manner, which also happened to be the final entry on a blog I started that year. Now, I want to dedicate more time to writing, and rebooting my blog seems to be a good way to achieve this. What better way to begin than by revisiting my previous post and picking up where I left off?
I believe the article is still fine, but reading it feels like looking at old code I wrote: slightly awkward. Now, I would change some parts which I may do in upcoming posts but let's start simple and rewrite in Scala 3. This is the code I left with last time:
def fizzbuzz(s: Int): List[String] = {
// helper method inspired by haskell, cycle a list infinitely,
def cycle(xs: List[String]): Stream[String] = Stream.continually(xs).flatten
// a infinite cycle of "", "", "Fizz"
val fizzes = cycle(List("", "", "Fizz"))
// a infinite cycle of "", "", "", "", "Buzz"
val buzzes = cycle(List("", "", "", "", "Buzz"))
// zip the fizzes and buzzes, and concatenate them, result is "", "", "Fizz", "", "Buzz", "Fizz", ...
val pattern = fizzes zip buzzes map { case (f, b) => f + b }
// zip numbers with the pattern, if the pattern is empty keep the number, otherwise keep the pattern
val numbers = Stream.from(1)
val numbersAndPattern = numbers zip pattern map {
case (n, p) => if (p.isEmpty) n.toString else p
}
numbersAndPattern take s toList
}
Below is a Scala 3 version in which I comment on the changes. Note that I added a Scala 3 version of a `main` function which can be run using Scala CLI.
// Commenting the Scala 3 version / Scala CLI directive
//> using scala 3.3.1
// optional significant white spacing, we can omit all curly braces
@main def fizzbuzz(): Unit =
def fizzbuzz(s: Int): List[String] =
// `Stream` is replaced with `LazyList`, the latter being fully lazy.
def cycle(xs: List[String]): LazyList[String] = LazyList.continually(xs).flatten
val fizzes = cycle(List("", "", "Fizz"))
val buzzes = cycle(List("", "", "", "", "Buzz"))
// case matches on tuples are not required anymore
val pattern = fizzes zip buzzes map(_ + _)
val numbers = LazyList.from(1)
// again case match on tuples removed
val numbersAndPattern = numbers zip pattern map:
(n, p) =>
// if-then-else statement
if p.isEmpty then n.toString else p
// here extra braces are required
(numbersAndPattern take s).toList
// not required, explicit end clause for larger functions
end fizzbuzz
fizzbuzz(20).foreach(fb => print(fb + "," ))
This will print 1,2,Fizz,4,Buzz,Fizz,7,8,Fizz,Buzz,11,Fizz,13,14,FizzBuzz,16,17,Fizz,19,Buzz,
.
Most notable is the significant white spacing. I understand it is not to everybody's taste but I quite like it. I admit sometimes I get unexpected compiler errors because of it but nothing big not frequently. Another nice improvement which always was weird is that now the `case` matches on tuples can be removed when mapping.
Cleaning up a little a removing superfluous comments I leave it at:
// Commenting the Scala 3 version / Scala CLI directive
//> using scala 3.3.1
@main def fizzbuzz(): Unit =
def fizzbuzz(s: Int): List[String] =
def cycle(xs: List[String]): LazyList[String] = LazyList.continually(xs).flatten
val fizzes = cycle(List("", "", "Fizz"))
val buzzes = cycle(List("", "", "", "", "Buzz"))
val pattern = fizzes zip buzzes map (_ + _)
val numbers = LazyList.from(1)
val numbersAndPattern = numbers zip pattern map: (n, p) =>
if p.isEmpty then n.toString else p
(numbersAndPattern take s).toList
end fizzbuzz
fizzbuzz(20).foreach(fb => print(fb + ","))
end fizzbuzz