【问题标题】:How to handle Option of List in Scala?如何在 Scala 中处理 List 的选项?
【发布时间】:2014-02-18 05:51:39
【问题描述】:

假设我有一个函数getCustomersgetOrdersByCustomer

def getCustomer():List[Customer] = ... def getOrdersByCustomer(cust: Customer): List[Order] = ...

现在我可以轻松定义一个函数getOrdersOfAllCustomers

def getOrdersOfAllCustomers(): List[Order] = for(cust

到目前为止,一切都很好,但是如果 getCustomergetOrdersByCustomer 返回列表中的 Options 呢?

def getCustomer():Option[List[Customer]] = ... def getOrdersByCustomer(cust: Customer): Option[List[Order]] = ...

现在我想实现两种不同风格的getOrdersOfAllCustomers()

  • 如果一个函数返回None,则返回None;
  • 如果getCustomer 返回None,则返回None,不关心getOrdersByCustomer 是否返回None。

您建议如何实施?

【问题讨论】:

  • 返回 None 而不是空 List 真的有意义吗?只是好奇,如果你正在设计这个 api。
  • 我想区分错误和空列表。
  • @Michael 但是抛出的错误与空列表无关。对于错误传播,可能是 Either 类型在这里更有用。 Either[Exception, List[Customer]] 有道理。
  • Try 似乎比 EitherOption 更具描述性。
  • @Dylan 我很乐意在另一个问题中讨论 TryEither :))

标签: scala functional-programming monads


【解决方案1】:

我认为您应该考虑三种可能性——填充列表、空列表或错误——并避免进行大量不雅的测试以确定发生了哪一种。

所以使用TryList

def getOrdersOfAllCustomers(): Try[List[Order]] = {
  Try(funtionReturningListOfOrders())
}

如果一切顺利,你会得到一个Success[List[Order]];如果没有,Failure[List[Order]]

这种方法的美妙之处在于无论发生哪种情况(填充列表、空列表或错误),您都可以使用列表执行所有操作。这是因为 Try 是一个单子,就像 Option 一样。继续,filterforEachmap 等随心所欲,而不用关心这三个中的哪一个发生了。

有一件事是尴尬的时刻,你必须弄清楚是成功还是失败。然后使用match 表达式:

getOrdersOfAllCustomers() match {
  case Success(orders) => println(s"Awww...yeah!")
  case Failure(ex) => println(s"Stupid Scala")
}

即使您不使用 Try,我也恳请您不要将空列表与填充列表区别对待。

【讨论】:

  • Try 不是单子。
  • 这与我读过的任何东西都不相符。您能否提供一些参考资料,以便我了解更多信息?
  • 这有点挑剔,但考虑到fn: Unit => Try[Unit] = sys.error("")Success(()).flatMap(fn)fn(()) 不同。
【解决方案2】:

试试这个,

def getOrdersOfAllCustomers(): Option[List[Order]] =
  for{
    cust <- getCustomer().toList.flatten; 
    order <- getOrderByCustomer(cust).toList.flatten
  } yield order

【讨论】:

    【解决方案3】:

    应该这样做:

    def getOrdersOfAllCustomers(): Option[List[Order]] = {
      getCustomer() flatMap { customers =>
        //optOrders is a List[Option[List[Order]]]
        val optOrders = customers map { getOrderByCustomer }
    
        // Any result must be wrapped in an Option because we're flatMapping 
        // the return from the initial getCustomer call
        if(optOrders contains None) None
        else {
          // map the nested Option[List[Order]]] into List[List[Order]]
          // and flatten into a List[Order]
          // This then gives a List[List[Order]] which can be flattened again
          Some(optOrders.map(_.toList.flatten).flatten)
        }
      }
    }
    

    困难的部分是处理getOrderByCustomer 的嵌套调用之一返回None 并将结果冒泡返回外部范围的情况(这就是为什么使用空列表如此更容易)

    【讨论】:

    • 谢谢 :) 但是用自己的 flatMap 定义一个 special monad OptionalList 不是更容易吗?对于我不止一次需要这个逻辑的情况。
    • 更好的是使用原始列表和Nil,而不是将所有内容包装在选项中。 Option 没有语义意义,所以如果你关心错误,那么你应该使用TryEither(或Scalaz 的Validation)并准确指定你想如何处理/聚合错误。
    • 不要定义 OptionalList,而是查看 Scalaz 中的 OptionT/ListT monad 转换器。
    猜你喜欢
    • 1970-01-01
    • 2014-12-21
    • 1970-01-01
    • 2013-12-31
    • 1970-01-01
    • 2021-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多