home search about rss feed twitter ko-fi

Debuggable: Using List Instead Of String (Scala 3 Video)

Motivation for this lesson

In this lesson I’ll (quickly) show a slight variation of the Debuggable class. The motivations for this are:

  • It’s important to get comfortable writing “wrapper” classes like this
  • Many of the lessons that follow show more wrapper classes, and a simple variation now will help get your ready for those later

Source code

The source code for this lesson is at this URL:

From String to List[String]

The change in this lesson is relatively simple: I’ll show how to implement Debuggable with a List to store the log messages, rather than using a String as I did in the previous lessons.

Since I want to store the messages as a List[String], the first change is to the Debuggable class signature, which now looks like this:

case class Debuggable[A](value: A, log: List[String]) {
                                        ------------

Inside the Debuggable class, this change has no effect on map. The log parameter used to be a String, and now it’s a List[String], and the code requires no changes:

def map[B](f: A => B): Debuggable[B] = {
    val nextValue = f(value)
    Debuggable(nextValue, this.log)
}

The change does affect flatMap, which now prepends the old log message before nextValue.log when creating a new Debuggable instance:

def flatMap[B](f: A => Debuggable[B]): Debuggable[B] = {
    val nextValue: Debuggable[B] = f(value)
    Debuggable(nextValue.value, this.log ::: nextValue.log)
}

The change also affects f (and g and h), which now give Debuggable a List (rather than a String):

def f(a: Int): Debuggable[Int] = {
    val result = a * 2
    Debuggable(result, List(s"f: multiply $a * 2 = $result"))
}

It also affects the final printing of the log messages:

// print a `List` (rather than a `String`)
finalResult.log.foreach(l => println(s"LOG: $l"))

The complete source code

With those changes, here’s the complete source code for the new version of Debuggable:

// `log` is now a list
case class Debuggable[A](value: A, log: List[String]) {

    def map[B](f: A => B): Debuggable[B] = {
        val nextValue = f(value)
        Debuggable(nextValue, this.log)
    }

    // prepend `this.log` before `nextValue.log`
    def flatMap[B](f: A => Debuggable[B]): Debuggable[B] = {
        val nextValue: Debuggable[B] = f(value)
        Debuggable(nextValue.value, this.log ::: nextValue.log)
    }
}

object DebuggableList extends App {

    def f(a: Int): Debuggable[Int] = {
        val result = a * 2
        Debuggable(result, List(s"f: multiply $a * 2 = $result"))
    }

    def g(a: Int): Debuggable[Int] = {
        val result = a * 3
        Debuggable(result, List(s"g: multiply $a * 3 = $result"))
    }

    def h(a: Int): Debuggable[Int] = {
        val result = a * 4
        Debuggable(result, List(s"h: multiply $a * 4 = $result"))
    }

    val finalResult = for {
        fRes <- f(100)
        gRes <- g(fRes)
        hRes <- h(gRes)
    } yield s"result: $hRes"

    finalResult.log.foreach(l => println(s"LOG: $l"))
    println(s"Output is ${finalResult.value}")

}

The output of the App looks like this:

LOG: f: multiply 100 * 2 = 200
LOG: g: multiply 200 * 3 = 600
LOG: h: multiply 600 * 4 = 2400
Output is result: 2400

Discussion

While this was a relatively minor change to the Debuggable class, it’s helpful to see how that change has a ripple effect throughout the code. I showed this change so you could see that effect, but I also prefer this List approach, which is a more “real world” example than the previous String-based example.

The Writer monad

If you’re interested in where the Debuggable class comes from, it’s actually an implementation of something known as the Writer monad in Haskell.

As Learn You a Haskell for Great Good! states, “the Writer monad is for values that have another value attached that acts as a sort of log value. Writer allows us to do computations while making sure that all the log values are combined into one log value that then gets attached to the result.”

Key points

In the last few lessons I showed several different versions of the Debuggable class to help you get comfortable with it. Here are a few important points about it:

  • A monad consists of a class with map and flatMap methods, along with some form of a “lift” function.
  • A class built like this is intended to be used in for expressions.
  • I refer to these as “wrapper” classes. While they’re intended to be used in for expressions, they’re not “containers” like Scala collections classes, they’re more a type of “wrapper” or “box.” They wrap existing types to enable them to be used in for expressions.

See also

When I was struggling to learn what a monad was, the following links were the best resources I found:

You can read more about the Writer monad at these resources:

Update: All of my new videos are now on
LearnScala.dev