Como você pode criar uma tabela de memorização coindutiva para funções recursivas em árvores binárias?

8

A biblioteca StreamMemo para Coq ilustra como memorizar uma função f : nat -> Asobre os números naturais. Em particular quando f (S n) = g (f n), imemo_makecompartilha o cálculo de chamadas recursivas.

Suponha que, em vez de números naturais, desejemos memorizar funções recursivas sobre árvores binárias:

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

Suponha que tenhamos uma função f : binTree -> Aestruturalmente recursiva, significando que existe uma função g : A -> A -> Acomo essa f (Branch x y) = g (f x) (f y). Como construímos uma tabela de memorando semelhante para fno Coq, de modo que os cálculos recursivos sejam compartilhados?

Em Haskell, não é muito difícil criar uma tabela de notas (veja o MemoTrie, por exemplo) e amarrar o nó. Claramente, essas tabelas de notas são produtivas. Como podemos organizar as coisas para convencer uma linguagem tipicamente dependente a aceitar que esse nó é produtivo?

Embora eu tenha especificado o problema no Coq, eu ficaria feliz com uma resposta no Agda ou em qualquer outro idioma de tipo dependente.

Russell O'Connor
fonte

Respostas:

4

É fácil o suficiente para fazer com que o padrão de recursão funcione com tipos de tamanho. Espero que o compartilhamento seja preservado através da compilação! [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.

Saizan
fonte
Obrigado por isso. Eu tenho duas preocupações com esse código. Primeiro, o govalor é uma função de um parâmetro Size. Em geral, não há compartilhamento entre chamadas de função independentes com o mesmo valor. Provavelmente, isso pode ser corrigido adicionando uma instrução let na definição de h (Branch l r). Em segundo lugar, a definição estratificada de BTsignifica que duas árvores, de outra forma idêntica, terão valores diferentes quando ocorrerem em níveis diferentes. Esses valores distintos não serão compartilhados no MemoTrie.
Russell O'Connor
Estou impressionado que o Agda aceite a definição aninhada de Memoin branch. O verificador de positividade da Coq parece rejeitar isso, tornando as coisas mais complicadas na Coq.
Russell O'Connor
O problema ao qual vinculei parece concluir que os tamanhos não são um problema em tempo de execução quando compilados com o back-end do GHC, embora eu mesmo não tenha verificado isso.
precisa saber é o seguinte
Eu vejo. Estou procurando uma solução de memorização que possa ser usada no assistente de prova para que possa ser usada como parte de uma prova por reflexão. Sua solução provavelmente é adequada para compilação, assumindo que os Sizetipos acabam sendo apagados.
Russell O'Connor
0

Eu criei uma "solução" que memoriza recursivamente funções estruturalmente recursivas de árvores binárias no Coq. Minha essência está em https://gist.github.com/roconnor/286d0f21af36c2e97e74338f10a4315b .

Ele opera de maneira semelhante à solução da Saizan , estratificando árvores binárias com base em uma métrica de tamanho, no meu caso, contando o número de nós internos de árvores binárias e produzindo um fluxo lento de contêineres de todas as soluções para um tamanho específico. O compartilhamento ocorre devido a uma instrução let no gerador de fluxo que mantém a parte inicial do fluxo a ser usada nas partes posteriores do fluxo.

Os exemplos mostram que vm_compute, avaliar uma árvore binária perfeita com 8 níveis após ter avaliado uma árvore binária perfeita com 9 níveis é muito mais rápido do que apenas avaliar uma árvore binária perfeita com 8 níveis.

No entanto, hesito em aceitar esta resposta porque a sobrecarga dessa solução em particular é ruim, pois ela tem um desempenho muito pior do que minha memorização sem recursão estrutural para meus exemplos de informações práticas. Naturalmente, quero uma solução que tenha um desempenho melhor com entradas razoáveis.

Tenho mais alguns comentários em " [Coq-Club] Como você pode criar uma tabela de memorização coindutiva para funções recursivas em árvores binárias? ".

Russell O'Connor
fonte