Как я прокомментировал, я не знаю общего способа обработки взаимно рекурсивных теорем, но (из моего мелкого опыта) у меня не было необходимости иметь набор взаимно рекурсивных теорем, если они не включают взаимно рекурсивные типы данных или функции (в этом случае я предлагаю Плагин уравнений ).
Этот ответ фокусируется на конкретной проблеме.
Проблема очень близка к последнему упражнению Индукционные упражнения , за исключением того, что программа останавливается, когда инструкции Плюс дается слишком мало аргументов (что делает задачу немного сложнее).
Мы начнем с определений @ larsr :
Require Import Coq.Lists.List.
Import ListNotations.
Inductive Sinstr : Set := SPush (_:nat) | SPlus.
Inductive Aexp : Set := ANum (_:nat) | APlus (_ _:Aexp).
Fixpoint sexec (p:list Sinstr) (s:list nat) : list nat :=
match (p, s) with
| ([], stack) => stack
| ((SPush x)::prog, stack) => sexec prog (x::stack)
| (Splus::prog, x1::x2::stack) => sexec prog (x1+x2::stack)
| (_, stack) => stack
end.
Fixpoint aeval (a:Aexp) : nat :=
match a with
| ANum n => n
| APlus a1 a2 => aeval a2 + aeval a1
end.
Fixpoint compile (a:Aexp) : list Sinstr :=
match a with
| ANum n => [SPush n]
| APlus a1 a2 => compile a1 ++ compile a2 ++ [SPlus]
end.
Сначала мы попробуем доказательство по индукции напрямую:
Theorem compile_correct_try e : sexec (compile e) [] = [aeval e].
Proof.
induction e; intros; auto. (* base case is easy *)
simpl.
e1, e2 : Aexp
IHe1 : sexec (compile e1) [] = [aeval e1]
IHe2 : sexec (compile e2) [] = [aeval e2]
____________________________________________
sexec (compile e1 ++ compile e2 ++ [SPlus]) [] = [aeval e2 + aeval e1]
Мы застряли на этом этапе. Мы можем сделать некоторые замечания, хотя:
- Нам нужна лемма, которая предполагает объединение двух программ (очевидно).
- Нам также нужна лемма, которая применяется к любому начальному стеку (поскольку
compile e2
будет работать в стеке [aeval e1]
, а не []
).
Итак, мы попробуем написать общую лемму:
Lemma prg_concat :
forall p1 p2 stack, sexec (p1 ++ p2) stack = sexec p2 (sexec p1 stack).
Но это просто неправильно, потому что p2
не должен запускаться, если p1
был прерван. Тогда мы должны убедиться, что p1
не прерывается. Кто-то может захотеть определить опору «выполняется до завершения», но у нас есть очевидный особый случай, который работает: compile e
. И это идеально подходит для нашего индуктивного случая, потому что левые операнды на ++
имеют форму compile _
:
sexec (compile e1 ++ compile e2 ++ [SPlus]) stack
->
sexec (compile e2 ++ [SPlus]) (sexec (compile e1) stack)
->
sexec [SPlus] (sexec (compile e2) (sexec (compile e1) stack))
Соответствующее утверждение:
Lemma compile_concat :
forall e p s, sexec (compile e ++ p) s = sexec p (sexec (compile e) s).
Но этого все еще недостаточно, потому что нет гарантии, что окончательный SPlus
будет успешным. Поэтому мы включаем в лемму основную цель sexec (compile e) = [aeval e]
, то есть вместо sexec (compile e) s
пишем aeval e :: s
. Теперь мы можем гарантировать, что в стеке будет по крайней мере два элемента, когда мы подойдем к финалу SPlus
.
Итак, вот лемма Ларса:
Lemma compile_ok (e:Aexp):
forall p s, sexec (compile e ++ p) s = sexec p (aeval e::s).
Proof.
induction e.
reflexivity.
intros; simpl;
rewrite <-? app_assoc, IHe1, IHe2; reflexivity.
Qed.
Также, здесь - документация rewrite <-? expr
, в конце раздела rewrite
:
Ориентацию ->
или <-
можно вставить перед каждым термином для перезаписи.
Во всех описанных выше формах перезаписи термину для переписывания может быть немедленно предшествен один из следующих модификаторов:
?
: тактика rewrite ?term
выполняет перезапись терма столько раз, сколько возможно (возможно, нулевое время). Эта форма никогда не подведет.
То есть rewrite <-? app_assoc, IHe1, IHe2.
означает повторение (обратное) перезапись на app_assoc
, а затем (прямое) перезапись на IHe1
и IHe2
по одному разу.