メモ

Data.MemoCombinatorsモジュールについて言及したblog
http://www.amateurtopologist.com/2009/11/22/data-memocombinators-and-you/
を見つけて、何をどうしてCombinatorsというモジュール名にしたのかというのとどういう実装に成っているか知りたいので読んでみました。

blogの方ではメモ化フィボナッチ関数の実装をしていて、しかしモジュールの該当するコードを読んでもぱっと見た感じ明示的にmemoizeしているようには見えません。良く分からん!

ので、必要な部分だけ引っ張りだしてみて

{-
via http://www.amateurtopologist.com/2009/11/22/data-memocombinators-and-you/

Following is Essence of Data.MemoCombinators Module:
http://hackage.haskell.org/packages/archive/data-memocombinators/0.3/doc/html/Data-MemoCombinators.html

Ref : http://www.haskell.org/haskellwiki/Memoization
-}

{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE ScopedTypeVariables #-}

import Data.Bits

type Memo a = forall r. (a -> r) -> (a -> r)

{-
10進10 -> 0b1010 -> [False,True,False,True]
-}
integralBits :: (Integral a) => a -> [Bool]
integralBits 0 = []
integralBits x = let (q,r) = quotRem x 2 in toBool r : integralBits q
    where
    toBool 0 = False
    toBool 1 = True

integralFromBits :: (Integral a) => [Bool] -> a
integralFromBits [] = 0
integralFromBits (x:xs) = unbit x + 2*integralFromBits xs
    where unbit True = 1 ; unbit False = 0

bool :: Memo Bool
bool f = cond (f True) (f False)
    where
    cond t f True  = t
    cond t f False = f

boollist :: Memo [Bool]
boollist (f :: [Bool] -> a) = table (f []) (bool (\x -> boollist (f . (x:))))
    where
      table :: a -> (Bool -> ([Bool] -> a)) -> [Bool] -> a
      table nil cons [] = nil
      table nil cons (x : xs) = (cons x) xs

fib5 = fib5'
    where
      fib5' 0 = 0
      fib5' 1 = 1
      fib5' n = fib5'' (n - 1) + fib5'' (n - 2)
      fib5'' x = f $ integralBits x
      f = boollist g -- calculating function!!
      -- f x = (boollist g) x -- 直前のfと同じように見えるが、こちらのfを使うと不味い
      g x = fib5' $ integralFromBits x

元の関数をごちゃごちゃ弄って等価っぽい関数に変換(η変換とか)しまわってたらfib5とかになってしまったのはさておき。

肝心な部分は

f = boollist g

bool :: Memo Bool
bool f = cond (f True) (f False)
    where
    cond t f True  = t
    cond t f False = f

で、fに関しては「gという関数」を計算させたくて、そこで重要なのがboolさんで、boolは引数fにTrueとFalseを適用させてしまう。

なので、

{-
fのcomputationを手動でやってみる。

boollist g
=>
table (g []) (bool (\x -> boollist (g . (x:))))
=>
table 0 (cond (boollist (g . (True:))) (boollist (g . (False:))))

boollist (g . (True:))
=>
table (g [True]) (bool (\x -> boollist ((g . (True:)) . (x:))))
=>
table (fib5' 1) (cond (boollist ((g . (True:)) . (True:))) (boollist ((g . (True:)) . (False:))))

boollist ((g . (True:)) . (True:))
=>
table (g [True,True]) (bool (\x -> boollist (((g . (True:)) . (True:)) . (x:))))
=>
table (fib5' 3) (cond (boollist (((g . (True:)) . (True:)) . (True:))) (boollist (((g . (True:)) . (True:)) . (False:))))

...
-}

という、2進化した木を作って行く感じです。

折角なのでHaskellWiki(http://www.haskell.org/haskellwiki/Memoization)を見てみると色々面白い事が書いていました。
例えば、

memoized_fib :: Int -> Integer
memoized_fib =
   let fib 0 = 0
       fib 1 = 1
       fib n = memoized_fib (n-2) + memoized_fib (n-1)
   in  (map fib [0 ..] !!)

とかは評価戦略に沿った実装になっていてすごひ。

先にこっちを知っていればData.MemoCombinatorsの実装ももうちょっと楽に読み解けたかもしれません。