Freewind
Freewind

Reputation: 198218

How to improve the code of a method which uses 'Free monad'?

I'm trying some code which inspects this slides about Free Monad in Scala, and made a small project with some slightly changed code.

The project is here: https://github.com/freewind/free-the-monads

Everything seems good at first, the code is clean and beautiful:

def insertAndGet() = for {
    _ <- Script.insert("name", "Freewind")
    value <- Script.get("name")
  } yield value

  def insertAndDelete() = for {
    _ <- Script.insert("name", "Freewind")
    _ <- Script.delete("name")
    value <- Script.get("name")
  } yield value

  def insertAndUpdateAndDelete() = for {
    _ <- Script.insert("name", "Freewind1")
    oriValue <- Script.update("name", "Freewind2")
    _ <- Script.delete("name")
    finalValue <- Script.get("name")
  } yield (oriValue, finalValue)

But when my logic is complex, e.g. there is some Script[Option[_]], and I need to check the option value to decide to do something, I can't use the for-comprehension any more, the code is like:

private def isLongName(name: String): Script[Boolean] = for {
  size <- Script.getLongNameConfig
} yield size.exists(name.length > _)

def upcaseLongName(key: String): Script[Option[String]] = {
  Script.get(key) flatMap {
    case Some(n) => for {
      isLong <- isLongName(n)
    } yield isLong match {
        case true => Some(n.toUpperCase)
        case false => Some(n)
      }
    case _ => Script.pure(None)
  }
}

I found the Free Monad approach is really interesting and cool, but I'm not familiar with scalaz, and just beginning to learn Monad things, not sure how to improve it.

Is there any way to make it better?


PS: You can just clone the project https://github.com/freewind/free-the-monads and try it yourself

Upvotes: 1

Views: 133

Answers (1)

Travis Brown
Travis Brown

Reputation: 139038

This is a good use case for the OptionT monad transformer:

import scalaz.OptionT, scalaz.syntax.monad._

def upcaseLongName(key: String): OptionT[Script, String] = for {
  n <- OptionT.optionT(Script.get(key))
  isLong <- isLongName(n).liftM[OptionT]
} yield if (isLong) n.toUpperCase else n 

Here OptionT.optionT converts a Script[Option[String]] into an OptionT[Script, String], and .liftM[OptionT] raises a Script[Boolean] into the same monad.

Now instead of this:

println(upcaseLongName("name1").runWith(interpreter))

You'd write this:

println(upcaseLongName("name1").run.runWith(interpreter))

You could also have upcaseLongName return an Script[Option[String]] directly by calling run there, but if there's any chance you'll need to be composing it with other option-y script-y things it's probably best to have it return OptionT[Script, String].

Upvotes: 6

Related Questions