fugafuga.write

日々のログ

すごいH本 part98

モナドを作る

モナドは作りたいと思って作るものではない。 ある問題の側面をモデル化した型を作り、 後からその型が文脈付きの値を表現していてモナドのように振る舞うとわかった場合に、 Monadインスタンスを与える場合が多い。

リスト [3,5,9] を、整数3, 5, 9が同時に存在している状態だとすると、 それぞれの数の存在確率の情報が足りないと気づく。

確率も含めて表現するとこう、

[(3,0.5),(5,0.25),(9,0.25)]

数学では確率は0から1までの実数で表現する。 確率を浮動小数で表現した場合、すぐに精度が落ちて困る。 そのため、Haskellには分数のためのデータ型がある。 Rationalと呼ばれる、Data.Ratioモジュールにある。 分子と分母は%記号で区切る。

*Main Data.Ratio> 1%4
1 % 4
*Main Data.Ratio> 1%2 + 1%2
1 % 1
*Main Data.Ratio> 1%3 + 5%4
19 % 12

確率をRationalで表す。

*Main Data.Ratio> [(3,1%2),(5,1%4),(9,1%4)]
[(3,1 % 2),(5,1 % 4),(9,1 % 4)]

これを newtype で新しい型に包む

import Data.Ratio

newtype Prob a = Prob { getProb :: [(a, Rational)] } deriving Show

リストはファンクターであるので、Probもファンクターになれる。

instance Functor Prob where
    fmap f (Prob xs) = Prob $ map (\(x, p) -> (f x, p)) xs

動作させてみる

*Main Data.Ratio> fmap negate (Prob [(-3,1 % 2),(-5,1 % 4),(-9,1 % 4)])
Prob {getProb = [(3,1 % 2),(5,1 % 4),(9,1 % 4)]}

確率の総和は常に1である。

これはモナドかどうか考える。 まず、return について。リストのreturnは値を取って単一要素のリストに入れる関数。 Probの場合も、単一要素を作るっぽい。確率は、1。 >>=は、m >>= fjoin (fmap f m) が等価であることを使って確率リストを平らにすることを考える。

'a','b'が起こる確率が25%,'c','d'が起こる確率が75%とした場合の状況を確率リストで表す。

thisSituation :: Prob (Prob Char)
thisSituation = Prob
    [(Prob [('a',1%2),('b', 1%2)], 1%4)
    ,(Prob [('c',1%2),('d', 1%2)], 3%4)
    ]

型が Prob (Prob Char)と入れ子になっている。これを平らにする。

flatten :: Prob (Prob a) -> Prob a
flatten (Prob xs) = Prob $ concat $ map multAll xs
    where multAll (Prob innerxs, p) = map (\(x, r) -> (x, p*r)) innerxs

関数 multAll は、確率リストとある確率pのタプルをとって、 リストの中の確率をp倍して、事象と確率の組のリストを返す関数。

flatten は、multAll を入れ子確率リストの各要素を適用してまわり、 得られた入れ子リストを最後にリストとして平らにする。

Monadインスタンスを書く。(Applicativeも書かないとGHCがエラー出す)

参考 : https://qiita.com/Aruneko/items/e72f7c6ee49159751cba

instance Applicative Prob where
    pure x = Prob [(x,1%1)]

instance Monad Prob where
    return x = Prob [(x,1%1)]
    m >>= f = flatten (fmap f m)
    fail _ = Prob []

モナドインスタンスが手に入ったので、 確率計算をするプログラムを書く。 普通のコインが2枚と、10回投げると9回裏がでるよう細工されたコイン1枚を全部同時に投げて、 全部裏が出る確率をもとめる。

data Coin = Heads | Tails deriving (Show, Eq)

coin :: Prob Coin
coin = Prob [(Heads,1%2),(Tails,1%2)]

loadedCoin :: Prob Coin
loadedCoin = Prob [(Heads,1%10),(Tails,9%10)]

flipThree :: Prob Bool
flipThree = do
    a <- coin
    b <- coin
    c <- loadedCoin
    return (all (==Tails) [a,b,c])

実行

*Main Data.Ratio> getProb flipThree
[(False,1 % 40),(False,9 % 40),(False,1 % 40),(False,9 % 40),(False,1 % 40),(False,9 % 40),(False,1 % 40),(True,9 % 40)]

3枚とも裏が出る確率は、9/40になる。

所感

自分で書ける気がしない。

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

  • 作者: MiranLipovaca
  • 出版社/メーカー: オーム社
  • 発売日: 2017/07/14
  • メディア: Kindle版
  • 購入: 4人 クリック: 9回
  • この商品を含むブログを見る