ZipList with Scalaz

420 views Asked by At

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 ?

3

There are 3 answers

0
Travis Brown On BEST ANSWER

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.

1
n1r3 On

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
4
Chris Scott On

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.