Free型はMonadをdata構造として表現したもの
正確に言うと「 f
がFunctorの場合の Free f
はMonadをdataとして表現したもの」
Free型の定義
hsdata Free f a = Pure a
| Join (f (Free f a))
monadとなるためには以下の3つがあれば十分
hsfmap :: (a -> b) -> m a -> m b
pure :: a -> m a
join :: m (m a) -> m a
hsPure :: a -> Free f a
Join :: f (Free f a) -> Free f a
pure
と join
に対応があることがわかる
こうやって見ると、
join
の
m (m a)
の、外側の
m
はFunctorで十分である、ということもわかる

だからあとは map
さえあればMonadにすることが出来る
そこで「Functorのinstanceである型」と組み合わせることで、Monadを作ることが出来る
Free
で構成したデータ型と、実際のMonadが、同じ見た目、同じ挙動になることを確認する
まず普通にPreludeに入っている Maybe
で適当な実装を書いてみる
hsmaybeMain :: Maybe Integer
maybeMain = do
a1 <- Just 1
a2 <- Just 1
pure $ a1 + a2
ここで独自に Maybe'
を用意し、Functorのinstanceにしておく
hsdata Maybe' a = Just' a | Nothing'
instance Functor Maybe' where
map f (Just' a) = Just' $ f a
map f Nothing' = Nothing'
以下、 Maybe
と書いたときはMonad、 Maybe'
と書いたときはFunctorであることに注意する
上で書いた「普通の Maybe
」のコードを、「 Maybe'
+ Free
」で以下のように書ける
hsmainWithFree :: Free Maybe' Integer
mainWithFree = do
a1 <- Join $ Just' $ Pure 1
a2 <- Join $ Just' $ Pure 1
Pure $ a1 + a2
型こそ異なれど、ほぼ同じ見た目をしている
便利関数を用意しておけばほぼ同じ見た目になる
hsmainWithFree' :: Free Maybe' Integer
mainWithFree' = do
a1 <- just' 1
a2 <- just' 1
pure $ a1 + a2
liftF :: Functor f => f r -> Free f r
liftF cmd = Join (fmap Pure cmd)
just' = liftF . Just'
nothing' = liftF Nothing'
Maybe'
はMonadのinstanceではなかったことを思い出す
Monadのinstnaceではないのに、Monadかのように扱えている
mainWithFree
を、do式ではなく bind
を使って書くと
hsmainWithFree'' :: Free Maybe' Integer
mainWithFree'' = Join $ Just' $ Pure 1 >>= (\x -> Join $ Just' $ Pure 1 >>= \y -> Pure $ x + y)
これを、 Monad (Free f)
の定義を「左辺→右辺」に簡約していくと以下のような経過を辿る
hsmainWithFree'' = Join $ Just' $ Pure 1 >>= (\x -> Join $ Just' $ Pure 1 >>= \y -> Pure $ x + y)
== Join $ Just' $ Pure 1 >>= (\x -> Join $ Just' $ Pure $ x + 1)
== Join $ Just' $ Join $ Just' $ Pure $ 1 + 1
== Join $ Just' $ Join $ Just' $ Pure 2
つまり、 mainWithFree
は、 Join $ Just' $ Join $ Just' $ Pure 2
と等価である
この「 Free
と Maybe
の入れ子のデータ構造」が、「do式で表現されるMonad」と同じものであることがわかった
こうやって見ると、タイトルの「Free型はMonadをdataとして表現したもの」の言っている意味がわかった気がする
do
の中の行数の増加に伴って、 Join
と Just
の入れ子も深くなっていく
しかし、全体の型としては Free Maybe' Integer
で一定である
ちなみに、 mainWithFree
はMonadなので、連結も出来る
hsf :: Free Maybe' Integer
f = mainWithFree >> mainWithFree' >> mainWithFree''
Monadとなる条件には
map
,
pure
,
join
の他に、
bind
,
pure
というのもあった
これを使うと別の仕方で Free
を定義できることができる
hs{-# LANGUAGE GADTs #-}
data Free f a where
Pure :: a -> Free f a
Bind :: f x -> (x -> Free f a) -> Free f a
pure
と bind
があれば十分なので、 map
を別途定義する必要はない
だからFunctorの制限が不要になる
実際、これを用いて上の mainWithFree
を書き直すと
hsmainWithFreeB :: Free Maybe' Integer
mainWithFreeB = (Just' 1) `Bind` (\a1 -> (Just' 1) `Bind` (\a2 -> Pure $ a1 + a2))
join
がないので、「do式を消したあるある見た目」っぽくなっている

上の例と同様に mainWithFreeB
はMonadなので連結も出来る
hsf :: Free Maybe' Integer
f = mainWithFreeB >> mainWithFreeB
参考
↓書く途中に書いてたメモ

間違っている
というか、意味をなしていない気がするが、
なんか一応残している
なんというか、 join
と Join
のアナロジーをしようとして上手くいかなかった
do式内の操作と同様7日を見る
上の理解が本当なのかどうかを確認してみる
Free f
はMonadをdataとして表現したものである
ということは、以下の2つは同じような挙動を示すはずである
Free
型をネストして組み上げたデータ構造
do
式の中ででactionを組み合わせて作った関数
まず Free
と Maybe
のネストしたデータ構造を定義してみる
purs(hs)mainWithFree :: ∀ a. Free Maybe (Free Maybe (Maybe a))
mainWithFree = Join $ Just (Join $ Just (Pure (Join $ Just (Pure Nothing))))
これは以下の2つに分解して見たほうがわかりやすい
purs(hs)mainWithFree :: ∀ a. Free Maybe (Free Maybe (Maybe a))
mainWithFree = Join $ Just (Join $ Just (Pure subWithFree))
subWithFree :: ∀ a. Free Maybe (Maybe a)
subWithFree = Join $ Just (Pure Nothing)
mainの中で、subを呼んでいる感じ
これを普通の Maybe
を使った関数で書くとこうなる
purs(hs)main :: ∀ a. Maybe (Maybe (Maybe a))
main = do
a1 <- Just 1
a2 <- Just 1
pure do
b1 <- Just 2
pure Nothing
同様に、分けて書くならこう
purs(hs)main :: ∀ a. Maybe (Maybe (Maybe a))
main = do
a1 <- Just 1
a2 <- Just 1
pure sub
sub :: ∀ a. Maybe (Maybe a)
sub = do
b1 <- Just 2
pure Nothing
型の対応が取れていることが見て取れる
purs(hs)mainWithFree :: ∀ a. Free Maybe (Free Maybe (Maybe a))
main :: forall a. Maybe (Maybe (Maybe a))
そして、Monadの性質により、 join
に適用することでこのネストをflatに出来る
purs(hs)joinedWithFree :: ∀ a. Free Maybe (Maybe a)
joinedWithFree = join mainWithFree
joined :: ∀ a. Maybe (Maybe a)
joined = join main
これも型の対応が取れている