do 式中の無名再帰でループを回す

do 式の中でループを書く場合,イチイチ let を使うのは面倒だなぁ,
と常々考えていたのですが,よく考えたら これ, fix を使えば済む話ですね:

import Data.IORef
import Data.Function

main = do
  -- 無引数無名再帰
  a_ref <- newIORef ( 0 :: Int )
  fix $ \loop -> do
    a <- readIORef a_ref
    if a < 10 then do
      print a
      writeIORef a_ref (a+1)
      loop
     else
      return ()
  
  -- 引数付き無名再帰
  sum <- newIORef ( 0 :: Int )
  ( `fix` 0 ) $ \ loop i -> do
    if i < 10 then do
      modifyIORef sum(+ i)
      loop $ i + 1
     else
      return ()
  print =<< readIORef sum


IORef と組み合わせれば かなり手続き的に書けて,個人的に大満足です.
まぁ,継続モナドを使う方が楽なケースも多いですが.


あ,継続モナドに関しての詳しい解説は, Haskell Advent Calendar 2011 の僕の番の時にでも.

追記

当たり前ですが,今回のような例では

main = do
  forM_ [0..9] $ \i -> do
    print i
  
  print $ sum [0..9]

と書けば それで済む話ですし,そう書いた方がエレガントです.


ただ,実際のプログラミングでは,そうではないケースも多いわけで,
そういう場合に,いちいち名前のある再帰関数を定義したり
継続モナドforever を使ったりするのは,何か違うんじゃね? と.

そもそも Haskell の Control.Monad には,手続き言語で言う while 系列の
「途中で処理を中止する」関数が存在しないんですよね.
仕方ないので

takeWhileM_ :: Monad m => ( a -> m Bool ) -> [a] -> m ()
takeWhileM_ p = ( `foldr` return () ) $ \x action -> do
  cond <- p x
  if cond then action
          else return ()

main = do
  (`takeWhileM_` [1..] ) $ \i -> do
    if i < 10 then do
      print i
      return True
     else
      return False

的な関数を用意してみても,どうも読みやすいようには書けない(( return True とかが邪魔になるので)).
仕方ないので継続モナドを使うことも考えましたが,それはそれで liftIO がキモい.
どうしたもんかな,と思って,色々と試行錯誤した結果,

(\f -> foldr f (return ()) [1..] ) $ \_ action -> do
  いろいろと束縛
  if 条件 then do
    処理
    action
   else
    return ()

的なコードに至り,その後,あれ,これって無名再帰と似てね? と思い至った次第.