【问题标题】:ZipList with Scalaz使用 Scalaz 的 ZipList
【发布时间】:2015-06-22 16:42:08
【问题描述】:

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

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 怎么办?

【问题讨论】:

  • scalaz 有应用程序。
  • 简单地执行一个 zip 后跟一个映射来将函数应用于其配对值有什么问题? xs.zip(fs).map(p=&gt;p._2.apply(p._1))
  • @ChrisScott zipmap 没有任何问题。我刚开始觉得这是实际上 &lt;*&gt;。所以使用&lt;*&gt; 代替zipmap正确的做法。另外我现在一直在学习Applicatives

标签: list scala scalaz applicative


【解决方案1】:

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

import scalaz._, Scalaz._

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

xs <*> fs

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

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 似乎已经被打破了。

【讨论】:

    【解决方案2】:

    我看到了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
    

    【讨论】:

    • 添加特定的导入会改进这个答案,以及添加任何可能的语法改进
    【解决方案3】:

    Learning Scalaz 在他们的introduction to Applicatives 中花费了几段关于这个主题的段落。他们引用LYAHFGG

    但是,[(+3),(2)] > [1,2] 也可以这样工作,即左侧列表中的第一个函数应用于第一个值在右侧,第二个函数应用于第二个值,依此类推。这将产生一个包含两个值的列表,即 [4,4]。您可以将其视为 [1 + 3, 2 * 2]。

    然后补充:

    这可以在 Scalaz 中完成,但并不容易。

    “不容易”部分使用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)
    

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

    其次(和相关),使用最不强大的抽象来完成工作只是一种可靠的开发实践。

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

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

    在此处使用zipmap

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

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

    【讨论】:

    • 感谢您的回答,但我认为mapzip 不是least powerful abstraction。它们在我看来只是低级,而不是不那么强大。
    • 我不同意你的观点,但我认为较低级别的抽象更干净,特别是如果您必须跳过障碍才能使较高级别的抽象工作。
    • 我对此表示怀疑。如果我需要重复mapzip 解决方案几十次 次,我可能更愿意一次 获得更高级别的抽象工作,然后重新使用它几十次。
    • 我想我们在简单性和更高级别的抽象栅栏的不同方面。无论如何,谢谢你的问题,这是很好的思考。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-08-09
    • 1970-01-01
    • 2022-01-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多