銀天随筆集 このページをアンテナに追加 RSSフィード Twitter

2011-11-18 手続き的な彼女 〜 procedural language

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

的な関数を用意してみても,どうも読みやすいようには書けない*1

仕方ないので継続モナドを使うことも考えましたが,それはそれで liftIO がキモい.

どうしたもんかな,と思って,色々と試行錯誤した結果,

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

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

*1return True とかが邪魔になるので

msakaimsakai 2011/11/20 22:24 回数等があらかじめ分かるなら、replicateM_, forM_ あたりを使った方が楽ですよ。

gintenlabogintenlabo 2011/11/21 05:15 それは当たり前.

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/gintenlabo/20111118/1321632864