Box

See also:

Intro

Lift’s Box[T] monad is similar to Scala’s Option[T].

Both were created to avoid having special meaning associated with some values.
Ex. a NULL pointer is value 0 meaning “nowhere”. It has to be explicitly checked since for the computer 0 is just an other number.

Using the box

Opening the box

Why bother boxing values?

Have you ever thrown a NullPointerException? Frankly, I don’t know any developers that haven’t had this happen to them at some point because of something they hadn’t considered during development… in short, a NPE is something that was caused by an unexpected series of events causing your application to explode in a variety of ways. This is not good.

Consider a scenario where I want to do something with a value returned from a database: it’s not uncommon to see programs where one assumes the database query got the correct result, then do some operation on it. Well, news flash, it’s highly plausible that the database query might not go as you had expected and return some other result….

…Boxes to the rescue! As the name might suggest, a Box is something that you can put stuff in, take stuff out of, and also find empty. Much as you would do with a real life box. Lets illustrate with a simple example:

import net.liftweb.common.{Box,Full,Empty,Failure,ParamFailure}

scala> val x: Box[String] = Empty
x: net.liftweb.common.Box[String] = Empty

scala> val y = Full
y: net.liftweb.common.Full[java.lang.String] = Full

We have two simple examples here that assign new boxes to vals, and as you can see, x is assigned as a Box[String], yet its actual value is Empty which is a subtype of Box. The second assignment to y has the same type signature, but this time it is “Full” with a value; in this instance “Thing”.

What about exceptions?

Lift has a helper method in net.liftweb.util.Helpers called tryo that is a specilized control structure so that if you can execute code in a block that returns a Box’ed value irrespective of what you did in the block and how its execution went. If you’re using lift-webkit then you already have access to these utility methods, however if you just want to stay light and are not using webkit then the definition of tryo looks like:

def tryo[T]
  : Box[T] = {
    try {
      Full
    } catch {
      case c if ignore.exists(_.isAssignableFrom) => 
        onError.foreach(_); Empty
      case c if  => 
        onError.foreach(_); Failure(c.getMessage, Full, Empty)
    }
}

There are some other overloads for syntax sugar, but essentially it lets you do:

tryo {
  // your code here
}

So let’s assume we had some remote API to invoke, and it could not connect, or blow up in some way you hadn’t planned, what would happen givenevernote a tryo block / wrap? Consider:

scala> tryo 
res4: net.liftweb.common.Box[Int] = Failure(
  For input string: "www",
  Full,
  Empty)

As “www” isnt an Int, it unsurprisingly cannot be converted to one, so it blows up with java.lang.NumberFormatException. However, using tryo we are left with a special Box subtype called Failure. This lets us handle the error in a concise way rather than needing to check all the possible outcomes or worry about some awful try-catch block.

scala> tryo openOr 1234                       
res7: Int = 1234

scala> tryo.map.openOr
res8: java.lang.String = Invalid Strings

scala> for(x <- tryo) yield x
res11: net.liftweb.common.Box[Int] = Failure(
  For input string: "www",
  Full,
  Empty)

So you can see there several ways to interact with Box, Full and Failure subtypes, providing defaults inline and mapping the results, etc.

Handling empty values?

But what if we need more information or the value we are looking for isnt a failure, its just the value is Empty ? For example, let’s assume we were looking for a request parameter to a REST API or similar. Rather than throwing a HTTP 500 error with no reason, it would be nice to give the user a much more granular reason. Consider getting a request paramater using Lift’s S.param method:

scala> S.param ?~ "You must supply an ID parameter" 
res17: net.liftweb.common.Failure = 
  Failure

The ?~ method allows you to supply an error message and convert the Empty into a Failure. This is an incredibly helpful paradigm when used with for comprehensions.

Chaining boxes

Let’s assume that we have a situation where by we could receive a value from several places, or we need to “fall through” to a specific result based on trying several values in a sequence. Box supports this by way of the or operand.

scala> val x = Empty
x: net.liftweb.common.Empty.type = Empty

scala> val y = tryo 
y: net.liftweb.common.Box[Int] = 
  Failure(For input string: "qqq",
  Full,
  Empty)

scala> x or y or Full
res18: net.liftweb.common.Box[Any] = Full

Box has a bunch of features I haven’t covered here, but I hope this helps you understand the rational of this specialised option-esq type.

Transforming content

The content of a box can simply be transformed with the map method. This will return a box with the transformed value . Another common pattern, if you don’t want the result of the transformation in a box, is this:

myInputValue.map openOr myFallbackValue

A related method is choice, which allows more complex transformations from Box[A] to Box[B].

Pattern matching

Box and its descendants have a logical inheritance chain which can be used to get as little or as much information as desired, as DPP has explained:

A Box can be full or empty: Full or EmptyBox

An empty box can contain no additional information as to why it’s empty or it can contain more information as to why the Box is empty: Failure. Now, a Failure could have subclasses such as Failure with exception and chained Failure, but it seemed to me that there would be an explosion of subclasses, so I just chose Failure with the various parameters.

Then someone pointed out that it’d be nice to carry around an HTTP response code or other data about the Failure which led to ParamFailure. Because ParamFailure is a subclass of Failure, you can pattern match on Failure and it will catch ParamFailure, but you lose the information about the parameter

Because of this you might pattern match on a response like so:

resp match {
  case ParamFailure => // ...
  case Failure => // ...
  case Empty => // ...
  case box: EmptyBox => // ...
  case Full => // ...
  case box: Box[_] => // ...
}