すごいH本8

ついに2018年の間には終わらず…年末に一気にやろうとしたので大盛り。
節タイトルの「中の人なんていません!」「それはあなたです!」ってどういう意味なんでしょうか。

感想
・errorの型が[Char] -> aだったのでundefinedはメッセージが特殊なerrorなのかな。
・関数f、引数xについて演算子(-:)を作ってx -: f = f xってするの、さらっと書かれてたけど自由すぎてびっくりした。
・適当に遊んでたら鳥の数が負になって笑った。黙って0にしたらいいかなと思ったけど練習と思ってWriterでログに書いてみ…ようとしたけど断念

landLeft :: Birds -> Pole -> Writer [String] (Maybe Pole)
landLeft n (left, right)
    | abs(left + n - right) < 4 = do
        if left + n > 0
            then writer (Just (left + n , right), ["OK. " ++ show (left + n, right)])
            else writer (Just (0, right), ["Bird never negative. " ++ show (0, right)])
    | otherwise = do
        tell ["Fall! " ++ show (left + n, right)]
        return Nothing

runWriter $ return (0,0) >>= landLeft (-1)
-- (Just (0,0),["Bird never negative. (0,0)"]) -- ここで手詰まり

モナドが二重になってる時ってどうやって(>>=)で連鎖させていくの…?
・Nothing >> Just 1がNothingになるのがあまりしっくりこない。

Nothing >> undefined
-- Nothing >>= \_ -> undefined -- Nothing

なのでそうなんだけど、(>>)って前から引数を取らないイメージがあるから気持ち悪い。
・リストについての(>>=)の定義で

xs >>= f = concat (map f xs)
-- 例
[1,2,3] >>= (\x -> [x, -x]) -- [1,-1,2,-2,3,-3]

となんで平らにするのかな…と思ったけど、リストに包まれたままだと結合法則を満たさなくなるのが困るからかな。
・というかアプリカティブファンクターにモノイドっぽさ足したらモナドになりそう?
・関数もモナドらしいので実験してみた。

return 1 >>= (/) $ 2 -- (/) 1 2
-- 0.5
(1/) >>= return $ 2 -- (1/) 2
-- 0.5
((1+) >>= (/)) >>= (+) $ 2
-- 3.5
(1+) >>= (\x -> (x/) >>= (+)) $ 2
-- 3.5

よくわからない…最後のやつは(<=<)になりそうと思ったので(Control.Monadをインポートして)

(1+) >>= ((+) <=< (/)) $ 2
-- 3.5
(return 4 >>= (/) >>= (/)) 2
-- 1.0
((/) <=< (/) <=< return) 4 2
-- 1.0

…やっぱりよくわからない。後にReaderモナドが出てきたけど関数モナドは(>>=)で考えずにdo式で考えたほうがわかりやすかった気がする。
・guardでガード再現できるかな?と思ったけど関数はMonadPlusのインスタンスじゃないし関係ないか…
・runWriterとかrunStateが謎だったけど、中身を取り出すための関数ってだけか。MaybeみたいにみんながみんなShowのインスタンスとは限らないってことかな。
・Nothingで(>>)って前から引数を取らないイメージがあるから…って書いたのと似てるけど、

runWriter $ (\a -> writer ((),"hoge") >> writer((),"huga") >> return a) 1
-- (1,"hogehuga")

のようにWriterでも(>>)でつながってるのに前のログを繋いでいくのが不思議だった。WriterのMonadインスタンス定義を見てたら、(>>)はログじゃなくて値部分を受け取ってないだけでログは繋がってるみたい。
・(++)の定義見てるとリストは左から右に構築されるっていうか、左から実体化していくって感じか(うまく言語化できない)

x0 : xs ++ ys = x0 : (xs ++ ys)
-- x0 : x1 : x2 : ... : ([] ++ ys)
-- x0 : x1 : x2 : ... : ys

・(a ++ (b ++ ... ++ z))..)と((...(a ++ b) ++ ... ++ zの効率の違い、本のコード見てると末尾再帰と(線形)再帰の効率の違いを思い浮かべたけど、ちょっと違うかも。
・差分リストの定義をじっと眺めると、関数ってモノイドかも?と思ったので実験。

(\xs -> [1,2,3] ++ xs) `mappend` (\xs -> [4,5,6] ++ xs) $ [7,8,9]
-- [1,2,3,7,8,9,4,5,6,7,8,9]
mappend <$> (\xs -> [1,2,3] ++ xs) <*> (\xs -> [4,5,6] ++ xs) $ [7,8,9]
-- [1,2,3,7,8,9,4,5,6,7,8,9]

…思ってたのと違う(下のはついで)。というかこれ単にリスト足してるだけっぽいし… 落ち着いて調べてたら、そもそもモノイドの種類は* -> Constraintなので関数はモノイドじゃないですね(と思ったけどnewtypeで包んだら(演算が閉じていれば)モノイドにできるっぽい)。
やりたかったのはこれ。

fmap (\xs -> [1,2,3] ++ xs) (\xs -> [4,5,6] ++ xs) [7,8,9]
-- [1,2,3,4,5,6,7,8,9]

よく読んでみれば全く本のDiffListと同じ意味ですね。
・readsが何なのかかなりわかりにくかった。

reads :: Read a => ReadS a -- String -> [(a, String)]
reads "123abc" :: [(Int,String)]
-- [(123,"abc")]
reads "1.2cd" :: [(Double,String)]
-- [(1.2,"cd")]
reads "1.2cd" :: [(Int,String)]
-- []

見てると、なんかreadsってStateっぽくない?ということで

readSt :: (Read a) => State String a
readSt = state $ head.reads
runState (readSt >>= \x -> readSt >>= \y -> return (x + y)) "1 2 3"
-- (3," 3")

・読者への演習問題になってた騎士の経路の一般バージョンもやってみた(本に載ってる関数は省略)

type KnightPath = [KnightPos]
traceKnight :: KnightPath -> [KnightPath]
traceKnight path = do
    end <- moveKnight $ head path
    return $ end:path
showPathInN :: Int -> KnightPos -> KnightPos -> [KnightPath]
showPathInN n start end = return [start] >>= foldr (<=<) return (replicate n traceKnight) >>= (\all@(x:xs) -> guard (x == end) >> return all)

チェス盤がないから答え合わせしてないけど多分あってるはず(願望)
・いつのバージョンからかはわからないけどMonadインスタンスはApplicativeインスタンスでないとだめになったらしい。本ではリストとかIOのアプリカティブの定義はモナドを使ってるのに循環しない?と思ってHoogleで調べたら(<*>) = apでいいらしい。まじか。

instance Applicative Prob where
    pure x = Prob [(x, 1%1)]
    (Prob fs) <*> (Prob xs) = Prob $ [(f x, pf * px) | (f, pf) <- fs, (x, px) <- xs]
    -- (<*>) = ap

気持ち悪いので自力で(<*>)を実装した。最後に演習問題として重複したProb値を統合する関数を実装。

joinProb (Prob xs) = 
    Prob $ compress [(x0, foldl (\acc (x, p) -> if x == x0 then acc + p else acc) 0 xs) | (x0, p0) <- xs] where
        compress [] = []
        compress (x:xs) = if x `elem` xs then compress xs else x:(compress xs)

test = (Prob [((1+),1%3),((2*),1%3),((3-),1%3)]) <*> (Prob [(1,2%3),(2,1%3)])
-- getProb test = [(2,2 % 9),(3,1 % 9),(2,2 % 9),(4,1 % 9),(2,2 % 9),(1,1 % 9)]
getProb $ joinProb test
-- [(3,1 % 9),(4,1 % 9),(2,2 % 3),(1,1 % 9)]

何とかできたけど、絶対もっとシンプルなのない?

モナドが気になってこの本を買ったのでここまでこれてよかった(まだ一章残ってるけど)。「(Haskell以外の)「代入」という操作は、状態付きの計算」って記述やMaybeやWriterでも感じたけど、モナドはdo式の中にこれまでの文脈を暗黙に閉じ込めるものなのかなって感触だった。

いよいよあと一章!