Subscribed unsubscribe Subscribe Subscribe

モナディックなループ

haskell

しょーもないイディオムの話。モナディックなループを書くとき、だいたい3つくらい宗派があるように思う。

{-# LANGUAGE ViewPatterns #-}
module Loop where
import Control.Monad (when)
import Data.Function (fix)
import System.Environment (getArgs)

f :: IO ()
f = do
  (read -> n):_ <- getArgs
  flip fix (0 :: Int) $ \loop i ->
    when (i < n) $ do
      print i
      loop $ i + 1

g :: IO ()
g = do
  (read -> n):_ <- getArgs
  let loop i = when (i < n) $ do
        print i
        loop $ i + 1
  loop (0 :: Int)

h :: IO ()
h = do
  (read -> n):_ <- getArgs
  loop n (0 :: Int)
  where
    loop n i = when (i < n) $ do
      print i
      loop n $ i + 1

普通のpureなコードを書いているときは基本的にはwhereを使うのだけど、この例で言うnのようにbindした値を後で使いたいときは不便に感じる。会社のコードでは大きいコードはhで小さなループはg、たまにfのコードを見かける*1感じ。

個人的にはletの中身が長くなるとlet前後のつながりがわかりにくく感じるのでfixの方が好き。ただ引数が増えるとflip (flip fix 0) "foo"とかfix `flip` 0 `flip` "foo"とかもあんまり見やすくない。fixの良い感じのinfix operatorがあると良いのかもしれないけど、良い記号も浮かばない。

*1:Coreレベルではfもgも同じ。hだけ2引数の関数の再帰になる。