Rotating args in Haskell and Ruby block style programming
posted by hitesh on 2008-02-12
I was writing some Haskell code and came across a function whose arguments weren't in the order I wanted. With most languages you're stuck, the library writer made a choice and now you have to live with it. But Haskell gives you more options ...
You may have encountered this before, for example with mapM_. The type signature of mapM_ is mapM_ :: (Monad m) => (a -> m b) -> [a] -> m (). But you can use flip on it and turn the type signature into flip mapM_ :: (Monad m) => [a] -> (a -> m b) -> m () which then lets you write code like this.
flip mapM_ [0..10] $ \n -> do
print n
print $ n * 3
Rubyists should see a familiar pattern there that looks a lot like a do/end block.
(0..10).each do |n|
puts n
puts n * 3
end
This is so useful that there is another function already defined that acts like mapM_ with its arguments flipped. It's called forM_ and as you might have guessed, it's type signature is forM_ :: (Monad m) => [a] -> (a -> m b) -> m ().
forM_ [0..10] $ \n -> do
print n
print $ n * 3
The flip function works very well for two argument functions, but what about three arguments or more? I was recently working with zipWithM_ :: (Monad m) => (a -> b -> m c) -> [a] -> [b] -> m (). To use it as is, you'd have to write code like this.
zipWithM_ (\a b -> do { print (a + b); print (a + b * 3) }) [0..10] [4..]
Blecch. Hey, I want my code block at the end so I can work with it like I would in Ruby. I can't use flip here so what can I do? Enter in a new routine we'll call frot, for function rotate.
frot2 = flip
frot3 f b c a = f a b c
So what can we do with frot? Check out this little example.
-- frot2 works just like flip (obviously)
frot2 mapM_ [0..10] $ \n -> do
print n
print $ n * 3
-- original zipWithM_
ghci> :t zipWithM_
zipWithM_ :: (Monad m) => (a -> b -> m c) -> [a] -> [b] -> m ()
-- We can alter zipWithM_ to act like it takes a block
ghci> :t frot3 zipWithM_
frot3 zipWithM_ :: (Monad m) => [a] -> [b] -> (a -> b -> m c) -> m ()
frot3 zipWithM_ [0..10] [4..] $ \a b -> do
print (a + b)
print (a + b * 3)
-- We can chain frot calls to alter the order - put [b] before [a]
ghci> :t frot2 $ frot3 zipWithM_
frot2 $ frot3 zipWithM_ :: (Monad m) => [b] -> [a] -> (a -> b -> m c) -> m ()
frot2 (frot3 zipWithM_) [4..] [0..10] $ \a b -> do
print (a + b)
print (a + b * 3)
-- We can continue rotating args
ghci> :t frot3 $ frot3 zipWithM_
frot3 $ frot3 zipWithM_ :: (Monad m) =>
[b] -> (a -> b -> m c) -> [a] -> m ()
frot3 (frot3 zipWithM_) [4..] (\a b -> do { print (a + b); print (a + b * 3)}) [0..10]
-- If we rotate enough, we're back where we started
ghci> :t frot3 $ frot3 $ frot3 zipWithM_
frot3 $ frot3 $ frot3 zipWithM_ :: (Monad m) =>
(a -> b -> m c) -> [a] -> [b] -> m ()
The best part about this is that frot is not tied to zipWithM_. It can be used with any function. For example, we can alter the findWithDefault function from Data.Map just as easily.
ghci> :t findWithDefault
findWithDefault :: (Ord k) => a -> k -> Map k a -> a
ghci> :t frot3 $ frot3 findWithDefault
frot3 $ frot3 findWithDefault :: (Ord k) => Map k a -> a -> k -> a
-- Now we can partially apply and use
> let ds = fromList [("new england", "patriots"), ("new york", "giants")]
> findTeam = frot3 (frot3 findWithDefault) ds "no team"
> findTeam "peoria"
"no team"
> findTeam "new york"
"giants"
Update Fixed typo in frot3.
Update 2 Part 2 partially explains why this works in Haskell. Obtaining a full understanding is left as an exercise for the reader.
Referrers
- http://search.live.com/results.aspx?q=print
- http://search.live.com/results.aspx?q=print&form=QBHP
- http://www.google.com.tr/search?hl=tr&q=can+frot&meta=
- http://www.google.com/search?q=ruby+block&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:en-US:official&client=firefox-a
- http://del.icio.us/gerd.storm?url=http%3A%2F%2Fwww.jasani.org%2F2008%2F02%2F12%2Frotating-args-in-haskell-and-ruby-block-style-programming&title=Rotating%20args%20in%20Haskell%20and%20Ruby%20block%20style%20programming&v=4
- http://lmsaklatex-lingerielmsak.012webpages.com
- http://www.google.be/search?hl=nl&rls=GGIC,GGIC:1970--2,GGIC:en&sa=X&oi=spell&resnum=0&ct=result&cd=1&q=haskell+rotate+function&spell=1
- http://www.google.ca/search?hl=en&client=firefox-a&rls=org.mozilla:en-US:official&hs=jYn&q=ruby+*args&start=20&sa=N
- http://www.google.ca/search?hl=fr&q=args+haskell&btnG=Rechercher&meta=
- http://www.google.ca/search?q=do+block+Haskell+&btnG=Search&hl=en&client=firefox-a&rls=org.mozilla%3Aen-US%3Aofficial&sa=2
About
Hitesh wrote his first program on a punch card in 1979 ... he's been debugging it ever since.