Wednesday, November 9, 2011

Some notes about how to simplify scala code (and monads)

This is just some notes...To be continued and completed ... Updated on 2011-11-17
Interesting articles (and set of comments) to understand and recognize monads :
- Monads are not metaphors
- Monads are elephants

Exploring the best way to get a value from a map, or a default value if the key doesn't exist :
val codes=Map("A"->10, "B"->5, "C"->20)

{ // Basic approach
  var code = 30
  if (codes contains "D") code = codes.get("D").get
}

{ // More compact approach
  val code = if (codes contains "D") codes.get("D").get else 30
}

{ // Match approach
  val code = codes.get("D") match {
    case Some(v)=>v
    case None=>30
  }
}

{ // Option monad approach
  val code = codes get "D" getOrElse 30
}

{ // Compact approach
  val code = codes.getOrElse("D",30)
}
With monads, many test cases can be avoided, the code looks like a data flow :
import collection.JavaConversions._
import java.io.File
val sp = java.lang.System.getProperties.toMap

val lookInto = sp.get("baseDir") orElse sp.get("rootDir") orElse sp.get("user.home")
val lookIntoDirFile = lookInto map {new File(_)} filter {_.exists}
val count = lookIntoDirFile map {_.list.size}

println(count map {_+" files"} getOrElse "Dir not found")

// will print "Dir not found" or for example "184 files"

An other example using Option Monad, the second fullName implementation becomes very simple:
// Using classical approach, a Java like approach
case class Person1(firstName:String, lastName:String) {
  def fullName() = {
    if (firstName!=null) {
      if (lastName!=null) {
        firstName+" "+lastName
      } else null
    } else null
  }
}

{
  val p1 = Person1("Einstein", "Albert")
  val p2 = Person1("Aristote", null)
  println("1-%s" format p1.fullName)
  val fullName = if (p2.fullName!=null) p2.fullName else "Unknown"
  println("1-%s" format fullName)
}

// Using Option monad approach  
case class Person2(firstName:Option[String], lastName:Option[String]) {
  def fullName() = firstName flatMap {fn => lastName map {ln => fn+" "+ln}}
}

{
  val p1 = Person2(Some("Einstein"), Some("Albert"))
  val p2 = Person2(Some("Aristote"), None)
  println("2-%s" format p1.fullName.getOrElse("Unknown"))
  println("2-%s" format p2.fullName.getOrElse("Unknown"))
}

// the Best and the cleanest, same usage as the previous one
case class Person3(firstName:Option[String], lastName:Option[String]) {
  def fullName() = for(fn<-firstName ; ln <- lastName) yield fn+" "+ln
}


To be continued and completed ...

No comments:

Post a Comment