【问题标题】:Binary operator with Option arguments带选项参数的二元运算符
【发布时间】:2012-05-23 23:35:01
【问题描述】:

在 scala 中,如何定义两个 Option 参数的加法?具体来说,假设它们是 Int 类型的包装器(我实际上正在使用双精度图,但这个示例更简单)。

我尝试了以下方法,但它只是给了我一个错误:

  def addOpt(a:Option[Int], b:Option[Int]) = {
    a match {
      case Some(x) => x.get
      case None => 0
    } + b match {
      case Some(y) => y.get
      case None => 0
    }
  }

编辑添加:

在我的实际问题中,我添加了两张代表稀疏向量的地图。所以 None 情况返回 Map[Int, Double] 而 + 实际上是一个 ++(在 stackoverflow.com/a/7080321/614684 进行了调整)

【问题讨论】:

  • 您没有以正确的方式提取选项的内容。当您匹配大小写 Some(x) 时,x 是选项内的值(类型 Int),并且您不调用 get 。案例一些(x)=> x。无论如何,如果你想要内容或默认,a.getOrElse(0) 更方便。
  • @didierd 谢谢!这就是我需要的答案。你能转换成答案吗?我会选择你的。

标签: scala


【解决方案1】:

类群

当您意识到您可以站在巨人的肩膀上并利用常见的抽象和为使用它们而构建的库时,您可能会发现生活变得更加轻松。为此,这个问题基本上是关于处理 monoids(有关此问题的更多信息,请参阅下面的相关问题),相关库称为 scalaz

使用 scalaz FP,这只是:

def add(a: Option[Int], b: Option[Int]) = ~(a |+| b)

更重要的是,这适用于任何幺半群 M:

def add[M: Monoid](a: Option[M], b: Option[M]) = ~(a |+| b)

更有用的是,它适用于放置在 Foldable 容器中的任意数量的它们:

def add[M: Monoid, F: Foldable](as: F[Option[M]]) = ~as.asMA.sum

请注意,除了显而易见的 IntStringBoolean 之外,还有一些相当有用的幺半群:

  1. Map[A, B: Monoid]
  2. A => (B: Monoid)
  3. Option[A: Monoid]

事实上,提取自己的方法几乎不值得:

scala> some(some(some(1))) #:: some(some(some(2))) #:: Stream.empty
res0: scala.collection.immutable.Stream[Option[Option[Option[Int]]]] = Stream(Some(Some(Some(1))), ?)

scala> ~res0.asMA.sum
res1: Option[Option[Int]] = Some(Some(3))

一些相关问题

问。什么是幺半群?

一个幺半群是一个类型M,它存在一个关联二元运算(M, M) => M和一个标识I,因此mplus(m, I) == m == mplus(I, m)对于所有m类型的M

问。什么是|+|

这只是 mplus 二进制操作的 scalaz 简写(或 ASCII 疯狂,ymmv)

问。什么是~

如果M 是一个幺半群,它是一个一元运算符,意思是“或身份”,由 scalaz 库改造(使用 scala 的隐式转换)到Option[M]。显然,非空选项返回其内容;一个空选项被幺半群的身份替换。

问。什么是asMA.sum

Foldable 基本上是一个可以折叠的数据结构(例如foldLeft)。回想一下foldLeft 采用一个种子值和一个操作来组成连续的计算。在对幺半群求和的情况下,种子值为身份I,运算为mplus。因此,您可以在Foldable[M : Monoid] 上致电asMA.sum。您可能需要使用 asMA,因为名称与标准库的 sum 方法冲突。

一些参考资料

  • SlidesVideo 的演讲中我给出了在野外使用幺半群的实际示例

【讨论】:

  • ~ 是做什么的?我认为你应该更好地解释成分。
  • 对于我们这些使用 scalaz 7 的人来说,我相信相当于 ~res0.suml
  • 谢谢!这很有帮助。我会和@didird一起去查明斯卡拉抱怨的原因。
  • @JasonMond 仔细看看这个幺半群东西,它很有用,我保证!有人应该在类路径上设置一个带有 scalaz 的“simplyscala”,以便于制作原型
  • 我认为~ 应该是“或零”而不是“或身份”。
【解决方案2】:
def addOpts(xs: Option[Int]*) = xs.flatten.sum

这适用于任意数量的输入。

【讨论】:

  • FWIW,为Map[Int, Double] 工作,它只是def addOpts(xs: Option[Map[Int, Double]]*) = xs.flatten.foldLeft(Map.empty[Int, Double]) { _ ++ _ }
  • 确实如此。或者同时适用于两者的 scalaz 版本:def addOpts[T:Monoid](xs: Option[T]*) = xs.flatten.asMA.sum
【解决方案3】:

如果它们都默认为 0,则不需要模式匹配:

  def addOpt(a:Option[Int], b:Option[Int]) = {
    a.getOrElse(0) + b.getOrElse(0)
  }

【讨论】:

  • 在我的实际问题中,他们没有。我添加了两张代表稀疏向量的地图。
  • 如果两者都没有默认值,则在这种情况下不能使用 Option,因为其中一个或两个都可能未定义。
  • 如果我让它们默认为 Map[Int, Double] (实际上是它们),而 + 实际上是 ++(在 stackoverflow.com/a/7080321/614684 处进行了调整)。
【解决方案4】:

(根据要求在答案中重复上述评论)

您没有以正确的方式提取选项的内容。当您与case Some(x) 匹配时,x 是选项内的值(类型为Int),并且您不调用get。做吧

case Some(x) => x 

无论如何,如果你想要内容或默认,a.getOrElse(0) 更方便

【讨论】:

    【解决方案5】:
    def addOpt(ao: Option[Int], bo: Option[Int]) =
        for {
            a <- ao
            b <- bo
        } yield a + b
    

    【讨论】:

    • 这将返回 Option[Int] 而不是 Int。此外,如果aoboNone,它返回None,这不是所需的结果。
    • 你是对的,它不会产生 OP 代码的预期结果,但我想说它确实解决了问题标题的精神。
    • 虽然它不像原始答案那样返回 0,但只需添加 getOrElse 就可以了
    猜你喜欢
    • 2021-10-07
    • 2018-08-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-24
    • 2016-07-23
    • 1970-01-01
    相关资源
    最近更新 更多