fix
は関数の最小不動点を求める関数です。
fix :: (a -> a) -> a
fix f = f (fix f)
例えば
> fix (const "hello")
"hello"
常に文字列"hello"
を返す関数の不動点(入力と出力が同じになる値)は"hello"
です。(参考: Haskell/不動点と再帰)
fix
といえば無名関数で再帰を書く時に使われる例が有名だと思います
fact :: Int -> Int
fact = fix $ \f n -> if n == 0 then 1 else n * f (n-1)
再帰関数が最小不動点として構成できるというのは凄いですね。(参考: 再帰プログラムの意味論について)
fixで簡単にループを書く
fix
は普段コードを書くときも便利に使えます。例えばユーザーからの入力を反転して出力する動作を"quit"
という入力が来るまで繰り返すプログラムを考えましょう
import Control.Monad
import Data.Function
main :: IO ()
main = do
putStrLn "Start"
fix $ \loop -> do -- ループ開始
str <- getLine -- 入力待ち
unless (str == "quit") $ do -- "quit"じゃ無ければ次を実行
putStrLn (reverse str) -- 反転して表示
loop -- 繰り返し
putStrLn "Bye"
fix
を使わない場合let
を使って再帰関数を一旦定義してから実行するといった手順でもできますが、fix
を使ったほうが定義と実行が一度にできるのでスッキリすると思います。好みの問題かもしれませんが
ループに初期値を与える
次に先ほどのプログラムに回数制限を設けてみます。"quit"
と入力されなくても3回ループすると自動的に終わるようにしてみます。
import Control.Monad
import Data.Function
main :: IO ()
main = do
putStrLn "Start"
($ 3) . fix $ \loop n -> do -- 初期値に3を与えてループ開始
unless (n == 0) $ do -- nが0じゃ無ければ次を実行
str <- getLine -- 入力待ち
unless (str == "quit") $ do -- "quit"かどうか確認
putStrLn (reverse str) -- 反転して表示
loop (n-1) -- nを1減らして繰り返す
putStrLn "Bye"
$
を使ってfix
で作った関数に値を適用しています。