How to compose functions that return Option[List] in Scala?

强颜欢笑 提交于 2019-11-30 10:48:04

You really want to be able to turn the middle two layers of Option[List[Option[List[Int]]]] inside out, so that you can get the options and lists next to each other. This operation is called sequencing, and it's provided by Scalaz:

import scalaz._, Scalaz._

val items: Option[List[Int]] =
  getOrders.flatMap(_.map(getOrderItems).sequence).map(_.flatten)

You could equivalently use traverse, which combines the map and sequence operations:

val items: Option[List[Int]] =
  getOrders.flatMap(_ traverse getOrderItems).map(_.flatten)

If you don't want to use Scalaz, you could write your own (less polymorphic) sequence:

def sequence[A](xs: List[Option[A]]) = xs.foldRight(Some(Nil): Option[List[A]]) {
  case (Some(h), Some(t)) => Some(h :: t)
  case _ => None
}

And then:

val items: Option[List[Int]] = getOrders.flatMap(
  orderIds => sequence(orderIds.map(getOrderItems))
).map(_.flatten)

The monad transformation solution is actually pretty straightforward as well (if you're willing to use Scalaz):

val items: Option[List[Int]] = (
  for {
    orderId <- ListT(getOrders)
    itemId  <- ListT(getOrderItems(orderId))
  } yield itemId
).underlying

The nice thing about this approach is that you don't have to think about where you need to flatten, sequence, etc.—the plain old monadic operations do exactly what you want.

The simplest modification I could think of is as below:

for{
    orderId <- getOrders.getOrElse(Nil)
    items <- getOrderItems(orderId)
} yield items

The for comprehension uses the first statement to determins the rest the types. For instance in the above the type List[Int] would be infered and this is different from Option[List[Int]].

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!