Monday, August 4, 2008

Two-dimensional zip

I've been doing battle with things like CSS recently (the W3C will be first against the wall when the revolution comes,) so it was a pleasure to run into a little Haskell puzzle and an elegant solution.

I was working with a matrix represented with a list of lists: [[a]]. I wanted to add the row and column indices to each element, giving: [[((Integer,Integer),a)]].

This isn't a pattern I remember seeing before, but it seemed simple enough, so I just hacked away. Here's my first try:

Serviceable, but neither pretty nor general. The one dimensional case is so elegant; for any list, you can add indices with:

> index1d xs = zip [0..] xs

The zip function is simple and the infinite list of indices covers all cases. So it occurred to me I was looking for two things: a two-dimensional version of zip; and a grid of index tuples, semi-infinite in both directions.

Both turned out to be simple enough. Here's the grid of tuples using list comprehensions:

> indices2d = [[(r,c) | c And the two-dimensional zip:

> zip2d = zipWith zip

And it's easy to reproduce the "With" version:

> zip2dWith f = zipWith (zipWith f)

So now my function looks like this:

> index2d grid = zip2d indices2d grid

or just:

> index2d' = zip2d indices2d

This version is clearer, simpler and easily generalizable to higher dimensions.

Of course this relates to zipping being an applicative functor. So the implementation for zip2DWith is just the applicative functor instance for ZipList :. ZipList (where (:.) is from TypeCompose), zip2DWith 3 is just liftA3 on that functor, etc.

I thought about exploring the point-free forms some more but decided it was time to post. Thanks for the TypeCompose observation as well. I haven't read that code since it first came out; clearly time to catch up. I was thinking that Conal must have done something similar to this zip in his Image work. I remember he was thinking about how to make the sets infinite in both directions.