this post was originally published on my Github Pages site on September 19th, 2017
Now that we have covered the fundamental iterative functions, I want to take some time to look at some more functions that you will typically find lurking in functional-first programming languages or functional utility libraries. While not as prevalent as map, filter, and fold, these functions have their use cases and though they can be easily implemented by way of fold, they are more purpose built so you will find the syntax to be tidier.
The first of these functions I want to talk about is the convolution function, more widely known as zip. In Haskell, the zip function has a type signature of zip :: [a] -> [b] -> [(a, b)]
. What this means is that the zip function accepts two lists, and combines them into a single list by merging each value of each list. Traditionally, zip outputs a tuple (the (a, b)
part of the type signature), however, since JavaScript doesn't have tuples (nor a zip function) we will use a two-value array to represent pairs and we will define the zip function ourselves.
We'll start off by defining zip as a function of map.
Note that this is not a battle-proven production-ready solution. This is just to get us going without worrying about external libraries. I would recommend looking into Underscore.js, Lodash, or Ramda for tested and well-designed implementations of zip. In the wild, zip will usually only map over the shortest length array so that your output array isn't longer than the shortest input array.
So what can we do with this? As usual, we'll start off with a very basic example.
And just like that, we've constructed the discrete sequence of values for
f(x) = x2
where { 0 <= x <= 10 }
. Pretty cool, eh? Now we can send that off to Chart.js or the like and get back a nice chart.
Because of the flexible type signature (read: non-existent) of our zip implementation, the return value can be an array of anything. It's an array by default, but in the second zip above we constructed some objects from our pairs. If you made it through map and fold, this should make sense to you right away (because you are a higher-order programmer now!), but I will show this same example done in an imperative style just in case.
As long as you know your two arrays match up one-to-one, you can use zip to compose objects of sublists into incrementally larger objects. Though there are varying opinions on the subject, I tend to prefer composing objects rather than inheriting and I think MPJ at Fun Fun Function makes a solid case for this idea as well. So how would we do this with zip? Well lets take our previous example where we took our x-values and y-values and combined them into an object, and lets take these boring 2-dimensional points and turn them into boring 3-dimensional points. We'll then consider these points to represent the directed endpoints of 3-dimensional vectors from the origin and calculate their magnitude.
Zip gives us a concise syntax for doing a good chunk of work. What if we want to take a list of two vectors and calculate their dot product and resultant vector?
Very easy. Note that since I'm such a smart guy I accidentally made the original arrays have more even-indexed members than odd, that's why I passed in the odds first. This is an example of something a good implementation of zip would do automatically. If I were to have passed the evens in as the first argument, we would have gotten an error since we are accessing the items in the second array by index.
Where should I use zip?
When you want to combine two or more lists with a one-to-one correspondence. Note that since zip combines things in some way, the result of consecutive zips usually ends in progressively larger, more complex items. Also, like all of the iterative functions I've covered so far, zip does not mutate the original arrays. This means that you can put some stuff in an array, zip it, and the original stuff will still be there untouched.
Which languages have zip?
The language you use probably has it so by sure to check the owner's manual. As far as I know, the following languages and libraries have zip:
Language | Function | Note |
---|---|---|
C# | Enumerable.Zip | |
Haskell | zip | |
JavaScript (Underscore.js) | _.zip | Be sure to check the docs on this. This implementation doesn't appear to accept a "zipping" function |
JavaScript (lodash) | _.zip | Be sure to check the docs on this. This implementation doesn't appear to accept a "zipping" function |
JavaScript (lodash) | _.zipWith | |
JavaScript (Ramda) | R.zip | Be sure to check the docs on this. This implementation doesn't appear to accept a "zipping" function |
JavaScript (Ramda) | R.zipWith | |
JavaScript (Immutable) | zip | Be sure to check the docs on this. This implementation doesn't appear to accept a "zipping" function |
JavaScript (Immutable) | zipWith | |
Python | zip | Be sure to check the docs on this. This implementation doesn't appear to accept a "zipping" function |
Top comments (3)
F# has List.zip for immutable lists, Seq.zip for sequences (the .NET
IEnumerable
type), and Array.zip for arrays.Swift also has a zip function for objects that implement
Sequence
protocol.Great article, zipWith could be very handy!