用应用函数总结选项列表

时间:2011-11-16 05:11:54

标签: scala scalaz

我有一个List [Option [Int]],想要使用applicative functor来总结它。 从[1]我明白它应该类似于以下

import scalaz._
import Scalaz._

List(1,2,3).map(some(_)).foldLeft(some(0))({
    case (acc,value) => (acc <|*|> value){_+_}
})

然而,我根本无法弄清楚写这个的正确方法。 如果有人可以帮助我,我会很高兴。

非常感谢

[1] How to combine Option values in Scala?

修改

感谢所有出色的答案。

如果列表中有None,我希望它返回None。 我试图用Option / Either替换Null / Exception,看看我是否可以生成一些可用的代码。

某些功能将填充我的列表,我希望尽可能简单地处理它,而不检查其中一个元素是否为None。它应该像Exceptions类似,我不需要在我的函数中检查它,但让调用者处理它。

6 个答案:

答案 0 :(得分:15)

你真的不需要Scalaz。您可以展平列表,将其转换为List[Int],删除None项。然后你可以减少它:

List(Some(1), None, Some(2), Some(3), None).flatten.reduce(_ + _) //returns 6: Int

答案 1 :(得分:9)

如果你有Option[T],如果MonoidT,那么Monoid[Option[T]]

implicit def optionTIsMonoid[T : Monoid]: Monoid[Option[T]] = new Monoid[Option[T]] {
  val monoid = implicitly[Monoid[T]]
  val zero = None
  def append(o1: Option[T], o2: =>Option[T]) = (o1, o2) match {
    case (Some(a), Some(b)) => Some(monoid.append(a, b))
    case (Some(a), _)       => o1
    case (_, Some(b))       => o2
    case _                  => zero
  }
}

配备完毕后,您可以使用sum(优于foldMap(identity),正如@missingfaktor所建议的那样):

 List(Some(1), None, Some(2), Some(3), None).asMA.sum === Some(6)

<强>更新

我们实际上可以使用applicative来简化上面的代码:

implicit def optionTIsMonoid[T : Monoid]: Monoid[Option[T]] = new Monoid[Option[T]] {
   val monoid = implicitly[Monoid[T]]
   val zero = None
   def append(o1: Option[T], o2: =>Option[T]) = (o1 |@| o2)(monoid.append(_, _))
}

这让我觉得我们甚至可以进一步概括为:

implicit def applicativeOfMonoidIsMonoid[F[_] : Applicative, T : Monoid]: Monoid[F[T]] = 
  new Monoid[F[T]] {
    val applic = implicitly[Applicative[F]]
    val monoid = implicitly[Monoid[T]]

    val zero = applic.point(monoid.zero)
    def append(o1: F[T], o2: =>F[T]) = (o1 |@| o2)(monoid.append(_, _))
  }

就像那样你甚至可以将列表列表,树木列表,...

加起来

<强> UPDATE2

问题澄清让我意识到上面的 UPDATE 不正确!

首先,optionTIsMonoid作为重构,不等同于第一个定义,因为第一个定义将跳过None值,而第二个定义将在{&1}}后立即返回None #39;输入列表中的sa None。但在这种情况下,这不是Monoid!实际上,Monoid[T]必须遵守Monoid法则,zero必须是identity元素。

我们应该:

zero    |+| Some(a) = Some(a)
Some(a) |+| zero    = Some(a)

但是当我使用Monoid[Option[T]] ApplicativeOption提出定义时,情况并非如此:

None    |+| Some(a) = None
None    |+| None    = None
=> zero |+| a      != a

Some(a) |+| None    = zero
None    |+| None    = zero
=> a    |+| zero   != a

修复并不难,我们需要更改zero的定义:

// the definition is renamed for clarity
implicit def optionTIsFailFastMonoid[T : Monoid]: Monoid[Option[T]] = 
  new Monoid[Option[T]] {
    monoid = implicitly[Monoid[T]]
    val zero = Some(monoid.zero)
    append(o1: Option[T], o2: =>Option[T]) = (o1 |@| o2)(monoid.append(_, _))
  }

在这种情况下,我们将{T作为Int):

Some(0) |+| Some(i) = Some(i)
Some(0) |+| None    = None
=> zero |+| a       = a

Some(i) |+| Some(0) = Some(i)
None    |+| Some(0) = None
=> a    |+| zero    = zero

证明身份法已经过验证(我们还应该验证associative法律是否得到尊重,......)。

现在我们可以随意使用2 Monoid[Option[T]],具体取决于我们在汇总列表时所需的行为:跳过None s或&#34;快速失败&#34;。

答案 2 :(得分:6)

scala> List(1, 2, 3).map(some).foldLeft(0 some) {
     |   case (r, c) => (r |@| c)(_ + _)
     | }
res180: Option[Int] = Some(6)

答案 3 :(得分:5)

一种选择是首先对整个列表进行排序,然后像普通列表一样折叠:

val a: List[Option[Int]] = List(1, 2, 3) map (Some(_))
a.sequence map (_.foldLeft(0)(_+_))

答案 4 :(得分:0)

使用Scalaz的ApplicativeBuilder将是另一种选择。

import scalaz._
import Scalaz._

List(1,2,3).map(_.some).foldl1((acc,v) => (acc |@| v) {_+_}) join

答案 5 :(得分:0)

不久之前发现这个地方,找不到来源了,但它一直在为我工作

 def sumOpt1(lst: List[Option[Int]]): Option[Int] = {
    lst.foldLeft(Option.empty[Int]) {
      case (prev, elem) =>
        (prev, elem) match {
          case (None, None) => None
          case (None, Some(el)) => Some(el)
          case (Some(p), None) => Some(p)
          case (Some(p), Some(el)) => Some(p + el)
        }
    }
  }

 def sumOpt2(lst: List[Option[Int]]): Option[Int] = {
    lst.foldLeft(Option.empty[Int]) {
      case (prev, elem) =>
        (prev, elem) match {
          case (None, None) => None
          case (p, el) => Some(p.getOrElse(0) + el.getOrElse(0))
        }
    }
  }

def sumOpt3(lst: List[Option[Int]]): Option[Int] = {
    lst.foldLeft(Option.empty[Int]) {
      case (prev, elem) =>
        (prev, elem) match {
          case (None, el) => el
          case (p, None) => p
          case (Some(p), Some(el)) => Some(p + el)
        }
    }
  }