2018-08-21 (Tue.)
Haskell の知識をちょっとだけ仮定します.
この文書に書いてある内容は全て以下に書いてあります.
ここでは擬似コードとして Haskell 風言語を用いる. いくつか主要な型及び型クラスがあるわけだが, この文書では以下が型だとする
()
というただ一つの値true
と false
という二つだけMaybe t = Just t | None
と書くEither t s = Left t | Right s
型に数を, それも (0以上の) 自然数を割り当てることをしてみる.
ある型
true, false
という2つの値しか持たない. また特別に作った ()
という1つの値しか持たない. これを型の数だということにしてみる.
type | number |
---|---|
1 | |
2 |
リスト, タプル, Maybe, Either という型は, 型を与えて型を構成するものであったので, 既に数が分かっている型を与えた場合の型の数を調べることにする.
簡単な順に見ていく.
type | number |
---|---|
例えば (true, ())
と (false, ())
の 2つがある.
type | number |
---|---|
Maybe と同様で,
type | number |
---|---|
リストは長さを固定した場合, それがタプルと等価であることはすぐに分かるだろう (例えば [a,b,c] == (a,b,c)
). 従って, 長さが
と書ける. もちろんこれは収束しない値だが.
あるいは次のような考え方もできる. リストとは長さが
f : [t] -> Either () (t, [t])
f [] = Left ()
f (x:xs) = Right (x, xs)
この
これを解くと
を得る. 右辺を級数展開すると先程の結果と一致する.
(
type | number |
---|---|
型に自然数を割り当てることをしてきたが, リストやMaybe, またそれらの合成は, いわば自然数から自然数への関数として機能しているように見える. 最後のリストがまさにそうだった.
[]
が型 t
を型 [t]
に写すものであるのと同様に,
タプル, Maybe や Either には
という関数があるのが見える.
これらを微分することにはどんな意味があるか. とりあえず代数的には (偏) 微分することが出来る.
次のようなデータ構造を考える. リストをある地点に注目してそこから左と右とに分割するのだ.
data Zipper a = Zipper [a] [a]
pop :: Zipper a -> a
pop (Zipper _ (x:xs)) = x
lpush :: Zipper a -> a -> Zipper a
lpush (Zipper left right) x = Zipper (x : left) right
rpush :: Zipper a -> a -> Zipper a
rpush (Zipper left right) x = Zipper left (x : right)
lrot :: Zipper a -> Zipper a
lrot (Zipper left (x : right)) = Zipper (x : left) right
rrot :: Zipper a -> Zipper a
rrot (Zipper (x : left) right) = Zipper left (x : right)
これは全体として表現するものはリストと同じであるが, リストの中のある地点 (要素と要素の境界) に注目して pop
push
を書き直したもの. その値を pop
する, その左右に値を push
する, 注目する要素を動かす ( rot
), といった操作で編集することで計算量がリストとは違う.
ところでそれはいいんだけど, Zipper a
という型の値を計算すると, a
の値をやはり
であることが分かる. そしてこれは明らかに
Zipper はリストを分割したものであって, 分割の仕方に意味があるので (一意ではないので) リストよりも豪華になっている.
まあリストの微分に対応する型がなんでもいいから知りたいなら何でも作れるし, Zipper
とか導入しなくていい. のだけど, この事実を元にして, ある型 (データ構造) T
の微分に対応する型 (データ構造) を T-Zipper
と呼ぶ.
ノードのみに値を持つ二分木を定義する.
data BTree a = Empty | Node a (BTree a) (BTree a)
この値は
という式を満たすものである. 陽には解かないで微分だけして, それに対応するデータ構造を考えてみる. まず両辺を微分すると,
を得る. これを解くと
になる. 右辺に [(Bool, a, BTree a)]
という型の値に等しい.
data BTreeZipper a = BTreeZipper BTree BTree [(Bool, a, BTree a)]
リストの Zipper がある地点に注目して分割したものであったように, BTree の Zipper も, あるノードに注目して分割したものだと思える. つまり
zipper = BTreeZipper left right steps
とはあるノード (の直下) に注目してその左下に left
, 右下に right
という二分木があって, 更にそこからルートまで一つずつ辿ったものが steps
. steps
の各要素
step = (dir, value, child)
は, ノードの値が value
で, 今来たのと逆 (左から来たなら右) に二分木 child
が付いてて, その方向を dir :: Bool
で決めている.
と解釈できる.
以上のように再帰構造の Zipper を取ると意外と, それそのものの別な表現が得られることがある.