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