Question Regarding Functional implementation of ZIP


#1

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.


#2

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…


#3

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.


#4

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.


#5

oh yes, I am aware of that: hehe


#6

thanks for the information.


#7

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).


#8

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))

#9

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.


#10

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:


#11

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