概要
Qiitaに初投稿した拙い記事の中でこんな再帰だけのHaskellのコードを書いていた。
update n x y = do
-- (描画処理など)
update (n+1) (x+vx) (y+vy)
where
r = n * pi/180.0
vx = sin r
vy = cos r
問題の記事では、コードを再帰させることの意味を強調するためにあえてそうしたつもりだったが、こういう時は素直にStateモナドを使ったほうが分かりやすいはずなので、一応ちゃんと書き直してみる。
特に意味はない。
本記事は関数型言語の入門程度の知識を前提としています。
使用環境
Haskell (ghc 8.4.4)
簡単な使用法
いきなりStateモナド自体の実装から入る解説が多いので、自分のようなミジンコでも手っ取り早く使える方法を記載しておく。(いい加減な情報なのでちゃんと知りたい方は参考文献を参照するかググってください)
Stateモナドは内部に状態変数を持っておける仕組みで、State s a
(sは状態変数の型、aは関数の戻り値の型)という形でデータ型を定義できる。
do構文などを使えば途中で状態変数を変更したりできる。
Stateが保有する関数の実体はs -> (a, s)
という関数型なので、下記のように漸化式のような形で直接指定することができる。
使用する際にはControl.Monad.State
のインポートが必要。
import Control.Monad.State
-- 値を後置インクリメントするState関数
increment :: State Int Int
increment = state $ \x -> (x, x + 1) -- xを返してx + 1を状態変数として持つ
この他に、現在の状態変数を返すget
、状態変数を更新するput
、状態変数に変更を加えずに戻り値を返却するreturn
が定義されている。
実際にそれなりの長さの関数を書く場合は恐らくこちらを使用した方が柔軟に書ける。
increment = do
x <- get
put (x + 1) -- x自体は更新されない
return x
修正後
だいぶ蛇足感がありますが…
import Control.Monad.State
type FrameData = (Int, Double, Double)
update :: State FrameData ()
update = do
(n, x, y) <- get -- 状態変数を取得
let r = fromIntegral n * pi / 180.0
vx = sin r
vy = cos r
put (n + 1, x + vx, y + vy) -- ステートを更新
-- ループ部分
mainloop :: State FrameData ()
mainloop = forever $ do
update
-- 描画処理など
状態変数を先に拾ってくる必要がある為where
句は使用できない(できないことはないが面倒)。
修正前のコードでは一度実行すると無限に再帰し続けるが、Stateモナド版ではupdate
関数を呼び出すことでステップ実行が可能。
実行するときはrunState mainloop (0, 0.0, 0.0)
などとすれば良い。