用应用函数总结选项列表

Man*_*idt 9 scala scalaz

我有一个List [Option [Int]]并希望使用applicative functors来总结它.从[1]我明白它应该是类似下面的东西

import scalaz._
import Scalaz._

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

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

非常感谢你

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

编辑

感谢所有的好答案.

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

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

Dan*_*mon 15

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

List(Some(1), None, Some(2), Some(3), None).flatten.reduce(_ + _) //returns 6: Int
Run Code Online (Sandbox Code Playgroud)

  • 或者`.flatten.sum`. (6认同)

Eri*_*ric 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
  }
}
Run Code Online (Sandbox Code Playgroud)

一旦你配备了这个,你可以使用sum(优于foldMap(identity)@missingfaktor建议):

 List(Some(1), None, Some(2), Some(3), None).asMA.sum === Some(6)
Run Code Online (Sandbox Code Playgroud)

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(_, _))
}
Run Code Online (Sandbox Code Playgroud)

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

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(_, _))
  }
Run Code Online (Sandbox Code Playgroud)

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

UPDATE2

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

首先optionTIsMonoid,作为重构,不等同于第一个定义,因为第一个定义将跳过None值,而第二个定义将在输入列表中None出现a时立即返回None.但在那种情况下,这不是一个Monoid!实际上,Monoid[T]必须尊重Monoid法则,并且zero必须是一个身份元素.

我们本应该:

zero    |+| Some(a) = Some(a)
Some(a) |+| zero    = Some(a)
Run Code Online (Sandbox Code Playgroud)

但是当我提出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
Run Code Online (Sandbox Code Playgroud)

修复并不难,我们需要更改定义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(_, _))
  }
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我们将(与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
Run Code Online (Sandbox Code Playgroud)

这证明了身份法得到了验证(我们还应该验证相关法律是否得到尊重,......).

现在我们Monoid[Option[T]]可以随意使用2 ,这取决于我在总结列表时所需的行为:跳过Nones或"快速失败".

  • `+`是由`T`的'Monoid`这一事实提供的.如果`T`是`Int`,Scalaz提供了一个`Monoid [Int]`,其中`append`操作是用`+`定义的.所以我的解决方案的优点是它更通用,因为它扩展到任何具有"类似添加"操作的东西. (2认同)

mis*_*tor 6

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


Owe*_*wen 5

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

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

  • 或者,确实只是a.sequence map {_.sum} (2认同)