如何在Scala中组合返回Option[List]的函数?

3
假设我有两个函数用于获取订单和订单项:
``` def getOrders(): Option[List[Int]] = ... def getOrderItems(orderId: Int): Option[List[Int]] = ... ```
请注意,这两个函数都返回 `Option[List]`,因为每个函数可能会失败。
现在我想要按如下方式获得所有订单项的 `Option` 列表:
- 如果两个函数都返回 `Some`,则返回 `Some[List]` - 如果其中任何一个返回 `None`,则返回 `None`
我尝试使用 `for` 组合这些函数(见下文),但并没有成功。
``` val allOrderItems = for { orderIds <- getOrders(); orderId <- orderIds; orderItems <- getOrderItems(orderId) } yield orderItems ```
如何使用函数 `getOrders` 和 `getOrderItems` 构建一个函数 `getAllOrderItems():Option[List[Int]]`?

你说的“它没起作用”是什么意思?你是否遇到了错误,或者得到了与预期不同的结果?你原本期望的是什么,它与实际情况有何不同? - Jesper
1
Option和List单子不兼容。您需要使用单子转换器,或在Options上调用toList(或使用适当类型的breakOut也可以起作用)。 - Gábor Bakos
@GáborBakos 谢谢。monad transformer 看起来是一个高级话题。此外,我需要 scalaz 来实现它。您如何建议使用 breakOut 实现它? - Michael
在这种情况下,可能只需在调用getOrder()后调用toList,例如getOrder().toList。我不确定breakOut是否可在此处使用,但它是为类似用例而设计的。(尽管在这种情况下,使用toList似乎更容易。) - Gábor Bakos
@GáborBakos 谢谢。toList 看起来真的很简单。 - Michael
2个回答

6

您真正想要的是能够将Option [List [Option [List [Int]]]]的中间两层反转,以便您可以将选项和列表放在一起。这个操作称为序列化,并由Scalaz提供:

import scalaz._, Scalaz._

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

您可以等效地使用,它结合了和操作:
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)

单子转换解决方案实际上也很简单(如果你愿意使用Scalaz):
val items: Option[List[Int]] = (
  for {
    orderId <- ListT(getOrders)
    itemId  <- ListT(getOrderItems(orderId))
  } yield itemId
).underlying

这种方法的好处在于您无需考虑何时需要展平、排序等操作——普通的单子操作正好符合您的要求。


谢谢!我喜欢ListT,但我不是很理解它的工作原理。为了理解它,我应该定义自己的单子OptionalList,用它来解决那个“order”练习,然后将其泛化,使其适用于任何M[_]而不仅仅是Option。所以我会尝试编写OptionalList并发布一个相关问题。听起来像个计划 :) - Michael
ListT很不错-我强烈推荐尝试一下实现。如果遇到问题,您知道在哪里提问- - Travis Brown

2
我能提供的最简单修改如下所示:
for{
    orderId <- getOrders.getOrElse(Nil)
    items <- getOrderItems(orderId)
} yield items
for 循环使用第一条语句来确定其余语句的类型。例如,在上面的例子中,将会推断出类型 List[Int],这与 Option[List[Int]] 不同。

1
正如您所指出的,这与规格不符。 - Travis Brown

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接