To help with this, I wrote a simple wrapper class that I've called MonadHelper thusly:
object MonadUtil {
implicit def option2wrapper[T](original: Option[T]) = new OptionWrapper(original)
class OptionWrapper[T](original: Option[T]) {
def asTry(throwableOnNone: Throwable) = original match {
case None => Failure(throwableOnNone)
case Some(v) => Success(v)
}
}
}
This allows one to construct a for comprehension elevating None returns to an error state somewhat gracefully like this slightly contrived example:
case class CartItemComposite(account: Tables.AccountRow, item: Item)
trait AccountDAO {
def findById(userId: Long): Option[Tables.AccountRow]
}
trait ItemDAO {
def findById(itemId: Long): Option[Item]
}
def findShoppingCartItem(itemId: Long, userId: Long)(userDAO: AccountDAO, itemDAO: ItemDAO): Try[CartItemComposite] = {
for {
user <- userDAO.findById(userId).asTry(new Throwable("Failed to find user for id " + userId))
item <- itemDAO.findById(itemId).asTry(new Throwable("Failed to find item for id " + itemId))
} yield CartItemComposite(user, item)
}
But you get the idea. You can check a set of conditions for validity, giving appropriate error feedback at each step along the way instead of losing the error meaning as you would with simple Option[T] monads in a way that looks less than insane.
Don't know if this is a great pattern yet, but, I'm giving it a whirl!
No comments:
Post a Comment