如何编写在Scala中返回Option [List]的函数?

时间:2014-03-10 09:58:16

标签: scala monads

假设我有两个功能来获取订单和订单商品:

def getOrders(): Option[List[Int]] = ...
def getOrderItems(orderId: Int): Option[List[Int]] = ...

请注意,两个函数都返回Option[List],因为每个函数都可能失败。

现在,我想获得Option所有订单商品List,如下所示:

    如果两个函数都返回Some[List]和,则
  • 返回Some
  • None如果其中任何一个返回None。

我尝试使用for(见下文)撰写这些功能,但它不起作用。

val allOrderItems = for {
  orderIds   <- getOrders();
  orderId    <- orderIds;
  orderItems <- getOrderItems(orderId)
} yield orderItems

如何使用函数getAllOrderItems():Option[List[Int]]getOrders构建函数getOrderItems

2 个答案:

答案 0 :(得分:6)

你真的希望能够将Option[List[Option[List[Int]]]]的中间两层内部翻出来,这样你就可以获得彼此相邻的选项和列表。此操作称为排序,由Scalaz提供:

import scalaz._, Scalaz._

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

您可以等效地使用traverse,它结合了mapsequence操作:

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

如果您不想使用Scalaz,您可以编写自己的(更少多态)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
}

然后:

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

Monad转换解决方案实际上非常简单(如果您愿意使用Scalaz):

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

这种方法的好处在于你不必考虑你需要压扁,顺序等等 - 普通的monadic操作完全符合你的要求。

答案 1 :(得分:2)

我能想到的最简单的修改如下:

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

for comprehension使用第一个语句来确定其余的类型。例如,在上文中,List[Int]类型将被推测,这与Option[List[Int]]不同。