あなたが何を求めているのか正確にはわかりません。しかし、そうです、ツリーで実行したい計算に対応する toTreeAlgebra
をフィードします。foldTree
たとえば、Int
s の木のすべての要素を合計するには、次の代数を使用します。
sumAlgebra :: TreeAlgebra Int Int
sumAlgebra = TreeAlgebra { leaf = id
, branch = (+) }
つまり、リーフの合計を取得するには、リーフid
の値に適用 (何もしない) します。ブランチの合計を取得するには、各子の合計を合計します。
(+)
たとえば、ではなく枝について言えるという事実\x y -> sumTree x + sumTree y
は、カタモルフィズムの本質的な特性です。再帰的なデータ構造で関数を計算するには、直接の子f
の値があれば十分です。f
Haskell は、カタモルフィズムのアイデアを抽象的に形式化できるという点で、非常にユニークな言語です。子に対してパラメータ化された、ツリー内の単一ノードのデータ型を作成しましょう。
data TreeNode a child
= Leaf a
| Branch child child
私たちがそこで何をしたか見てください。再帰的な子を選択したタイプに置き換えただけです。これは、折りたたみ時にサブツリーの合計をそこに配置できるようにするためです。
さて、本当に魔法のことです。これを疑似Haskellで書くつもりです - 実際のHaskellで書くことは可能ですが、タイプチェッカーを助けるためにいくつかの注釈を追加する必要があり、混乱を招く可能性があります. T
パラメータ化されたデータ型の「固定小数点」を取ります。つまり、 のようなデータ型を構築しますT = TreeNode a T
。彼らはこれを operator と呼んでいますMu
。
type Mu f = f (Mu f)
ここをよく見てください。への引数は、やMu
のような型ではありません。orのような型コンストラクタです--それ自体への引数は引数を取ります。(型コンストラクターを抽象化できる可能性は、Haskell の型システムの表現力を本当に際立たせるものの 1 つです)。Int
Foo -> Bar
Maybe
TreeNode Int
Mu
したがって、型Mu f
は、その型パラメーターを取り、それ自体f
で埋めるものとして定義されMu f
ます。ノイズの一部を減らすために同義語を定義します。
type IntNode = TreeNode Int
を展開するMu IntNode
と、次のようになります。
Mu IntNode = IntNode (Mu IntNode)
= Leaf Int | Branch (Mu IntNode) (Mu IntNode)
があなたの とどのようMu IntNode
に等しいか分かりますTree Int
か? 再帰構造をばらばらにしてMu
から、元に戻しました。Mu
これにより、すべてのタイプについて一度に話すことができるという利点が得られます。これにより、カタモルフィズムを定義するために必要なものが得られます。
定義しましょう:
type IntTree = Mu IntNode
カタモルフィズムの本質的な特性は、何らかの関数 を計算するには、直接の子f
の値があれば十分であると述べましたf
。計算しようとしているものの型とr
データ構造を呼び出しましょうnode
(IntNode
これのインスタンス化の可能性があります)。したがって、特定のノードで計算するr
には、その子を に置き換えたノードが必要r
です。この計算の型はnode r -> r
です。したがって、カタモルフィズムは、これらの計算の 1 つがあれば、再帰r
構造全体を計算できることを示しています (再帰はここで で明示的に示されていることを思い出してください)。Mu
cata :: (node r -> r) -> Mu node -> r
この例を具体的にすると、次のようになります。
cata :: (IntNode r -> r) -> IntTree -> r
r
言い換えると、子に s を持つノードを取得して を計算できれば、ツリー全体の をr
計算できます。r
これを実際に計算するには、ノードの子に対して任意の関数をマップできる必要がありますnode
。Functor
fmap :: (a -> b) -> node a -> node b
これは、 に対して簡単に実行できますIntNode
。
fmap f (Leaf x) = Leaf x -- has no children, so stays the same
fmap f (Branch l r) = Branch (f l) (f r) -- apply function to each child
さて、最後に、 の定義を与えることができますcata
(制約は、 が適切な を持っているFunctor node
と言っているだけです):node
fmap
cata :: (Functor node) => (node r -> r) -> Mu node -> r
cata f t = f (fmap (cata f) t)
t
「tree」のニーモニック値にパラメーター名を使用しました。これは抽象的で緻密な定義ですが、実際には非常に単純です。再帰的に実行cata f
- ツリーに対して行っている計算 -t
の子 (それ自体がMu node
s) のそれぞれに対して を取得しnode r
、その結果を渡してそれ自体f
の結果を計算しt
ます。
node r -> r
これを最初に結び付けると、定義している代数は本質的にその関数を定義する方法です。実際、 が与えられるとTreeAlgebra
、fold 関数を簡単に取得できます。
foldFunction :: TreeAlgebra a r -> (TreeNode a r -> r)
foldFunction alg (Leaf a) = leaf alg a
foldFunction alg (Branch l r) = branch alg l r
したがって、ツリーのカタモルフィズムは、次のように一般的なものの観点から定義できます。
type Tree a = Mu (TreeNode a)
treeCata :: TreeAlgebra a r -> (Tree a -> r)
treeCata alg = cata (foldFunction alg)
時間がありません。非常に急速に抽象化されたことは承知していますが、少なくとも学習に役立つ新しい視点が得られたことを願っています. 幸運を!