wvogel日記

自分用の技術備忘録が多めです.

Free Monadで料理を記述する

ここ数日、Why free monads matterの記事を読んでいました。
非常に簡単にモナドを構成できるのはわかったけれど、
これで何が嬉しいのかよくわかりませんでした。
で、記事を読み進めていくうちに、手続き自体の構造を記述するのに非常に便利なのではないか、それなら料理のレシピとか記述できそう!

料理をモナドにすることの何が嬉しいかって、
記述時に、do記法を使って非常に直感的に書けるじゃん!
と、ただそれだけの理由で作ってみた。

今回は卵焼きを作ってみた。

まずは必要なデータを用意。
調理器具と食材はユーザーが自由に設定出来るよう、Stringとしました。
加熱には時間と調理器具(今気づいたけど、食材でもいいですね)と火力、
焼く・混ぜる作業には食材群が必要です。
また、レシピモナドであることを明確にするため、RecipeTを定義します。

--食材
type Ingredient = String
--火力
data Power = Low | Medium | Strong deriving Show
--調理器具
type Cookware = String

data Cook next =
    Heat Int Power Cookware next
    | Mix [Ingredient] next
    | Bake Int [Ingredient] next
    | Done

type RecipeT = FreeT Cook

そしてこいつを、Functorのインスタンスにします

instance Functor Cook where
    fmap f (Heat n p ware next) = Heat n p ware (f next)
    fmap f (Mix ings next) = Mix ings (f next)
    fmap f (Bake n ings next) = Bake n ings (f next)
    fmap f Done = Done

次に、調理方法を関数として実装します

bake :: Monad m => Int -> [Ingredient] -> RecipeT m ()
bake n ings = liftF (Bake n ings ())

mix :: Monad m => [Ingredient] -> RecipeT m ()
mix ings = liftF (Mix ings ())

heat :: Monad m => Int ->Power -> Cookware -> RecipeT m ()
heat n pw ware = liftF (Heat n pw ware ())

done :: Monad m => RecipeT m ()
done = liftF Done

では実際に卵焼きのレシピを作成しましょう!!
熱した鍋に溶いた卵をいれて焼けばいいですよね

omelet :: Monad m => RecipeT m ()
omelet = do
    let egg = ["egg"]
    mix egg
    heat 2 Strong "pan"
    bake 3 egg
    done

と、このように!
非常に直感的に書けるようになりました!パチパチー