Definiciones recursivas sobre un tipo inductivo con componentes anidados

21

Considere un tipo inductivo que tiene algunas ocurrencias recursivas en una ubicación anidada, pero estrictamente positiva. Por ejemplo, árboles con ramificación finita con nodos que utilizan una estructura de datos de lista genérica para almacenar los elementos secundarios.

Inductive LTree : Set := Node : list LTree -> LTree.

La forma ingenua de definir una función recursiva sobre estos árboles recurriendo sobre árboles y listas de árboles no funciona. Aquí hay un ejemplo con la sizefunción que calcula el número de nodos.

Fixpoint size (t : LTree) : nat := match t with Node l => 1 + (size_l l) end
with size_l (l : list LTree) : nat := match l with
    | nil => 0
    | cons h r => size h + size_l r
  end.

Esta definición está mal formada (mensaje de error extraído):

Error:
Recursive definition of size_l is ill-formed.
Recursive call to size has principal argument equal to
"h" instead of "r".

¿Por qué la definición está mal formada, aunque res claramente un subterráneo l? ¿Hay alguna manera de definir funciones recursivas en dicha estructura de datos?


Si no tiene fluidez en la sintaxis de Coq: LTreees un tipo inductivo que corresponde a la siguiente gramática.

LTrmimi:: =El |lyost(LTrmimi)

Intentamos definir la sizefunción por inducción sobre árboles y listas. En OCaml, eso sería:

type t = Node of t list
let rec size = function Node l -> 1 + size_l l
and size_l = function [] -> 0
                    | h::r -> size h + size_l r
Gilles 'SO- deja de ser malvado'
fuente
¿Esto es sobre el tema? No estoy seguro; Discutamos esto en Meta .
Gilles 'SO- deja de ser malvado'
¿Puedes agregar definiciones de funciones equivalentes en algo menos Coqy y más mathy? Tengo problemas para entender la sintaxis.
Raphael
1
@Raphael Lo he intentado, ¿está mejor ahora? Honestamente, esta pregunta es bastante específica para Coq.
Gilles 'SO- deja de ser malvado'

Respuestas:

14

Que funciona

Si anida la definición del punto de fijación en las listas dentro de la definición del punto de fijación en los árboles, el resultado está bien escrito. Este es un principio general cuando tiene recursión anidada en un tipo inductivo, es decir, cuando la recursión pasa por un constructor como list.

Fixpoint size (t : LTree) : nat :=
  let size_l := (fix size_l (l : list LTree) : nat :=
                  match l with
                    | nil => 0
                    | h::r => size h + size_l r
                  end) in
  match t with Node l =>
    1 + size_l l
  end.

O si prefiere escribir esto más brevemente:

Fixpoint size (t : LTree) : nat :=
  match t with Node l =>
    1 + (fix size_l (l : list LTree) : nat :=
          match l with
            | nil => 0
            | h::r => size h + size_l r
          end) l
  end.

(No tengo idea de quién lo escuché primero; esto ciertamente se descubrió independientemente muchas veces).

Un predicado de recursión general

De manera más general, puede definir el principio de inducción "adecuado" LTreemanualmente. El principio de inducción generado automáticamente LTree_rectomite la hipótesis en la lista, porque el generador del principio de inducción solo comprende las ocurrencias estrictamente positivas no anidadas del tipo inductivo.

LTree_rect = 
fun (P : LTree -> Type) (f : forall l : list LTree, P (Node l)) (l : LTree) =>
match l as l0 return (P l0) with
| Node x => f x
end
     : forall P : LTree -> Type,
       (forall l : list LTree, P (Node l)) -> forall l : LTree, P l

Agreguemos la hipótesis de inducción en las listas. Para cumplirlo en la llamada recursiva, llamamos al principio de inducción de la lista y lo pasamos al principio de inducción del árbol en el árbol más pequeño dentro de la lista.

Fixpoint LTree_rect_nest (P : LTree -> Type) (Q : list LTree -> Type)
                         (f : forall l, Q l -> P (Node l))
                         (g : Q nil) (h : forall t l, P t -> Q l -> Q (cons t l))
                         (t : LTree) :=
  match t as t0 return (P t0) with
    | Node l => f l (list_rect Q g (fun u r => h u r (LTree_rect_nest P Q f g h u)) l)
  end.

Por qué

La respuesta a por qué radica en las reglas precisas para aceptar funciones recursivas. Estas reglas son forzosamente sutiles, porque hay un delicado equilibrio entre permitir casos complejos (como este, con recursión anidada en el tipo de datos) y falta de solidez. El manual de referencia de Coq presenta el lenguaje (el cálculo de las construcciones inductivas, que es el lenguaje de prueba de Coq), principalmente con definiciones formalmente precisas, pero si desea las reglas exactas con respecto a la inducción y la coinducción necesitará ir a los trabajos de investigación, sobre este tema de Eduardo Giménez [1].

FixFyoXFyo{F1:UNA1: =t1;F2:UNA2: =t2}

Γ1=(X:LTrmimi)UNA1=norteunatt1=dounasmi(X,LTrmimi,λy.sol1(F2y))Γ2=(l:lyostLTrmimi)UNA2=norteunatt2=dounasmi(l,lyostLTrmimi,λhr.sol2(F1h)(F2r))

FjtyoFyo

  • yo=1j=2ltsize
  • yo=2j=1hlsize_l
  • yo=2j=2rlsize_l

La razón por la cual hno es estructuralmente más pequeña que lsegún el intérprete de Coq no me resulta clara. Según tengo entendido por las discusiones sobre la lista de Coq-club [1] [2], esta es una restricción en el intérprete, que en principio podría eliminarse, pero con mucho cuidado para evitar la introducción de una inconsistencia.

Referencias

Cocorico, el wiki de Coq no terminante: inducción mutua

Lista de correo de Coq-Club:

El equipo de desarrollo de Coq. The Coq Proof Assistant: Manual de referencia . Versión 8.3 (2010). [ web ] cap. 4 .

Eduardo Giménez. Codificación de definiciones guardadas con esquemas recursivos . En Tipos'94: Tipos de pruebas y programas , LNCS 996. Springer-Verlag, 1994. doi: 10.1007 / 3-540-60579-7_3 [ Springer ]

Eduardo Giménez. Definiciones recursivas estructurales en la teoría de tipos . En ICALP'98: Actas del 25º Coloquio internacional sobre autómatas, idiomas y programación. Springer-Verlag, 1998. [ PDF ]

Gilles 'SO- deja de ser malvado'
fuente
7

Obviamente, este es un problema específico de Coq, ya que creo que hay formas más agradables de solucionarlo con otros asistentes de prueba (estoy mirando Agda)

Al principio pensé que rno era reconocido como estructuralmente más pequeño porque la estructura solo se trata de la definición inductiva que actualmente maneja Fixpoint: por lo que esto no es un LTreesubterráneo, incluso si es unlist subterráneo.

Pero si expande el procesamiento de la lista, entonces funciona:

Fixpoint size t :=
  match t with
  | Node l => S
     ((fix size_l l :=
     match l with
     | nil => 0
     | cons t l => size_l l + size t
     end) l)
 end.

O como la función auxiliar ya existe en la biblioteca estándar:

Require Import List.

Fixpoint size t :=
  match t with
  | Node l => S (fold_left (fun a t => a + size t) l 0)
  end.

Para ser sincero, no estoy seguro de por qué Coq los acepta, pero estoy feliz de que lo sean.

También hay una solución que funciona con más frecuencia y no solo para las listas: definir un tipo inductivo autónomo. En este caso, puede definir su sizefunción manualmente. (con dos puntos de fijación)

Inductive LTree : Set :=
  | Node : list_LTree -> LTree
with list_LTree : Set :=
  | LTree_nil : list_LTree
  | LTree_cons : LTree -> list_LTree -> list_LTree.

Tenga en cuenta que si tiene problemas para definiciones inductivas más complejas, puede usar un argumento de disminución de tamaño. Eso es posible pero engorroso para este problema (y me atrevería a decir que para la mayoría de los problemas)

jmad
fuente
Lo que todavía no entiendo hoy es por qué el enfoque ingenuo no funciona. En principio, esto debería ser una consecuencia del artículo de Eduardo Giménez, pero no veo dónde se rompe la deducción; Esto puede ser una limitación del motor Coq en lugar del cálculo subyacente.
Gilles 'SO- deja de ser malvado'