带 Scalaz 的 ZipList

ZipList with Scalaz

假设我有一个数字列表和函数列表:

val xs: List[Int] = List(1, 2, 3)
val fs: List[Int => Int] = List(f1, f2, f3)

现在我想使用 Applicativef1 应用于 1f2 应用于 2,等等

val ys: List[Int] = xs <*> fs // expect List(f1(1), f2(2), f3(3))

我怎样才能用 Scalaz 做到这一点?

我看到 streamZipApplicative 的解决方案:

import scalaz.std.stream._
import scalaz.Tags

val xs: List[Int] = List(1, 2, 3)
val fs: List[Int => Int] = List(f1, f2, f3)

val zippedLists = streamZipApplicative.ap(Tags.Zip(xs.toStream)) (Tags.Zip(fs.toStream))

val result = Tag.unwrap(zippedLists).toList

pure 用于 zip 列表永远重复该值,因此不可能为 Scala 的 List(或任何类似列表)定义一个 zippy 应用程序实例。 Scalaz 确实为 Stream 提供了一个 Zip 标签和适当的 zippy 应用程序实例,但据我所知它仍然很糟糕。例如,这不起作用(但应该):

import scalaz._, Scalaz._

val xs = Tags.Zip(Stream(1, 2, 3))
val fs = Tags.Zip(Stream[Int => Int](_ + 3, _ + 2, _ + 1))

xs <*> fs

您可以直接使用应用程序实例(如另一个答案),但拥有语法很好,而且编写 "real"(即未标记)包装器并不难。这是我使用的解决方法,例如:

case class ZipList[A](s: Stream[A])

import scalaz._, Scalaz._, Isomorphism._

implicit val zipListApplicative: Applicative[ZipList] =
  new IsomorphismApplicative[ZipList, ({ type L[x] = Stream[x] @@ Tags.Zip })#L] {
    val iso =
      new IsoFunctorTemplate[ZipList, ({ type L[x] = Stream[x] @@ Tags.Zip })#L] {
        def to[A](fa: ZipList[A]) = Tags.Zip(fa.s)
        def from[A](ga: Stream[A] @@ Tags.Zip) = ZipList(Tag.unwrap(ga))
      }
    val G = streamZipApplicative
  }

然后:

scala> val xs = ZipList(Stream(1, 2, 3))
xs: ZipList[Int] = ZipList(Stream(1, ?))

scala> val fs = ZipList(Stream[Int => Int](_ + 10, _ + 11, _ + 12))
fs: ZipList[Int => Int] = ZipList(Stream(<function1>, ?))

scala> xs <*> fs
res0: ZipList[Int] = ZipList(Stream(11, ?))

scala> res0.s.toList
res1: List[Int] = List(11, 13, 15)

就其价值而言,它似乎已在 at least a couple of years 中被破坏。

Learning Scalaz 在他们的 introduction to Applicatives. They quote LYAHFGG:

中用了几段话来讨论这个主题

However, [(+3),(2)] <> [1,2] could also work in such a way that the first function in the left list gets applied to the first value in the right one, the second function gets applied to the second value, and so on. That would result in a list with two values, namely [4,4]. You could look at it as [1 + 3, 2 * 2].

然后补充说:

This can be done in Scalaz, but not easily.

"not easily" 部分使用 streamZipApplicative 就像@n1r3 的回答:

scala> streamZipApplicative.ap(Tags.Zip(Stream(1, 2)))(Tags.Zip(Stream({(_: Int) + 3}, {(_: Int) * 2})))
res32: scala.collection.immutable.Stream[Int] with Object{type Tag = scalaz.Tags.Zip} = Stream(4, ?)

scala> res32.toList
res33: List[Int] = List(4, 4)

"not easily" 是困扰我的部分。我想借用 @Travis Brown 很棒的答案。他正在比较 monad 和 applicatives 的使用(即当你有 monad 时为什么要使用 applicatives?):

Second (and relatedly), it's just a solid development practice to use the least powerful abstraction that will get the job done.

所以,我会说,直到框架提供了一个像您的第一个用例一样工作的应用程序:

val ys: List[Int] = xs <*> fs

要在此处使用 zipmap

xs.zip(fs).map(p=>p._2.apply(p._1))

对我来说,这段代码比 scalaz 中的替代代码更清晰、更简单。这是完成工作的最不强大的抽象。