[英]Summing a List of Options with Applicative Functors
我有一个List [Option [Int]]并希望使用applicative functors来总结它。 从[1]我明白它应该是类似下面的东西
import scalaz._
import Scalaz._
List(1,2,3).map(some(_)).foldLeft(some(0))({
case (acc,value) => (acc <|*|> value){_+_}
})
但是我根本无法弄清楚写这个的正确方法。 如果有人可以帮助我,我会很高兴。
非常感谢你
[1] 如何在Scala中组合选项值?
编辑
感谢所有的好答案。
如果列表中有任何None,我希望它返回None。 我试图用Option / Either替换Null / Exception,看看我是否可以生成一些可用的代码。
一些函数将填充我的列表,我想尽可能简单地处理它,而不检查其中一个元素是否为None。 它应该像Exceptions类似,我不需要在我的函数中检查它,但让调用者处理它。
你真的不需要Scalaz。 您可以将列表展平,将其转换为List[Int]
,删除任何None
项目。 然后你可以减少它:
List(Some(1), None, Some(2), Some(3), None).flatten.reduce(_ + _) //returns 6: Int
如果你有Option[T]
并且如果有一个Monoid
for T
,那么就有一个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)
UPDATE
我们实际上可以使用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
问题澄清让我意识到上面的更新是不正确的!
首先, optionTIsMonoid
,作为重构,不等同于第一个定义,因为第一个定义将跳过None
值,而第二个定义将在输入列表中只有None
时返回None
。 但在那种情况下,这不是一个Monoid
! 实际上, Monoid[T]
必须遵守Monoid定律, zero
必须是一个恒等元素。
我们本应该:
zero |+| Some(a) = Some(a)
Some(a) |+| zero = Some(a)
但是当我使用Applicative
for Option
提出Monoid[Option[T]]
的定义时,情况并非如此:
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
这证明了身份法得到了验证(我们还应该验证相关法律是否得到尊重,......)。
现在我们有2个Monoid[Option[T]]
,我们可以随意使用,具体取决于我们在对列表求和时所需的行为:跳过None
s或“快速失败”。
scala> List(1, 2, 3).map(some).foldLeft(0 some) {
| case (r, c) => (r |@| c)(_ + _)
| }
res180: Option[Int] = Some(6)
一种选择是首先对整个列表进行排序,然后像常规一样将其折叠:
val a: List[Option[Int]] = List(1, 2, 3) map (Some(_))
a.sequence map (_.foldLeft(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)
}
}
}
使用Scalaz的ApplicativeBuilder将是另一种选择。
import scalaz._
import Scalaz._
List(1,2,3).map(_.some).foldl1((acc,v) => (acc |@| v) {_+_}) join
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.