ColOfAbRiX
ColOfAbRiX

Reputation: 1059

Creating a Writer monad in Scala

To learn how to create monads and to learn their internal mechanisms I am trying to implement a version of Writer following this awsome article: You Could Have Invented Monads!

The problem I'm facing is that I can't use the data structures I created in a for comprehension.

This is a cleaned extract of my code:

case class Writer( x: Double, log: String = "  Beginning of log") {
  def flatMap( f: Double => Writer ): Writer = f( x ) match {
    case Writer( fx, msg ) => Writer( fx, log + "\n  " + msg )
  }
  def map( f: Double => Double ): Double = f( x )
}

def outer( x: Double ) = Writer( Math.log( x ), s"Called outer($x)" )
def inner( x: Double ) = Writer( Math.exp( x ), s"Called inner($x)" )

def example( x: Double ): Unit = {
  // This works
  val result = Writer( x ).flatMap {
    y1 => inner(y1).flatMap { y2 => outer(y2) }
  }
  println( result )

  // This works
  val result2 = for { y1 <- Writer(x) } yield {
    for { y2 <- inner( y1 ) } yield {
      for { y3 <- outer( y2 ) } yield y3
    }
  }
  println( result2 )

  // THIS DOESN'T WORK
  val result3 = for {
    y1 <- Writer(x)
    y2 <- inner( y1 )
    y3 <- outer( y2 )
  } yield y3 // Or whatever, it doesn't work
  println( result3 )
}

My full code is available here: Debuggable.scala

  1. I understand how a for comprehension gets desugared, my problem is not there.
  2. I read several tutorials including: Writer? I hardly knew her!
  3. I tried to understand an example of Writer checking out scalaz: GitHub scalaz WriterT but there's too much noise for me

What I cannot figure out is what's missing in my code. It's like the scalaz implementation returns something that is good in a for comprehension and then you run it with a specific method. But can't find how to do it myself

Upvotes: 0

Views: 836

Answers (1)

Andrey Tyukin
Andrey Tyukin

Reputation: 44918

Here is how you can force it to compile:

case class Writer(x: Double, log: String = "  Beginning of log") {
  def flatMap(f: Double => Writer): Writer = f(x) match {
    case Writer(fx, msg) => Writer(fx, log + "\n  " + msg)
  }
  def map(f: Double => Double): Writer = Writer(f(x), log)
}

def outer(x: Double) = Writer(Math.log(x), s"Called outer($x)")
def inner(x: Double) = Writer(Math.exp(x), s"Called inner($x)")

def example(x: Double): Unit = {

  val result = Writer(x).flatMap {
    y1 => inner(y1).flatMap { y2 => outer(y2) }
  }
  println(result)

  val result3 = for {
    y1 <- Writer(x)
    y2 <- inner(y1)
    y3 <- outer(y2)
  } yield y3

  println(result3)
}

example(42)

prints:

Writer(42.0,  Beginning of log
  Called inner(42.0)
  Called outer(1.73927494152050099E18))
Writer(42.0,  Beginning of log
  Called inner(42.0)
  Called outer(1.73927494152050099E18))

As already mentioned in the comments, this whole construct is not a monad, it's rather something like "function composition in the monoid of Double-endomorphisms with logging". There is no way to make your second example work (because Writer is not a Double, but your map takes only Double => Double), so I removed it.

Upvotes: 1

Related Questions