Why Box/Option instead of Exception in LiftWeb/Scala?

So, I've been reading this article about Box usage in LiftWeb which seems so be part of the official documentation as it's linked through the source code comments. My question is why is Box/Failure preferable to actually coding without null and throwing an Exception that would be caught at top level and transformed into an appropriate error code/message. So instead of

case "user" :: "info" :: _ XmlGet _ =>
  for {
    id <- S.param("id") ?~ "id param missing" ~> 401
    u <- User.find(id) ?~ "User not found"
  } yield u.toXml

why not

case "user" :: "info" :: _ XmlGet _ => User.find(S.param("id").openOrThrow(
    new IllegalArgumentException("idParamMissing"))).toXml

and have User.find throw something like NotFoundException

Answers


Imagine you have a method which does some operation which may potentially fail, for example fetching a web page.

def getUrl(url: String): NodeSeq = {
   val page = // ...
   // ...
   if (failure) throw new PageNotFoundException()
   page
}

If you want to use it, you need to do

val node = try {
  getUrl(someUrl)
} catch {
  case PageNotFoundException => NodeSeq.Empty
}

or similar depending on the situation. Still, it looks somewhat okay to do so. But now imagine you want to do this for a collection of URLs.

val urls = Seq(url1, ...)
val nodeseqs: Seq[NodeSeq] = try {
  urls.map(getUrl)
} catch {
  case PageNotFoundException => Seq.Empty
}

Okay so this return an empty sequence whenever one of the pages could not be loaded. What if we’d like to receive as many as possible?

val urls = Seq(url1, ...)
val nodeseqs: Seq[NodeSeq] = urls.map { url =>
  try {
    getUrl(url)
  } catch {
    case PageNotFoundException => NodeSeq.Empty
  }
}

Now our logic is intermingled with error handling code.

Compare this to the following:

def getUrl(url: String): Box[NodeSeq] = {
  val page = // ...
  // ...
  if (failure) Failure("Not found")
  else Full(page)
}

val urls = Seq(url1, ...)
val nodeseqs: Seq[Box[NodeSeq]] = urls.map(getUrl(url)).filter(_.isDefined)
// or even
val trueNodeseqs: Seq[NodeSeq] = urls.map(getUrl(url)).flatten

Using Option or Box (or Either or scalaz’ Validation) gives you way more power over deciding when to deal with a problem than throwing exceptions ever could.

With exceptions you may only traverse the stack and catch it as some point there. If you encode the failure inside a type, you may carry it around with you as long as you like and deal with it in the situation you think is most appropriate.


If you throw an exception, the only thing you can do is catch it. So, if two parts of your page throw an exception, you won't ever get to the second one. If one part of your page returns a Box which happens to be a Failure, and the second part doesn't need the return value of the first one, you can see both. More generally, Box has a useful API which Exception lacks.


Need Your Help

gson.fromJson - deserialization failing

android gson

I am facing issues with deserialization since I use same object structure for two different web-response. In 'search', I get array of object, and in 'details' I get just one object.

How to implement both pinch zoom and pan with multitouch?

android zoom multi-touch pan

First of all i should say my app is already has pinch zoom which works perfectly and it has pan feature too but this pan feature works with single touch and i want to make both work with multi touch.

About UNIX Resources Network

Original, collect and organize Developers related documents, information and materials, contains jQuery, Html, CSS, MySQL, .NET, ASP.NET, SQL, objective-c, iPhone, Ruby on Rails, C, SQL Server, Ruby, Arrays, Regex, ASP.NET MVC, WPF, XML, Ajax, DataBase, and so on.