# FizzBuzz fun in Scala: Combining functions

Every implementation of FizzBuzz in this series, at its core, has relied on an infinitely counting lazy list. This modelling is logical, as the game can theoretically be played indefinitely. In this post, we will explore the possibility of defining a single function operating on this running number and, as a bonus, utilize Scala 3's extension methods to enhance code clarity.

```scala
LazyList
  .from(1)
  ... // multiple statements
  .take(n)
  .toList
```

What we are looking for is:

```scala
LazyList
  .from(1)
  .map(i: Int => ???)
  .take(n)
  .toList
```

Let's define a method and its signature:

```scala
LazyList
  .from(1)
  .map(fizzbuzzAt)
  .take(n)
  .toList

def fizzbuzzAt(n: Int): String = ???
```

Note from the first article in this series an implementation of `fizzbuzzAt` using `if` statements could be:

```scala
// Commenting the Scala 3 version / Scala CLI directive
//> using scala 3.3.1

@main def fizzbuzz(): Unit =

  // function to re-implement
  def fizzbuzzAt(n: Int): String =
    if n % 15 == 0 then "FizzBuzz"
    else if n % 5 == 0 then "Buzz"
    else if n % 3 == 0 then "Fizz"
    else
      n.toString
  end fizzbuzzAt

  def fizzbuzz(n: Int): List[String] =
    LazyList
      .from(1)
      .map(fizzbuzzAt)
      .take(n)
      .toList
  end fizzbuzz

  fizzbuzz(20).foreach(fb => print(fb + ","))
end fizzbuzz
```

Now we want to simplify the `if` expression and make it easier to implement e.g. other 'shouts', say Bazz every 2 times. For this article, we want to be looking into a solution using functions.

A quite natural function to decide whether Fizz should be shouted is one taking an integer as input and returning *maybe* a String ("Fizz"):

```scala
val fizz: Int => Option[String] = ???
```

Our `fizzbuzzAt` could then look like:

```scala
  def fizzbuzzAt(n: Int): String =
    // fizz and buzz are similar and can be implemented:
    val fizz: Int => Option[String] = i =>
      if i % 3 == 0 then Some("Fizz") else None
    val buzz: Int => Option[String] =
      i => if i % 5 == 0 then Some("Buzz") else None
    // Both need to be combined into a function with same signature
    val combined: Int => Option[String] = ???
    // The final function which should return a String
    val fizzbuzz: Int => String = ???
    fizzbuzz(n)
  end fizzbuzzAt
```

So we need to combine 2 functions of the same signature `Int => Option[String]`:

```scala
  def combine[A](f1: A => Option[String], f2: A => Option[String]): A => Option[String] =
    a =>
      (f1(a), f2(a)) match
        case (Some(s1), Some(s2)) => Some(s1 + s2)
        case (None, Some(s2))     => Some(s2)
        case (Some(s1), None)     => Some(s1)
        case (None, None)         => None
```

We generalized a little in the argument, the return type we keep at String as we need to operate on it. (In a next blog post we will also generalize this more but we leave it as this for now.)

`fizzbuzz` needs to turn the combined function `Int => Option[String]` into a function `Int => String`. For the FizzBuzz game, turning `Option[String]` into a `String` given an integer can be done like:

```scala
val fizzbuzz: Int => String = i =>
  combined(i).getOrElse(i.toString)
```

`fizzbuzzAt` then becomes:

```scala
def fizzbuzzAt(n: Int): String =
  val fizz: Int => Option[String] = i =>
    if i % 3 == 0 then Some("Fizz") else None
  val buzz: Int => Option[String] = i =>
    if i % 5 == 0 then Some("Buzz") else None
  val combined: Int => Option[String] =
    combine(fizz, buzz)
  val fizzbuzz: Int => String = i =>
    combined(i).getOrElse(i.toString)
  fizzbuzz(n)
end fizzbuzzAt
```

Let's transform `fizzbuzzAt` into a function as well:

```scala
val fizzbuzzAt: Int => String =
  val fizz: Int => Option[String] = i =>
    if i % 3 == 0 then Some("Fizz") else None
  val buzz: Int => Option[String] = i =>
    if i % 5 == 0 then Some("Buzz") else None
  val combined: Int => Option[String] =
    combine(fizz, buzz)
  val fizzbuzz: Int => String = i =>
    combined(i).getOrElse(i.toString)
  fizzbuzz // return a function from Int to String
end fizzbuzzAt
```

Let's eliminate the duplication in the `fizz` and `buzz` functions:

```scala
  val fizzbuzzAt: Int => String =
    extension (word: String)
      def every(n: Int): Int => Option[String] = i => 
        if i % n == 0 then Some(word) else None

    val fizz = "Fizz".every(3)
    val buzz = "Buzz".every(5)
    val combined: Int => Option[String] =
      combine(fizz, buzz)
    val fizzbuzz: Int => String = i =>
      combined(i).getOrElse(i.toString)
    fizzbuzz
  end fizzbuzzAt
```

Here we make use of a Scala 3 extension method to make the function definition clearer and, I would argue, more easily reusable with less chance of errors.

Putting all word shouts into a list allows for easier extension in a single place:

```scala
val fizzbuzzAt: Int => String =
  extension (word: String)
    def every(n: Int): Int => Option[String] = i => if i % n == 0 then Some(word) else None

  val wordShouts = List(
    "Fizz".every(3),
    "Buzz".every(5),
    // Easy to extend:
    "Bazz".every(2)
  )
  val combined = wordShouts.reduce(combine) // will fail on empty wordShouts
  val fizzbuzz: Int => String = i =>
    combined(i).getOrElse(i.toString)
  fizzbuzz
end fizzbuzzAt
```

If we want to account for empty `wordShouts` we can instead resort to a `fold`:

```scala
val combined = words.fold(_ => None)(combine)
```

We defined the 'zero' function to accept any integer and return `None`. I will expand on this in a next blog post and leave the `reduce` function in the final version of this article:

```scala
// Commenting the Scala 3 version / Scala CLI directive
//> using scala 3.3.1

@main def fizzbuzz(): Unit =

  def combine[A](f1: A => Option[String], f2: A => Option[String]): A => Option[String] =
    a =>
      (f1(a), f2(a)) match
        case (Some(s1), Some(s2)) => Some(s1 + s2)
        case (None, Some(s2))     => Some(s2)
        case (Some(s1), None)     => Some(s1)
        case (None, None)         => None

  val fizzbuzzAt: Int => String =
    extension (word: String)
      def every(n: Int): Int => Option[String] = i =>
        if i % n == 0 then Some(word) else None

    val wordShouts = List(
      "Fizz".every(3),
      "Buzz".every(5)
    )
    val combined = wordShouts.reduce(combine) // will fail on empty wordShouts
    val fizzbuzz: Int => String = i =>
      combined(i).getOrElse(i.toString)
    fizzbuzz
  end fizzbuzzAt

  def fizzbuzz(n: Int): List[String] =
    LazyList
      .from(1)
      .map(fizzbuzzAt)
      .take(n)
      .toList
  end fizzbuzz

  fizzbuzz(20).foreach(fb => print(fb + ","))
end fizzbuzz
```

This prints:

```bash
1,2,Fizz,4,Buzz,Fizz,7,8,Fizz,Buzz,11,Fizz,13,14,FizzBuzz,16,17,Fizz,19,Buzz,
```

In conclusion, this article demonstrated how to create a single function for the FizzBuzz game using Scala, combining functions and making use of Scala 3's extension methods for clarity and reduced potential for errors. This approach allows for easier testing and extension of the game with additional word shouts, showcasing the power and flexibility of functional programming and Scala.
