9

我有一个 List[Option[Int]] 并想使用应用函子对其进行求和。从 [1] 我了解到它应该类似于以下内容

import scalaz._
import Scalaz._

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

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

非常感谢

[1]如何在 Scala 中组合 Option 值?

编辑

感谢所有伟大的答案。

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

一些函数会填满我的列表,我想尽可能简单地进一步处理它,而不检查其中一个元素是否为无。它应该与异常类似,我不必在我的函数中检查它,而是让调用者处理它。

4

6 回答 6

15

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

List(Some(1), None, Some(2), Some(3), None).flatten.reduce(_ + _) //returns 6: Int
于 2011-11-16T05:38:35.510 回答
9

如果你有Option[T]并且如果有一个Monoidfor 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)

更新

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

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(_, _))
  }

像这样,您甚至可以对列表列表、树列表进行求和,...

更新2

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

首先optionTIsMonoid,经过重构,不等同于第一个定义,因为第一个定义将跳过None值,而第二个定义将在输入列表中None有 a 时立即返回。None但在那种情况下,这不是一个Monoid!确实,aMonoid[T]必须尊重 Monoid 定律,并且zero必须是等元素。

我们本应该:

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

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

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(_, _))
  }

在这种情况下,我们将(使用Tas 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]]我们可以随意使用,这取决于我们在对列表求和时想要的行为:跳过Nones 或“快速失败”。

于 2011-11-16T05:43:32.993 回答
6
scala> List(1, 2, 3).map(some).foldLeft(0 some) {
     |   case (r, c) => (r |@| c)(_ + _)
     | }
res180: Option[Int] = Some(6)
于 2011-11-16T05:39:40.477 回答
5

一种选择是先对整个列表进行排序,然后像常规一样折叠它:

val a: List[Option[Int]] = List(1, 2, 3) map (Some(_))
a.sequence map (_.foldLeft(0)(_+_))
于 2011-11-16T05:33:20.863 回答
0

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

import scalaz._
import Scalaz._

List(1,2,3).map(_.some).foldl1((acc,v) => (acc |@| v) {_+_}) join
于 2011-11-26T13:51:40.740 回答
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)
        }
    }
  }
于 2016-09-07T10:56:07.353 回答