Question Regarding Functional implementation of ZIP

While going thru the “Impossible For A Beginner To Read” book titled ‘Functional Programming In Scala’ by Paul Chiusano and Rúnar Bjarnason
Published by Manning Publications, 2014, I came across this interesting implementation of the “zipAll” function. This one I really find hard to completely understand due to the use of the ‘->’ symbol. Is there a Scala Guru in here that has the time to describe in English how this implementation is working. I can “sort of” understand most of it but I cannot find any explanation of the use of the ‘->’ in the implementation, though thru tracing the code it is sort of apparent. Just wondering if someone can describe its usage here to help enlighten us beginners a little as to its usage. I will post the code below but first need to explain a few things for those that have not attempted to read that book. The author creates an implementation of a Stream object that is used in the implementation of zipAll. So you can take that for granted; Also, Cons is used to construct Streams.

now here is the implementation of zipAll:

 def zipAll[B](s2: Stream[B]): Stream[(Option[A], Option[B])] =
    zipWithAll(s2)((_, _))

  def zipWithAll[B, C](s2: Stream[B])(f: (Option[A], Option[B]) => C): Stream[C] =
    Stream.unfold((this, s2)) {
      case (Empty, Empty)               => None
      case (Cons(h, t), Empty)          => Some(f(Some(h()), Option.empty[B]) -> (t(), empty[B]))
      case (Empty, Cons(h, t))          => Some(f(Option.empty[A], Some(h())) -> (empty[A] -> t()))
      case (Cons(h1, t1), Cons(h2, t2)) => Some(f(Some(h1()), Some(h2())) -> (t1() -> t2()))
    }

My main question is regarding the use of the -> in the code above.

Honestly, this is another case of “you probably shouldn’t be reading this book yet, because you’re still learning the basics”.

-> is a simple operator, built into the standard library, which builds Tuple2s; it is essentially synonymous with a comma. It is typically used in Maps and stuff like that – it’s the Scala-idiomatic way of making up key/value pairs. So:

  a -> b

means precisely the same thing as:

  (a, b)

but is usually used to signify a key/value relationship. It’s almost universally used in Map literals, such as:

  val myMap = Map(1 -> "a", 2 -> "b", 3 -> "c")

It’s basically there to make such things more readable.

As to how they’re intending it in the book, I dunno, but that’s what the operator is. It’s just a common bit of syntax sugar in the libraries…

2 Likes

Not directly related to your question, but might be an useful resource:
https://scalabridge.gitbooks.io/curriculum/content/resources.html

This sorts the resources (books, blogs, and tools, etc) by learning levels. Enjoy learning Scala.

Oh I should have mentioned that. I am aware of how maps work in Scala using that symbol. But the usage here is something a little different than the map usage; This usage appears to magically generating a tuple that is the result of the case expression. Sort of interesting to say the least.

oh yes, I am aware of that: hehe

thanks for the information.

I think I understand what is going on in the zip function now. That use of -> threw me for a little loop but your explanation pointed me in the right direction. However , I am curious as to why the author didn’t just use a “tuple” like he had been doing in the previous examples in Chapter 5. I will investigate further now that I know it is relatively simple semantically speaking. The object is to create a tuple. But there probably is some real reason that the author used a -> instead of ( something, something).

Yes you are 100% correct. The following two sections of code produce identical results:

 def zipAll2[B](s2: Stream[B]): Stream[(Option[A], Option[B])] =
    zipWithAll(s2)((_, _))
    
  def zipAll[B](s2: Stream[B]): Stream[(Option[A], Option[B])] =
    zipWithAll2(s2)((_, _))

  def zipWithAll2[B, C](s2: Stream[B])(f: (Option[A], Option[B]) => C): Stream[C] =
    Stream.unfold((this, s2)) {
      case (Empty, Empty)               => None
      case (Cons(h, t), Empty)          => Some(f(Some(h()), Option.empty[B]) -> (t(), empty[B]))
      case (Empty, Cons(h, t))          => Some(f(Option.empty[A], Some(h())) -> (empty[A] -> t()))
      case (Cons(h1, t1), Cons(h2, t2)) => Some(f(Some(h1()), Some(h2())) -> (t1() -> t2()))
    }
    def zipWithAll[B, C](s2: Stream[B])(f: (Option[A], Option[B]) => C): Stream[C] =
    Stream.unfold((this, s2)) {
      case (Empty, Empty)               => None
      case (Cons(h, t), Empty)          => Some(f(Some(h()), Option.empty[B]) , (t(), empty[B]))
      case (Empty, Cons(h, t))          => Some(f(Option.empty[A], Some(h())) , (empty[A] , t()))
      case (Cons(h1, t1), Cons(h2, t2)) => Some(f(Some(h1()), Some(h2())) , (t1() , t2()))
    }

driver code:
    val list = cons(1, Stream(1, 2, 3, 4))
    val list2 = cons(5, Stream(6, 7, 8))
    println(list.zipAll2[Int](list2) toList)
    println(list.zipAll[Int](list2) toList)

output:
List((Some(1),Some(5)), (Some(1),Some(6)), (Some(2),Some(7)), (Some(3),Some(8)), (Some(4),None))

List((Some(1),Some(5)), (Some(1),Some(6)), (Some(2),Some(7)), (Some(3),Some(8)), (Some(4),None))

I don’t know the authors’ intentions, but I’d guess -> is used for
readability, since it reduces the number of parens needed. I usually choose

Some(a -> b)

instead of

Some((a, b))

FWIW.

1 Like

Perhaps that is the reason. The author usually explains little things like that but this time he forgot to explain to the beginners that happen to be reading the book :slight_smile:

I really need to make a PR with my textbooks on the Scala Bridge page.

2 Likes