二分木の上の再帰関数のための共和的メモ化テーブルをどのように構築できますか?


8

Coq のStreamMemoライブラリはf : nat -> A、自然数に対して関数をメモ化する方法を示しています。特にの場合f (S n) = g (f n)imemo_makeは再帰呼び出しの計算を共有します。

自然数の代わりに、二分木の再帰関数をメモしたいとします:

Inductive binTree : Set := | Leaf : binTree | Branch : binTree -> binTree -> binTree.

我々は機能があるとf : binTree -> A機能があることを意味し、構造的に再帰的であるg : A -> A -> Aことなどf (Branch x y) = g (f x) (f y)fCoqで同様のメモテーブルを作成して、再帰的な計算を共有する方法を教えてください。

Haskellでは、そのようなメモテーブル(たとえば、MemoTrieを参照)と結び目を作成することはそれほど難しくありません。明らかに、このようなメモテーブルは生産的です。依存型付けされた言語がそのような結び目を受け入れることを受け入れるように納得させるように物事をどのように整理できるか?

私はCoqで問題を指定しましたが、Agdaまたは他の依存型付き言語での回答にも満足しています。

回答:


4

サイズ変更された型で再帰パターンを機能させるのは簡単です。うまくいけば、共有はコンパイルを通じて保持されます![1]

module _ where

open import Size
open import Data.Nat

data BT (i : Size) : Set where
  Leaf : BT i
  Branch : ∀ {j : Size< i} → BT j → BT j → BT i

record Memo (A : Set) (i : Size) : Set where
  coinductive
  field
    leaf : A
    branch : ∀ {j : Size< i} → Memo (Memo A j) j

open Memo

trie : ∀ {i} {A} → (BT i → A) → Memo A i
trie f .leaf = f Leaf
trie f .branch = trie (\ l → trie \ r → f (Branch l r))

untrie : ∀ {i A} → Memo A i → BT i → A
untrie m Leaf         = m .leaf
untrie m (Branch l r) = untrie (untrie (m .branch) l) r

memo : ∀ {i A} → (BT i → A) → BT i → A
memo f = untrie (trie f)

memoFix : ∀ {A : Set} → A → (A → A → A) → ∀ {i} → BT i → A
memoFix {A} lf br = go
 where
  go h : ∀ {i} → BT i → A
  go = memo h
  h Leaf = lf
  h (Branch l r) = br (go l) (go r)

[1] https://github.com/agda/agda/issues/2918


これをありがとう。このコードについて2つの心配があります。まず、go値はサイズパラメータの関数です。一般に、同じ値の独立した関数呼び出し間での共有はありません。これはおそらく、の定義にletステートメントを追加することで修正できh (Branch l r)ます。第2に、層別化された定義BTは、2つのツリー、またはその他の形が同じであるツリーが、異なるレベルで発生すると異なる値を持つことを意味します。これらの個別の値はMemoTrieで共有されません。
ラッセルオコナー

AgdaがMemoinのネストされた定義を受け入れることに感心しましたbranch。Coqの陽性チェッカーはこれを拒否しているようで、Coqの状況はより複雑になっています。
ラッセルオコナー

私がリンクした問題は、GHCバックエンドでコンパイルした場合、実行時にサイズが問題にならないと結論付けているようですが、これは自分で確認していません。
サイザン

そうですか。プルーフアシスタント内で使用できるメモ化ソリューションを探しています。これにより、リフレクションによってプルーフの一部として使用できます。あなたの解決策はおそらく、Size型が消去されてしまうことを想定したコンパイルに適しています。
ラッセルオコナー

0

私は、Coqのバイナリツリーの構造的に再帰的な関数を再帰的にメモする「ソリューション」を作成しました。私の要点はhttps://gist.github.com/roconnor/286d0f21af36c2e97e74338f10a4315bにあります

サイズメトリックに基づいてバイナリツリーを階層化し、私の場合はバイナリツリーの内部ノードの数をカウントし、特定のサイズのすべてのソリューションのコンテナーのレイジーストリームを生成することにより、Saizanのソリューションと同様に動作します。共有は、ストリームの後の部分で使用されるストリームの最初の部分を保持するストリームジェネレーターのletステートメントが原因で発生します。

例は、の場合vm_compute、9レベルの完全なバイナリツリーを評価した後、8レベルの完全なバイナリツリーを評価する方が、8レベルの完全なバイナリツリーを評価するよりもはるかに高速であることを示しています。

ただし、この特定のソリューションのオーバーヘッドは悪いので、実用的な入力の例を構造的に再帰せずメモした場合よりもパフォーマンスが悪いため、この答えを受け入れるのをためらっています。当然のことながら、妥当な入力の下でパフォーマンスが向上するソリューションが必要です。

[Coq-Club]バイナリツリーでの再帰関数の連想メモ化テーブルをどのように作成できますか?」でさらにコメントがあります

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.