blog blog blog

Kelsey is a nerd.

Box Cheat Sheet for Lift

| Comments

When you’re learning Scala, one of the first concepts that clicks is pattern matching. Once you get the “hammer” of pattern matching in your Scala toolbox, everything is a nail; you want to use it everywhere for everything. Pattern matching is very powerful and easy to use, but it turns out for many, many simple cases using a higher-order function is more concise and performant. I have Tony Morris’s Option Cheet Sheet bookmarked and use it almost every day.

In Lift, though, we generally use Box instead of Option. Box adds a third Failure state and a host of additional methods. I got tired of translating the Option methods to Box ones in my head so I wrote up this translation of Box methods to match statements. I used the Lift API Docs and the Lift source code to compile it.

The next hammer-that-turns-everything-into-a-nail that I encountered in learning Scala was for-comprehensions. I’m still kind of in love with them! In the interest of length, I haven’t included any examples here, but Box plays very nicely with for-comprehensions as well. I hope to cover some ways we use Box in for-comprehensions in a future post.

flatMap

theBox.flatMap(foo(_))
foo returns a Box of any type
1
2
3
4
5
theBox match {
  case Empty => Empty
  case Failure(message, exception, chain) => Failure(message, exception, chain)
  case Full(x) => foo(x)
}

map

theBox.map(foo(_))
foo returns any type
1
2
3
4
5
theBox match {
  case Empty => Empty
  case Failure(message, exception, chain) => Failure(message, exception, chain)
  case Full(x) => Full(foo(x))
}

dmap

Equivalent to .map(foo(_)).openOr(bar)

theBox.dmap(bar)(foo(_))
foo returns a value of the same type as bar
1
2
3
4
5
theBox match {
  case Empty => bar
  case Failure(message, exception, chain) => bar
  case Full(x) => foo(x)
}

choice

Equivalent to .flatMap(foo(_)).or(bar())

theBox.choice(foo(_))(bar())
foo and bar are functions that return Boxes of the same type
1
2
3
4
5
theBox match {
  case Empty => bar()
  case Failure(message, exception, chain) => bar()
  case Full(x) => foo(x)
}

===

theBox === bar
bar is a value of any type
1
2
3
4
5
theBox match {
  case Empty => false
  case Failure(message, exception, chain) => false
  case Full(x) => x.equals(bar)
}

equals

Determines equality based upon the the Box’s content. In the case of two Failures being compared, the causes must match exactly.

theBox.equals(bar)
bar is a value of any type
1
2
3
4
5
6
7
(theBox, bar) match {
  case (Empty, Empty) => true
  case (Failure(msg1, ex1, chain1), Failure(msg2, ex2, chain2)) => (msg1, ex1, chain1) == (msg2, ex2, chain2)
  case (Full(x), Full(y)) => x.equals(y)
  case (Full(x), y) => x.equals(y)
  case _ => false
}

isEmpty

This one can be tricky!

theBox.isEmpty
1
2
3
4
5
theBox match {
  case Empty => true
  case Failure(message, exception, chain) => true
  case Full(x) => false
}

isDefined

theBox.isDefined
1
2
3
4
5
theBox match {
  case Empty => false
  case Failure(message, exception, chain) => false
  case Full(x) => true
}

exists

theBox.exists(foo(_))
foo returns Boolean
1
2
3
4
5
theBox match {
  case Empty => false
  case Failure(message, exception, chain) => false
  case Full(x) => foo(x)
}

forall

theBox.forall(foo(_))
foo returns Boolean
1
2
3
4
5
theBox match {
  case Empty => true
  case Failure(message, exception, chain) => true
  case Full(x) => foo(x)
}

foreach

theBox.foreach(foo(_))
foo returns Unit
1
2
3
4
5
theBox match {
  case Empty => {}
  case Failure(message, exception, chain) => {}
  case Full(x) => foo(x)
}

pass

theBox.pass(foo(_))
foo takes a Box and returns Unit
1
2
3
4
5
theBox match {
  case Empty => Empty
  case Failure(message, exception, chain) => Failure(message, exception, chain)
  case Full(x) => foo(theBox); theBox
}

filter

theBox.filter(foo(_))
foo returns Boolean
1
2
3
4
5
theBox match {
  case Empty => Empty
  case Failure(message, exception, chain) => Failure(message, exception, chain)
  case Full(x) => if (foo(x)) Full(x) else Empty
}

filterMsg

Returns a Failure with the provided message if the predicate is not met.

theBox.filterMsg(msg)(foo(_))
foo returns Boolean
1
2
3
4
5
theBox match {
  case Empty => Failure(msg, Empty, Empty)
  case Failure(message, exception, chain) => Failure(message, exception, chain)
  case Full(x) => if (foo(x)) Full(x) else Failure(msg, Empty, Empty)
}

filterNot

theBox.filterNot(foo(_))
foo returns Boolean
1
2
3
4
5
theBox match {
  case Empty => Empty
  case Failure(message, exception, chain) => Failure(message, exception, chain)
  case Full(x) => if (!foo(x)) Full(x) else Empty
}

openOr

box openOr(bar)
bar is a value of the same type or a descendant of the Box’s type
1
2
3
4
5
theBox match {
  case Empty => bar
  case Failure(message, exception, chain) => bar
  case Full(x) => x
}

or

theBox.or(bar)
bar is a Box of the same type or a descendant of the Box’s type
1
2
3
4
5
theBox match {
  case Empty => bar
  case Failure(message, exception, chain) => bar
  case Full(x) => Full(x)
}

toOption

Box defines an implicit conversion from Box[T] to Option[T], so you can call Option methods on Boxes if you want. This is how that implicit conversion is defined (you also can call .toOption directly.)

theBox.toOption
1
2
3
4
5
theBox match {
  case Empty => None
  case Failure(message, exception, chain) => None
  case Full(x) => Some(x)
}

toList

Box also defines an implicit conversion to Iterable, so you can call any methods from Iterable that you find useful. Note, though, that if the Box is full .toList will always return a List with one element. If you want to call Iterable methods on the value of a Box[List[Foo]] you probably want to use .elements.

theBox.toList
1
2
3
4
5
theBox match {
  case Empty => Nil
  case Failure(message, exception, chain) => Nil
  case Full(x) => List(x)
}

elements

.elements returns an Iterator over the value of the box. Good for manipulating the contents of a Box[List[Foo]].

theBox.elements
1
2
3
4
5
theBox match {
  case Empty => Iterator.empty
  case Failure(message, exception, chain) => Iterator.empty
  case Full(x) => Iterator(x)
}

isA

theBox.isA[Bar]
Bar is a Class or primitive
1
2
3
4
5
theBox match {
  case Empty => Empty
  case Failure(message, exception, chain) => Empty
  case Full(x) => if (Bar.isAssignableFrom(x.getClass)) Full(value.asInstanceOf[Bar]) else Empty
}

asA

theBox.asA[Bar]
Bar is a Class or primitive
1
2
3
4
5
theBox match {
  case Empty => Empty
  case Failure(message, exception, chain) => Empty
  case Full(x) => if (Full(x).isA[Bar]) then Full(x).asInstanceOf[Box[Bar]] else Empty
}

?~

Useful in for-comprehensions.

box ?~ emptyMsg
emptyMsg is a string
1
2
3
4
5
theBox match {
  case Empty => Failure(emptyMsg, Empty, Empty)
  case Failure(message, exception, chain) => Failure(message, exception, chain)
  case Full(x) => Full(x)
}

?~!

Like ?~, but if the Box is already a Failure it replaces the message and chains the existing Failure.

box ?~! failMsg
emptyMsg is a string
1
2
3
4
5
theBox match {
  case Empty => Failure(failMsg, Empty, Empty)
  case Failure(message, exception, chain) => Failure(failMsg, Empty, Full(box))
  case Full(x) => Full(x)
}

Constructors

from Option

Box(foo: Option[T])
1
2
3
4
foo match {
  case Some(x) => Full(x)
  case None => Empty
}

from List

This returns a Box with the head of the list, if the list isn’t empty.

Box(foo: List[T])
1
2
3
4
foo match {
  case x :: _ => Full(x)
  case Nil => Empty
}

null-safe

This converts null values to Empty, which is very useful when you’re dealing with values from Java code.

Box !! bar
1
2
3
4
bar match {
    case null => Empty
    case _ => Full(bar)
}

EmptyBox

While Box’s advantage over Option is that it distinguishes between the empty case and the failure case, if you don’t care whether a Box is a Failure or an Empty, you can match on EmptyBox. The following code:

1
2
3
4
5
theBox match {
  case Empty => foo
  case Failure(message, exception, chain) => foo
  case Full(x) => bar
}

is equivalent to:

1
2
3
4
theBox match {
  case EmptyBox => foo
  case Full(x) => bar
}

tryo

An insanely useful helper method that catches exceptions and converts them to Failures. You can also pass a list of Exception classes that should be converted to Emptys instead, and/or a callback function that should be triggered if an exception is thrown.

tryo(foo(bar))
foo is a function that can throw an exception
1
2
3
4
5
try {
  Full(foo(bar))
} catch {
  case ex: Failure(ex.getMessage, Full(ex), Empty)
}

Comments