evan_tech

Previous Entry Share Next Entry
12:54 pm, 28 Jun 06

with_index

_why has a hack to generalize _with_index from Ruby, but it's quite complicated.

Background: many Ruby iterators have a _with_index flavor that gives you each value along with its index in the collection. For example, you can do
array.each do { |element| ... }
or you can do
array.each_with_index do { |element, index| ... }
but it's always felt a bit lacking that you have to implement both versions when you're writing code. (For example, I've needed map_with_index in a few programs so I've implemented it myself.)

Whenever I see these sorts of complicated hacks, I think to myself, "transforming code? that's functional!" The Ruby code there does crazy stuff like creating method names from strings and sticking instances variables into a block. Here's a more sane implementation in Haskell.

So, for example, I can take code like:
map (\char -> toUpper char) ['a', 'b', 'c']
and when I need the index of each element it becomes
map (\(char,index) -> some_code_using_char_and_index) (with_index ['a', 'b', 'c'])

The magic "with_index" code is simple enough that it's almost not worth including:
with_index :: [a] -> [(a, Int)]
with_index l = zip l [0..]


Laziness makes many "iterator" patterns nearly transparent in Haskell, because you don't have this tension between "do I create a data structure and return the whole thing?" or "do I expose an interface to get each element sequentially?" as they're both implemented the same way. (It's not completely magical, of course; sometimes IO complicates things.)

(Rubyists will probably feel my pain when they see the following snippet. I feel like I've written code like this a million times, where "foo", "bar", and "baz" depend on the situation:
def foo
  ret = []
  bar.baz { |x| ret << x }
  ret
end
)