fugafuga.write

日々のログ

すごいH本 part87

リストモナド

5という値は決定的である。一方、[1,2,3]のような値は複数の計算結果を含んでいるとも、複数の候補地を同時に重ね合わせたような1つの値であるとも解釈できる。

リストをアプリカティブスタイルで使う

*Main> (*) <$> [1,2,3] <*> [10,100,1000]
[10,100,1000,20,200,2000,30,300,3000]

左辺のリスト要素と右辺のリスト要素のすべての組み合わせが結果のリストに含まれており、非決定性を表現している。

この非決定性計算はモナドとして利用することができる。

リストMonadのインスタンス

instance Monad [] where
    return x = [x]
    xs >>= f = concat (map f xs)
    fail _ = []

return は引数の値が1つだけ入っているリストを作って返す。return は、普通の値をリストにくるんで非決定な値に混ぜたいときに便利。

>>=を使ってみる

*Main> [3,4,5] >>= (\x -> [x, -x])
[3,-3,4,-4,5,-5]

リスト内の要素に関数が適用されリストががっちゃんこされて返ってきている。

非決定性計算は失敗する可能性のある計算の上位互換になっている。

*Main> [] >>= \x -> ["bad","mad","rad"]
[]
*Main> [1,3,4] >>= \x -> []
[]

空リストは返すべき値がなにもないということを表現しており、MaybeのNothingと似ている。リストにおける失敗は空リストで表現すればよい。

Maybeのときと同様でリストを連結して非決定性という文脈を伝播できる。

*Main> [1,2] >>= \n -> ['a','b'] >>= \ch -> return (n, ch)
[(1,'a'),(1,'b'),(2,'a'),(2,'b')]

これは、[1,2]の各要素n、['a','b']各要素chに対して、タプルを作りなさいというプログラムになっている。

do 記法で書き直す

listOfTuples :: [(Int, Char)]
listOfTuples = do
    n <- [1,2]
    ch <- ['a','b']
    return (n, ch)

実行

*Main> listOfTuples
[(1,'a'),(1,'b'),(2,'a'),(2,'b')]

非決定性という文脈を>>=がいい感じにしてくれている。

所感

文脈というものがイメージできるようになってきたので、モナドがやってくれていることもだんだんイメージできるようになってきた。

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

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

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

すごいH本 part86

ピエールリターンズ

ピエールの綱渡りの一連の処理を do を使って書く

routine :: Maybe Pole
routine = do
    start <- return (0, 0)
    first <- landLeft 2 start
    second <- landRight 2 first
    landLeft 1 second

実行

*Main> routine
Just (3,2)

手続き型のように見えるがそうではない。

ピエールにバナナの皮を踏ませたい場合

routine :: Maybe Pole
routine = do
    start <- return (0, 0)
    first <- landLeft 2 start
    Nothing
    second <- landRight 2 first
    landLeft 1 second

実行

*Main> routine
Nothing

ピエールはしっかり落ちた。 <- の束縛をせずにモナドを使うと、 結果を無視したいモナドの後に>>を使うのと同じ意味になる。 _ <- Nothingと書くと同じ処理になるが、Nothingだけのほうがシンプルである。

do 記法を使うか >>= を使うかは自分次第で、ピエールの例だと>>=がよい。 なぜなら、直前のモナドに次のモナドが依存しているから。

do 式におけるパターンマッチ

do 記法でモナド値を変数名に束縛するときにパターンマッチが使える。

justH :: Maybe Char
justH = do
    (x:xs) <- Just "hello"
    return x

実行

*Main> justH
Just 'h'

do 式の中でパターンマッチが失敗した場合、Monad型クラスのfail関数が使われる。

fail :: (Monad m) => String -> m a
fail msg = error msg

デフォルトではプログラムを異常終了させるようになっている。 一方、Maybe の fail の実装は以下のようになっている。

fail _ = Nothing

msg を無視して Nothing を返している。

パターンマッチに失敗するような do 式を書く

wopwop :: Maybe Char
wopwop = do
    (x:xs) <- Just ""
    return x

実行

*Main> wopwop
Nothing

モナドの文脈で失敗が発生している。

所感

モナドが抽象化してくれているのでかんたんに書ける。

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

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

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

すごいH本 part85

do 記法

do 記法は IOモナドだけでなく、他のモナドにも使える

*Main Lib> Just 3 >>= (\x -> Just (show x ++ "!"))
Just "3!"

入れ子にする

*Main Lib> Just 3 >>= (\x -> Just "!" >>= (\y -> Just (show x ++ y)))
Just "3!"

let式と似ている

*Main Lib> let x = 3; y = "!" in show x ++ y
"3!"

letと異なる点は文脈を持っているかどうか

*Main Lib> Just 3 >>= (\x -> Nothing >>= (\y -> Just (show x ++ y)))
Nothing

Nothingを途中で与えるとちゃんとNothingを返す

この式をスクリプト風に書きなおしてみる

foo :: Maybe String
foo = Just 3 >>= (\x ->
      Just "!" >>= (\y ->
      Just (show x ++ y)))

ラムダ式が煩わしいので do を使って書き直す

foo :: Maybe String
foo = do
        x <- Just 3
        y <- Just "!"
        Just (show x ++ y)

Maybe値がNothingかどうか気にせずMaybe値から生の値がとれそうな書き方になっている。 もし、Nothingだった場合は、do 式全体がNothingになる。

所感

doで糊付けしている中身がわかった。

すごいH本 part84

ピエールを落とす

バナナを仕掛けてピエールを問答無用で落とす関数を作る

banana :: Pole -> Maybe Pole
banana _ = Nothing

これを一連の関数の中に混ぜて使う

*Main> return (0, 0) >>= landLeft 1 >>= banana >>= landRight 1
Nothing

落ちた。

入力に関係なく規定のモナド値を返す関数を作りたい場合、>>を使うとよい。

(>>) :: (Monad m) => m a -> m b -> m b
m >> n = m >>= \_ -> n

実行

*Main> Nothing >> Just 3
Nothing
*Main> Just 3 >> Just 4
Just 4
*Main> Just 3 >> Nothing
Nothing

1番目の例が既定値のJust 3を返さず、Nothingを返しているのは、 Nothing >>= \_ -> Just 3 となっているため。

*Main> Nothing >>= \_ -> Just 3
Nothing

このことから、banana関数は、>> Nothingで置き換えることができる。

*Main> return (0, 0) >>= landLeft 1 >> Nothing >>= landRight 1
Nothing

仮にMaybeによる文脈を使わずに一連の処理を書こうとすると次のようになる。

routine :: Maybe Pole
routine = case landLeft 1 (0, 0) of
    Nothing -> Nothing
    Just pole1 -> case landRight 4 pole1 of
        Nothing -> Nothing
        Just pole2 -> case landLeft 2 pole2 of
            Nothing -> Nothing
            Just pole3 -> landLeft 1 pole3

失敗しているかどうか検査する度に分岐が発生している。 このようなコードが発生する場合に、Maybeモナドが有用である。

Maybeモナドの >>= の実装は、値がNothingかどうかを判定し、その知識に基いて動作を変える というロジックになっている。

所感

Maybeモナドが失敗するかも知れないという文脈を抽象化してくれているおかげで、 一連の処理の定義がシンプルになっていることがわかった。

ただ、モナドはこれだけでは終わらんのであろう。

すごいH本 part83

ピエールの人生とは

せっかくの休暇にバランス棒やってんのに鳥に邪魔されるピエール

まず、鳥とポールを定義

その後に、ポールの右と左に鳥が止まる関数を定義

type Birds = Int
type Pole = (Birds, Birds)

landLeft :: Birds -> Pole -> Pole
landLeft n (left, right) = (left + n, right)

landRight :: Birds -> Pole -> Pole
landRight n (left, right) = (left, right + n)

実行

*Main> landLeft 2 (0, 0)
(2,0)
*Main> landRight 5 (1, 2)
(1,7)
*Main> landRight (-1) (1, 2)
(1,1)

鳥がいなくなる場合、負の数を指定することで実現する。

landLeft と landRight は Pole を返すので、関数合成ができる。

*Main> landLeft 2 . landRight 5 . landLeft 1 $ (0, 0)
(3,5)

ポールを先に書いて鳥を止まらせる関数を後ろに書くほうが読みやすそう

よって、以下のような関数を定義する

x -: f = f x

これで関数を適用するのに引数 -> 関数の順番で書けるようになる。

*Main> 100 -: (*3)
300
*Main> True -: not
False
*Main> (0, 0) -: landLeft 2
(2,0)
*Main> (0, 0) -: landLeft 2 -: landRight 1 -: landLeft 2
(4,1)

ピエールは落ちる

ポールの片方に一気に10羽きた場合、

*Main> landLeft 10 (0, 3)
(10,3)

ピエールは落ちる。

この場合はどうか、

*Main> (0, 0) -: landLeft 1 -: landRight 4 -: landLeft (-1) -: landRight (-2)
(0,2)

最終的には問題ないが、途中経過で(0, 4)の状態になる瞬間があるのでピエールは落ちる。

landLeft と landRight は失敗を表現できる必要がある。 鳥の止まり方が偏りすぎた場合は、失敗を返すようにしたい。

成功か失敗か、ということは Maybe を使う。

landLeft :: Birds -> Pole -> Maybe Pole
landLeft n (left, right)
    | abs ((left + n - right)) < 4 = Just (left + n, right)
    | otherwise = Nothing

landRight :: Birds -> Pole -> Maybe Pole
landRight n (left, right)
    | abs (left - (right + n)) < 4 = Just (left, right + n)
    | otherwise = Nothing

実行

*Main> landLeft 2 (0, 0)
Just (2,0)
*Main> landLeft 10 (0, 3)
Nothing

ピエールが落ちるかどうかを検知できるようになった。

しかし、この変更により landLeft, landRight を関数合成できなくなってしまった。 関数は Maybe Pole を返すが、引数として要求しているのは、Pole だからだ。 なんとかして Pole を取る関数に Maybe Pole を渡したい。

こんなこともあろうかと言う声が聞こえる。 満を持して>>=の登場である。

*Main> landRight 1 (0, 0) >>= landLeft 2
Just (2,1)

Maybe Pole 型の値を Pole 型を要求する関数の引数として渡せている。

Nothing の場合

*Main> Nothing >>= landLeft 2
Nothing

ちゃんとピエールが落ちている。

鳥が止まる一連のイベントは以下のようになる

*Main> return (0, 0) >>= landRight 2 >>= landLeft 2 >>= landRight 2
Just (2,4)

return でポールの状態を Just で包んでいる。

以前出てきたこのパターンをモナド適用を使って書き換える。

*Main> (0, 0) -: landLeft 1 -: landRight 4 -: landLeft (-1) -: landRight (-2)
(0,2)

*Main> return (0, 0) >>= landLeft 1 >>= landRight 4 >>= landLeft (-1) >>= landRight (-2)
Nothing

ピエールはしっかり落ちている。

まとめ

Maybe をアプリカティブ値として扱うだけでは、ここまでのことはできない。 アプリカティブファンクターの機能ではアプリカティブ値同士を深く相互作用させることができない。 アプリカティブにできることは、通常の関数にアプリカティブスタイルで引数を渡すことぐらい。

アプリカティブ値同士を扱うためにモナド適用が使える。

所感

今のところ、モナドは一連の処理の中で文脈を保持してくれて関数合成に使える便利なものというイメージ。

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

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

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