メモ

654 名前:デフォルトの名無しさん [sage]: 2008/08/28(木) 21:41:23  
    具体的にどういう場合にどうして効率が良くなるんですか?
    ReadPだと、PがReadPで包まれてるわけだけど、

    get' = Get return
    look' = Look return
    sat' p = do a <- get' ; if p a then return a else Fail
    char' c = sat' (c == )
    string' s = do str <- look' ; scan s str
       where scan [] _ = return s
           scan (x:xs) (y:ys) | x == y = do get' ; scan xs ys
           scan _ _ = Fail
    みたいにReadPでくるまないバージョンも用意できて、それもrunで使える。
    http://www.cs.chalmers.se/Cs/Grundutb/Kurser/afp/2006/Papers/parser-claessen.pdf
    ここにも効率がって書いてあるけどどんな場合なのかさっぱりだ。。 


657 名前:デフォルトの名無しさん [sage]: 2008/08/29(金) 17:28:28  
    >>654
    ReadP の計算で左結合になってる >>= がある場合でも、
    内側の P の >>= をすべて右結合にすることで、
    P の >>= の再帰が無くなって効率が良くなる。

    9節の第1パラグラフに書いてある通りなんだけど。

    左結合を右結合にってのは、>>= の結合則
    (m >>= f) >>= g == m >>= (\a -> f a >>= g)
    の左辺を右辺にするってな話。
    例えば、string s >>= f でも string s の中で
    >>= を使ってるので、左結合になってる。
    つまりほとんど全ての場合に当てはまる。 


658 名前:デフォルトの名無しさん [sage]: 2008/08/29(金) 17:29:19  
    効率が悪くなる事情は、そこにも書いてあるようにリストの ++ と同じ。

    リストの ++ は左引数に関して再帰する。
    [] ++ ys = ys
    (x:xs) ++ ys = x : (xs ++ ys)
    そのため (xs ++ ys) ++ zs は xs に関して二重に再帰することになる。
    foldr (++) [] (map show [1..10000])
    foldl (++) [] (map show [1..10000])
    実際これらを実行してみると前者はすぐ終わるけど、後者は "1" を10000回結合、
    "2" を9999回結合、... "10000" を1回結合、みたいになって遅い。加速してくけど。
    遅いだけじゃなく、中間リストを生成するので無駄にメモリを使うことにもなる。
    foldl は極端な例だけど、foldr も極端で、いつも無駄が無いようにはいかない。 


659 名前:デフォルトの名無しさん [sage]: 2008/08/29(金) 17:30:18  
    で、回避策。

    xs ++ ys は、xs の最後の [] を ys に置き換える。
    それを効率よくやるには、最初っから [] なんか使わないで、
    1:2:3:[] を \nil -> 1:2:3:nil みたいにしとけばいいじゃんという発想。
    つまり [a] を [a] -> [a] に、xs を xs ++ に、++ を (.) にする。

    こうしておくと、[] を与えてリストに戻すときには、
    (.) が右結合になってなくても ++ は右結合になる。
    (((xs ++) . (ys ++)) . (zs ++)) []
    = ((xs ++) . (ys ++)) (zs ++ [])
    = (xs ++) (ys ++ (zs ++ []))
    = xs ++ (ys ++ (zs ++ []))

    実際 String の ++ を頻繁に使う class Show あたりでは、
    できるだけ type ShowS = String -> String を使うことになってる。
    shows :: Show a => a -> ShowS を使ってさっきの
    foldl (.) id (map shows [1..10000]) []
    をやってみると、今度は問題無く速い。 


660 名前:デフォルトの名無しさん [sage]: 2008/08/29(金) 17:31:29  
    で、ReadP。

    m >>= f (P の >>=)は、m の最後の return a を f a に置き換える。
    それを効率よくやるには、最初っから return なんか使わないで、
    Get (\c1 -> Get (\c2 -> return [c1,c2])) を
    \k -> Get (\c1 -> Get (\c2 -> k [c1,c2])) みたいにしとけばいいじゃんという発想。
    つまり P a を forall b. (a -> P b) -> P b に、
    m を m >>= に、>>= を \m f k -> m (\a -> f a k) にする。

    以下略。 


661 名前:デフォルトの名無しさん [sage]: 2008/08/29(金) 17:32:22  
    で、余談。

    foldr c n xs は、xs の : を c に、[] を n に置き換える。
    それを効率よくやるには、最初っから : や [] なんか使わないで、
    1:2:3:[] を \c n -> 1 `c` 2 `c` 3 `c` n みたいにしとけばいいじゃんという発想。
    つまり [a] を forall b. (a -> b -> b) -> b -> b にする。
    リストに戻すときは build xs = xs (:) [] を使う。
    すると foldr c n (build xs) ==> xs c n と変換できる。

    map f xs <==> build (\c n -> foldr (c . f) n xs)
    例えばこういう変換を定義すれば、
    (map f . map g) xs = map f (map g xs)
    ==> build (\c n -> foldr (c . f) n (build (\c n -> foldr (c . g) n xs)))
    ==> build (\c n -> (\c n -> foldr (c . g) n xs) (c . f) n)
    ==> build (\c n -> foldr (c . f . g) n xs)
    ==> map (f . g) xs
    のように map f . map g ==> map (f . g) という変換ができる。
    map f . map g 以外にも、他のいろいろなリスト関数の
    foldr/build を使った形への変換を定義しておけば、いろいろな変換ができる。
    foldr/build による融合変換ってやつ。今の GHC もこれを使ってる。
    詳しくは GHC User's Guide の 8.13. Rewrite rules あたりを見てくれ。