ZipList with Scalaz

喜夏-厌秋 提交于 2020-01-04 19:54:03

问题


Suppose I have a list of numbers and list of functions:

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

Now I would like to use an Applicative to apply f1 to 1, f2 to 2, etc.

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

How can I do it with Scalaz ?


回答1:


pure for zip lists repeats the value forever, so it's not possible to define a zippy applicative instance for Scala's List (or for anything like lists). Scalaz does provide a Zip tag for Stream and the appropriate zippy applicative instance, but as far as I know it's still pretty broken. For example, this won't work (but should):

import scalaz._, Scalaz._

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

xs <*> fs

You can use the applicative instance directly (as in the other answer), but it's nice to have the syntax, and it's not too hard to write a "real" (i.e. not tagged) wrapper. Here's the workaround I've used, for example:

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
  }

And then:

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)

For what it's worth, it looks like this has been broken for at least a couple of years.




回答2:


I see a solution with 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 spends a few paragraphs on this topic in their 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].

But then adds:

This can be done in Scalaz, but not easily.

The "not easily" part uses streamZipApplicative like in @n1r3's answer:

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)

The "not easily" is the part that bothers me. I'd like to borrow from @Travis Brown fantastic answer. He is comparing the use of monads and applicatives (i.e. why use applicatives when you have a monad?):

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

So, I would say that until a framework provides an applicative that works like your first use-case:

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

To use zip and map here instead:

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

To me, this code is much clearer and simpler than the alternatives in scalaz. This is the least powerful abstraction that gets the job done.



来源:https://stackoverflow.com/questions/30985397/ziplist-with-scalaz

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!