【问题标题】:How to compose functions that return Option[List] in Scala?如何在 Scala 中编写返回 Option[List] 的函数?
【发布时间】:2014-04-13 08:48:29
【问题描述】:

假设我有两个函数来获取订单和订购商品:

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

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

现在我想得到Option of List 的所有订单项目如下:

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

我尝试用for(见下文)组合这些函数,但没有成功。

val allOrderItems = for { orderIds

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

【问题讨论】:

  • “它不起作用”是什么意思?您是否收到错误或与预期不同的结果?您的预期是什么?这与实际情况有何不同?
  • Option 和 List monad 不兼容。您需要使用 monad 转换器,或在 Options 上调用 toList (或使用适当类型的 breakOut 也可以工作)。
  • @GáborBakos 谢谢。 monad transformer 看起来像是一个高级主题。此外,我需要scalaz。您如何建议使用 breakOut 来做到这一点?
  • @Michael 在这种情况下,可能只会在 getOrder() 之后调用 toList,例如 getOrder().toList。我不确定 breakOut 是否可以在这里使用,但它是为类似的用例设计的。 (虽然在这种情况下 toList 似乎更容易。)
  • @GáborBakos 谢谢。 toList 看起来很简单。

标签: scala monads


【解决方案1】:

您真的希望能够将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

这种方法的好处是您不必考虑需要在哪里进行展平、排序等 - 普通的旧单子操作完全符合您的要求。

【讨论】:

  • 谢谢!我喜欢ListT,但我不太明白它是如何工作的。为了理解它,我应该定义自己的 monad OptionalList,用它解决“顺序”练习,然后对其进行概括,使其适用于任何 M[_],而不是仅适用于 Option。所以我会尝试写OptionalList 并发布一个关于它的问题。听起来像一个计划:)
  • ListT 非常简洁——我强烈建议尝试一下实现。如果你遇到问题,你知道在哪里提问——
【解决方案2】:

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

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

for 推导式使用第一条语句来确定其余的类型。例如,在上面会推断出 List[Int] 类型,这与 Option[List[Int]] 不同。

【讨论】:

  • 如您所见,这与规范不符。
猜你喜欢
  • 1970-01-01
  • 2012-01-10
  • 1970-01-01
  • 1970-01-01
  • 2016-07-02
  • 1970-01-01
  • 1970-01-01
  • 2021-08-18
  • 1970-01-01
相关资源
最近更新 更多