Smallstep: スモールステップ操作的意味論
Set Warnings "-notation-overridden,-parsing".
From Coq Require Import Arith.Arith.
From Coq Require Import Arith.EqNat.
From Coq Require Import Init.Nat.
From Coq Require Import omega.Omega.
From Coq Require Import Lists.List.
Import ListNotations.
From PLF Require Import Maps.
From PLF Require Import Imp.
ここまで見てきた評価器(例えばaexp用、bexp用、コマンド用、などなど)は「ビッグステップスタイル」で記述されてきました。
つまり、与えられた式がどのように最終的な値になるか(またはコマンドと記憶状態の組がどのように最終記憶状態になるか)を「すべて大きな1ステップ」として決定しました。
このスタイルは単純で多くの目的のために自然な方法です。
実際、この手法を広めた Gilles Kahn は、これを「自然意味論(natural semantics)」と呼びました。
しかし、これではうまくいかないときもあります。
特に、この方法は、並列プログラミング言語について言及する際には自然ではありません。
並列プログラミング言語の場合、プログラムの「意味」、つまりプログラムがどのように振る舞うかの本質は、入力状態が出力状態にどのように写像されるかだけではなく、途中で通過する状態も含みます。
なぜなら、中間状態は並列実行される他のコードからも観測されるからです。
ビッグステップスタイルのもう1つの欠点は、より技術的なことですが、ある種のアプリケーションには致命的です。
変数が数値だけでなく数値のリストにも束縛されうるImpの変種を考えましょう。
構文的には、この言語では2 + nilのような変な式を書くことができます。
そして、意味論上では、この式がどのように動くのかについて言及する必要があります。
一つの動き方としては、リストを適当な数値として扱うことで、全ての算術式を数値に評価するものがあります。
例えば数値を期待する箇所ではリストを常に0として扱う、といった扱い方があります。
ただ、この方法は少し奇抜な方法です。
より自然な方法としては、2+nilのような式の動き方を「未定義(undefined)」としてしまうことです。
つまりどのような結果にも評価されないということです。
これは簡単に実現できます。
aevalとbevalを、Fixpoint ではなく帰納的(Inductive)命題として定義するだけです。
すると、全関数ではなく部分関数にすることができます。
しかしながら、この方法で Imp を定義することには深刻な欠陥があります。
この言語では、コマンドが初期状態を終了状態に写像するのに失敗した際に、「まったく異なる」2種類の原因があり得ます。
1つは評価が無限ループに陥ることによるもの、もう1つは、どこかの地点でプログラムが、数値をリストに加えるなどの意味のない操作をしようとして、どの評価規則も適用できなくなることによるものです。
この2つの結果、つまり「停止しないこと」と「間違った設定によって行き詰まること」をごちゃ混ぜにしないでください。
特に、1つ目は「許容」したい(無限ループの可能性を許すことは、プログラミングに while のような一般的ループ構造を使う便利さの代償です)一方で、2つ目(これはただの間違いです)は「禁止」したいのです。
これは例えば言語に何らかの「型チェック(typechecking)」を追加することで実現できます。
実のところ、これはこのコースの残りの部分の主要なトピックです。
まずは、停止しないことと、間違いによる「行き詰まり状態」を区別することができる別の意味提示方法が必要です。
このように、いろいろな理由で、プログラムの振る舞いを定義し推論するよりきめの細かい方法が欲しいことが度々あります。
これがこの章のトピックです。
目標は、与えられたプログラムに対して計算の「アトミックなステップ」がどのように行われるかを定める「スモールステップ」の関係によって、「ビッグステップ」のeval関係を置き換えることです。
無駄な議論を省くため、定数と足し算だけの極端に単純な言語に戻りましょう。
(可読性のために、定数と足し算を表すコンストラクタにCとPという一文字の名前を用います。)
この章の終わりには、同じテクニックをImp言語全体に適用する方法がわかるでしょう。
次がこの言語の標準的な評価器です。
ここまでやってきたのと同じビッグステップスタイルで記述されています。
同じ評価器を、まったく同じスタイルながら、帰納的に定義された関係によって定式化したものです。
ここでは「tがnに評価される」を記法 t ==> n で表しています。
--------- (E_Const) C n ==> n t1 ==> n1 t2 ==> n2 ------------------- (E_Plus) P t1 t2 ==> n1 + n2
Reserved Notation " t '==>' n " (at level 50, left associativity).
Inductive eval : tm -> nat -> Prop :=
| E_Const : forall n,
C n ==> n
| E_Plus : forall t1 t2 n1 n2,
t1 ==> n1 ->
t2 ==> n2 ->
P t1 t2 ==> (n1 + n2)
where " t '==>' n " := (eval t n).
Module SimpleArith1.
そして、次が対応する「スモールステップ」版です。
------------------------------- (ST_PlusConstConst) P (C n1) (C n2) --> C (n1 + n2) t1 --> t1' -------------------- (ST_Plus1) P t1 t2 --> P t1' t2 t2 --> t2' ---------------------------- (ST_Plus2) P (C n1) t2 --> P (C n1) t2'
Reserved Notation " t '-->' t' " (at level 40).
Inductive step : tm -> tm -> Prop :=
| ST_PlusConstConst : forall n1 n2,
P (C n1) (C n2) --> C (n1 + n2)
| ST_Plus1 : forall t1 t1' t2,
t1 --> t1' ->
P t1 t2 --> P t1' t2
| ST_Plus2 : forall n1 t2 t2',
t2 --> t2' ->
P (C n1) t2 --> P (C n1) t2'
where " t '-->' t' " := (step t t').
注目すること:
Let's pause and check a couple of examples of reasoning with
the step relation...
- 定義しているのは簡約のちょうど1ステップです。
そこでは1つのPノードがその値に置き換えられます。
- 各ステップでは「最左」の準備ができている(つまり、引数が両方とも定数である)Pノードを探して、それをその場で書き換えます。
最初の規則はPノードをどのように書き換えるかを定めます。
残りの2つの規則は、それをどう探すかを定めます。
- 定数の項は、ステップを進めません。
Example test_step_1 :
P
(P (C 0) (C 3))
(P (C 2) (C 4))
-->
P
(C (0 + 3))
(P (C 2) (C 4)).
Proof.
apply ST_Plus1. apply ST_PlusConstConst. Qed.
練習問題: ★, standard (test_step_2)
Example test_step_2 :
P
(C 0)
(P
(C 2)
(P (C 0) (C 3)))
-->
P
(C 0)
(P
(C 2)
(C (0 + 3))).
Proof.
Admitted.
☐
Relations
Our main examples of such relations in this chapter will be
the single-step reduction relation, -->, and its multi-step
variant, -->* (defined below), but there are many other
examples -- e.g., the "equals," "less than," "less than or equal
to," and "is the square of" relations on numbers, and the "prefix
of" relation on lists and strings.
関係 --> の性質の1つは、Imp プログラムの言語の大ステップ評価関係と同様、決定性を持つ(deterministic)ということです。
「定理」: 各tに対して、tが1ステップでt'になる(t --> t' が証明可能な)t'は高々1つである。
「証明スケッチ」:xが1ステップでy1とy2のどちらにもなるとき、y1とy2
が等しいことを、step x y1 の導出についての帰納法で示す。
この導出と step x y2 の導出のそれぞれで使われた最後の規則によって、
いくつかの場合がある。
Formally:
- もし両者とも ST_PlusConstConst ならば、一致は明らかである。
- 導出が両者とも ST_Plus1 または ST_Plus2 で終わるならば、帰納法の仮定から成立する。
- 一方が ST_PlusConstConst で、他方が ST_Plus1 または ST_Plus2 であることはあり得ない。
なぜなら、そうなるためには、 x が P t1 t2 の形で(ST_PlusConstConstより)t1とt2が両者とも定数であり、「かつ」t1またはt2が P _ の形でなければならないためである。
- 同様に、一方が ST_Plus1 で他方が ST_Plus2 であることもあり得ない。 なぜなら、そのためには、x は P t1 t2 の形で、 t1 が P t11 t12 の形であると同時に C n の形でもなければならないからである。 ☐
Definition deterministic {X : Type} (R : relation X) :=
forall x y1 y2 : X, R x y1 -> R x y2 -> y1 = y2.
Module SimpleArith2.
Import SimpleArith1.
Theorem step_deterministic:
deterministic step.
Proof.
unfold deterministic. intros x y1 y2 Hy1 Hy2.
generalize dependent y2.
induction Hy1; intros y2 Hy2.
- inversion Hy2.
+ reflexivity.
+ inversion H2.
+ inversion H2.
- inversion Hy2.
+
rewrite <- H0 in Hy1. inversion Hy1.
+
rewrite <- (IHHy1 t1'0).
reflexivity. assumption.
+
rewrite <- H in Hy1. inversion Hy1.
- inversion Hy2.
+
rewrite <- H1 in Hy1. inversion Hy1.
+ inversion H2.
+
rewrite <- (IHHy1 t2'0).
reflexivity. assumption.
Qed.
End SimpleArith2.
There is some annoying repetition in this proof. Each use of
inversion Hy2 results in three subcases, only one of which is
relevant (the one that matches the current case in the induction
on Hy1). The other two subcases need to be dismissed by finding
the contradiction among the hypotheses and doing inversion on it.
The following custom tactic, called solve_by_inverts, can be
helpful in such cases. It will solve the goal if it can be solved
by inverting some hypothesis; otherwise, it fails.
Ltac solve_by_inverts n :=
match goal with | H : ?T |- _ =>
match type of T with Prop =>
solve [
inversion H;
match n with S (S (?n')) => subst; solve_by_inverts (S n') end ]
end end.
The details of how this works are not important for now, but it
illustrates the power of Coq's Ltac language for
programmatically defining special-purpose tactics. It looks
through the current proof state for a hypothesis H (the first
match) of type Prop (the second match) such that performing
inversion on H (followed by a recursive invocation of the same
tactic, if its argument n is greater than one) completely solves
the current goal. If no such hypothesis exists, it fails.
We will usually want to call solve_by_inverts with argument
1 (especially as larger arguments can lead to very slow proof
checking), so we define solve_by_invert as a shorthand for this
case.
Ltac solve_by_invert :=
solve_by_inverts 1.
Let's see how a proof of the previous theorem can be simplified
using this tactic...
Module SimpleArith3.
Import SimpleArith1.
Theorem step_deterministic_alt: deterministic step.
Proof.
intros x y1 y2 Hy1 Hy2.
generalize dependent y2.
induction Hy1; intros y2 Hy2;
inversion Hy2; subst; try solve_by_invert.
- reflexivity.
-
apply IHHy1 in H2. rewrite H2. reflexivity.
-
apply IHHy1 in H2. rewrite H2. reflexivity.
Qed.
End SimpleArith3.
次に、1ステップ簡約を再定義して、「値」であることを明示するようにしましょう。
関係 --> が抽象機械(abstract machine)の定義と考えると便利です:
- どの時点でも、機械の状態(state)は項です。
- 機械のステップ(step)は、計算のアトミックな単位です。ここでは、1つの加算処理です。
- 機械の停止状態(halting states)は、さらなる計算が存在しない状態です。
このとき、項tは以下のように評価できます:
値の概念を導入したので、これを --> 関係の定義に使うことで、
ST_Plus2 規則をもう少しだけきれいなものにできます:
------------------------------- (ST_PlusConstConst) P (C n1) (C n2) --> C (n1 + n2) t1 --> t1' -------------------- (ST_Plus1) P t1 t2 --> P t1' t2 value v1 t2 --> t2' -------------------- (ST_Plus2) P v1 t2 --> P v1 t2'
Reserved Notation " t '-->' t' " (at level 40).
Inductive step : tm -> tm -> Prop :=
| ST_PlusConstConst : forall n1 n2,
P (C n1) (C n2)
--> C (n1 + n2)
| ST_Plus1 : forall t1 t1' t2,
t1 --> t1' ->
P t1 t2 --> P t1' t2
| ST_Plus2 : forall v1 t2 t2',
value v1 ->
t2 --> t2' ->
P v1 t2 --> P v1 t2'
where " t '-->' t' " := (step t t').
練習問題: ★★★, standard, recommended (redo_determinacy)
- もし両者ともST_PlusConstConstならば、一致は明らかである。
- 一方が ST_PlusConstConst で、他方が ST_Plus1 または ST_Plus2
であることはあり得ない。なぜなら、そうなるためには、
x が P t1 t2 の形で(ST_PlusConstConstより)
t1とt2が両者とも定数であり、かつt1またはt2が P _ の形でなければならない。
- 同様に、一方が ST_Plus1 で他方が ST_Plus2 であることもあり得ない。
なぜなら、そのためには、x が P t1 t2 の形で、
t1 は P t11 t12 の形であり、かつ値でもなければならない
(つまり C n の形でもある)からである。
- 導出が両者とも ST_Plus1 または ST_Plus2 で終わるならば、 帰納法の仮定から成立する。☐
証明のほとんどは前のものと同じです。しかし、練習問題の効果を最大にするために、
ゼロから形式的証明を書き、前のものを見るのは行き詰まった時だけにしなさい。
☐
おもちゃの言語に対する1ステップの簡約の定義はかなり単純です。
しかし、より大きな言語に対しては、何か規則を忘れてしまうことは簡単に起き、
項が完全に値に簡約されていないのにステップを進めなくなってしまうことが発生します。
次の定理は、このような間違いをしていないことを示します。
「証明」: tについての帰納法で証明する。
形式的には次の通りです。
- t = C n とする。すると、t は値である。
- t = P t1 t2 と仮定する。ここで(帰納仮定から)
t1は値であるか、1ステップであるt1'になり、また、t2は値であるか、
1ステップであるt2'になる。
ここで必要なのは、P t1 t2が値であるか、
あるt'に1ステップで進むということを示すことである。
Theorem strong_progress : forall t,
value t \/ (exists t', t --> t').
Proof.
induction t.
- left. apply v_const.
- right. destruct IHt1 as [IHt1 | [t1' Ht1]].
+ destruct IHt2 as [IHt2 | [t2' Ht2]].
* inversion IHt1. inversion IHt2.
exists (C (n + n0)).
apply ST_PlusConstConst.
*
exists (P t1 t2').
apply ST_Plus2. apply IHt1. apply Ht2.
+
exists (P t1' t2).
apply ST_Plus1. apply Ht1.
Qed.
この重要な性質は「強進行」(strong progress)と呼ばれます。
これは、すべての項が値であるか、別の項に「進行できる」("make progress")
ことからきた名称です。「強」("strong")という修飾句は、
後の章のより細分されたバージョン(単に「進行」("progress")と呼ばれる)
と区別するためのものです。
「進行する」という概念の拡張から、value(値)についての興味深い性質がわかります:
値とはこの意味で進行「できない」項のことに他なりません。
この観察結果を形式的に述べるために、進行できない項に名前をつけましょう。そういう項を「正規形(normal form)」と呼びます。
この定義は実際には、任意の集合Xの上の「任意の」関係Rについて、
何が正規形であるかを定めています。
今興味対象としている、項の上の特定の1ステップ簡約関係に限るものではありません。
このコースで後に、別の関係の議論において同じ用語法を用います。
強進行定理の洞察を一般化するためにこの用語を使います。
この言語では、正規形と値とは実質的に同じものです。
Lemma value_is_nf : forall v,
value v -> normal_form step v.
Proof.
unfold normal_form. intros v H. inversion H.
intros contra. inversion contra. inversion H1.
Qed.
Lemma nf_is_value : forall t,
normal_form step t -> value t.
Proof. unfold normal_form. intros t H.
assert (G : value t \/ exists t', t --> t').
{ apply strong_progress. }
destruct G as [G | G].
- apply G.
- exfalso. apply H. assumption.
Qed.
Corollary nf_same_as_value : forall t,
normal_form step t <-> value t.
Proof.
split. apply nf_is_value. apply value_is_nf.
Qed.
なぜこれが興味深いのでしょう?
なぜならvalue(値)は構文的概念です。つまり項の形を見ることで定義されます。
一方normal_form(正規形)は意味論的なものです。
つまり項がどのようにステップを進むかによって定義されます。
この2つの概念が一致することは自明ではないのです!
実際、正規形と値の概念が一致「しない」定義を誤って書くことが簡単に起こりえます。
練習問題: ★★★, standard, optional (value_not_same_as_normal_form1)
Module Temp1.
Inductive value : tm -> Prop :=
| v_const : forall n, value (C n)
| v_funny : forall t1 n2,
value (P t1 (C n2)).
Reserved Notation " t '-->' t' " (at level 40).
Inductive step : tm -> tm -> Prop :=
| ST_PlusConstConst : forall n1 n2,
P (C n1) (C n2) --> C (n1 + n2)
| ST_Plus1 : forall t1 t1' t2,
t1 --> t1' ->
P t1 t2 --> P t1' t2
| ST_Plus2 : forall v1 t2 t2',
value v1 ->
t2 --> t2' ->
P v1 t2 --> P v1 t2'
where " t '-->' t' " := (step t t').
Lemma value_not_same_as_normal_form :
exists v, value v /\ ~ normal_form step v.
Proof.
Admitted.
End Temp1.
☐
練習問題: ★★, standard, optional (value_not_same_as_normal_form2)
Module Temp2.
Inductive value : tm -> Prop :=
| v_const : forall n, value (C n).
Reserved Notation " t '-->' t' " (at level 40).
Inductive step : tm -> tm -> Prop :=
| ST_Funny : forall n,
C n --> P (C n) (C 0)
| ST_PlusConstConst : forall n1 n2,
P (C n1) (C n2) --> C (n1 + n2)
| ST_Plus1 : forall t1 t1' t2,
t1 --> t1' ->
P t1 t2 --> P t1' t2
| ST_Plus2 : forall v1 t2 t2',
value v1 ->
t2 --> t2' ->
P v1 t2 --> P v1 t2'
where " t '-->' t' " := (step t t').
Lemma value_not_same_as_normal_form :
exists v, value v /\ ~ normal_form step v.
Proof.
Admitted.
End Temp2.
☐
練習問題: ★★★, standard, optional (value_not_same_as_normal_form3)
Module Temp3.
Inductive value : tm -> Prop :=
| v_const : forall n, value (C n).
Reserved Notation " t '-->' t' " (at level 40).
Inductive step : tm -> tm -> Prop :=
| ST_PlusConstConst : forall n1 n2,
P (C n1) (C n2) --> C (n1 + n2)
| ST_Plus1 : forall t1 t1' t2,
t1 --> t1' ->
P t1 t2 --> P t1' t2
where " t '-->' t' " := (step t t').
(ST_Plus2 がないことに注意します。)
Lemma value_not_same_as_normal_form :
exists t, ~ value t /\ normal_form step t.
Proof.
Admitted.
End Temp3.
☐
以下は、別の非常に簡単な言語です。項は、加算式と数の代わりに、
真理値 true と false、および条件式です...
Inductive tm : Type :=
| tru : tm
| fls : tm
| test : tm -> tm -> tm -> tm.
Inductive value : tm -> Prop :=
| v_tru : value tru
| v_fls : value fls.
Reserved Notation " t '-->' t' " (at level 40).
Inductive step : tm -> tm -> Prop :=
| ST_IfTrue : forall t1 t2,
test tru t1 t2 --> t1
| ST_IfFalse : forall t1 t2,
test fls t1 t2 --> t2
| ST_If : forall t1 t1' t2 t3,
t1 --> t1' ->
test t1 t2 t3 --> test t1' t2 t3
where " t '-->' t' " := (step t t').
練習問題: ★, standard (smallstep_bools)
Definition bool_step_prop1 :=
fls --> fls.
Definition bool_step_prop2 :=
test
tru
(test tru tru tru)
(test fls fls fls)
-->
tru.
Definition bool_step_prop3 :=
test
(test tru tru tru)
(test tru tru tru)
fls
-->
test
tru
(test tru tru tru)
fls.
Definition manual_grade_for_smallstep_bools : option (nat*string) := None.
☐
☐
☐
練習問題: ★★ (smallstep_bool_shortcut)
test
(test tru tru tru)
fls
fls
-->
fls
ステップ関係にこのための追加の節を記述し、bool_step_prop4を証明しなさい。
Reserved Notation " t '-->' t' " (at level 40).
Inductive step : tm -> tm -> Prop :=
| ST_IfTrue : forall t1 t2,
test tru t1 t2 --> t1
| ST_IfFalse : forall t1 t2,
test fls t1 t2 --> t2
| ST_If : forall t1 t1' t2 t3,
t1 --> t1' ->
test t1 t2 t3 --> test t1' t2 t3
where " t '-->' t' " := (step t t').
Definition bool_step_prop4 :=
test
(test tru tru tru)
fls
fls
-->
fls.
Example bool_step_prop4_holds :
bool_step_prop4.
Proof.
Admitted.
☐
練習問題: ★★★, standard, optional (properties_of_altered_step)
- step関係はそれでも決定性を持つでしょうか?
yes または no と書き、簡潔に(1文で)その答えを説明しなさい。
ここに答えを書きなさい。
- 強進行定理は成立するでしょうか?
yes または no と書き、簡潔に(1文で)その答えを説明しなさい。
ここに答えを書きなさい。
☐
- 一般に、オリジナルのステップ関係から1つ以上のコンストラクタを取り除いて、 強進行性を持たなくする方法はあるでしょうか? yes または no と書き、簡潔に(1文で)その答えを説明しなさい。
ここまでは、1ステップ簡約関係 --> に取り組んできました。
これは、プログラムを実行する抽象機械の1つのステップを形式化したものです。
この機械を使って、プログラムを完全に簡約してみるのもおもしろいでしょう。
つまり、その最後の結果がどうなるかを調べることです。
これは以下のように形式化できます:
Since we'll want to reuse the idea of multi-step reduction many
times, let's take a little extra trouble and define it
generically.
Given a relation R (which will be --> for present purposes),
we define a relation multi R, called the multi-step closure
of R as follows.
- 最初に、「マルチステップ簡約関係」(multi-step reduction relation) -->* を定義します。
この関係は、tから1ステップ簡約を何回か(0回も含みます)行うことでt'に到達できるとき、
tとt'を関係づけるものです。
- 次に項tの結果("result")を、 tがマルチステップ簡約で到達できる正規形tとして定義します。
Inductive multi {X : Type} (R : relation X) : relation X :=
| multi_refl : forall (x : X), multi R x x
| multi_step : forall (x y z : X),
R x y ->
multi R y z ->
multi R x z.
(In the Rel chapter of Logical Foundations and
the Coq standard library, this relation is called
clos_refl_trans_1n. We give it a shorter name here for the sake
of readability.)
The effect of this definition is that multi R relates two
elements x and y if
Thus, if R describes a single-step of computation, then z1 ... zn
is the sequence of intermediate steps of computation between x and
y.
We write -->* for the multi step relation on terms.
- x = y, or
- R x y, or
- there is some nonempty sequence z1, z2, ..., zn such that
The relation multi R has several crucial properties.
First, it is obviously reflexive (that is, forall x, multi R x
x). In the case of the -->* (i.e., multi step) relation, the
intuition is that a term can execute to itself by taking zero
steps of execution.
Second, it contains R -- that is, single-step executions are a
particular case of multi-step executions. (It is this fact that
justifies the word "closure" in the term "multi-step closure of
R.")
Theorem multi_R : forall (X : Type) (R : relation X) (x y : X),
R x y -> (multi R) x y.
Proof.
intros X R x y H.
apply multi_step with y. apply H. apply multi_refl.
Qed.
Theorem multi_trans :
forall (X : Type) (R : relation X) (x y z : X),
multi R x y ->
multi R y z ->
multi R x z.
Proof.
intros X R x y z G H.
induction G.
- assumption.
-
apply multi_step with y. assumption.
apply IHG. assumption.
Qed.
Lemma test_multistep_1:
P
(P (C 0) (C 3))
(P (C 2) (C 4))
-->*
C ((0 + 3) + (2 + 4)).
Proof.
apply multi_step with
(P (C (0 + 3))
(P (C 2) (C 4))).
{ apply ST_Plus1. apply ST_PlusConstConst. }
apply multi_step with
(P (C (0 + 3))
(C (2 + 4))).
{ apply ST_Plus2. apply v_const. apply ST_PlusConstConst. }
apply multi_R.
{ apply ST_PlusConstConst. }
Qed.
以下は別証です。
eapplyを使うことで、すべての中間的な項を明示的に構成する必要をなくしたものです。
Lemma test_multistep_1':
P
(P (C 0) (C 3))
(P (C 2) (C 4))
-->*
C ((0 + 3) + (2 + 4)).
Proof.
eapply multi_step. { apply ST_Plus1. apply ST_PlusConstConst. }
eapply multi_step. { apply ST_Plus2. apply v_const.
apply ST_PlusConstConst. }
eapply multi_step. { apply ST_PlusConstConst. }
apply multi_refl.
Qed.
☐
☐
Lemma test_multistep_4:
P
(C 0)
(P
(C 2)
(P (C 0) (C 3)))
-->*
P
(C 0)
(C (2 + (0 + 3))).
Proof.
Admitted.
P
(C 0)
(P
(C 2)
(P (C 0) (C 3)))
-->*
P
(C 0)
(C (2 + (0 + 3))).
Proof.
Admitted.
☐
Definition step_normal_form := normal_form step.
Definition normal_form_of (t t' : tm) :=
(t -->* t' /\ step_normal_form t').
この言語が1ステップ簡約が決定性を持つことを既に見ました。
つまり、項が1ステップ進む方法は高々1種類だということです。
このことから、tが正規形に到達するならば、その正規形は1つに決まることになります。
言い換えると、 normal_form t t' を、(「t'はtの正規形である」と読む以外に)
「tの正規形とはt'である」と読んでよいということです。
(訳注:原文では "the normal form of t" と定冠詞を使ってよいことと記述されています。)
Theorem normal_forms_unique:
deterministic normal_form_of.
Proof.
unfold deterministic. unfold normal_form_of.
intros x y1 y2 P1 P2.
inversion P1 as [P11 P12]; clear P1.
inversion P2 as [P21 P22]; clear P2.
generalize dependent y2.
Admitted.
deterministic normal_form_of.
Proof.
unfold deterministic. unfold normal_form_of.
intros x y1 y2 P1 P2.
inversion P1 as [P11 P12]; clear P1.
inversion P2 as [P21 P22]; clear P2.
generalize dependent y2.
Admitted.
☐
実のところ、この言語については、より強いことが成立します
(これは他のすべての言語で成立することではありません):
「任意の」項tはいつかは正規形に到達する、ということです。
つまり normal_form_of は全関数です。
形式的には、step関係は正規化性を持つ(normalizing)と言います。
Definition normalizing {X : Type} (R : relation X) :=
forall t, exists t',
(multi R) t t' /\ normal_form R t'.
stepが正規化性を持つことを証明するため、二つの補題を必要とします。
一つは、tがt'に何ステップかで簡約されるならば、
tが P ノードの左側の子ノードとして現れるときには、
t内で同じ簡約ステップ列が可能である、ということ、
そしてまた同様のことが、
tが(左側の子ノードが値である)P
の右側の子ノードとして現れるときにも言える、ということです。
Lemma multistep_congr_1 : forall t1 t1' t2,
t1 -->* t1' ->
P t1 t2 -->* P t1' t2.
Proof.
intros t1 t1' t2 H. induction H.
- apply multi_refl.
- apply multi_step with (P y t2).
+ apply ST_Plus1. apply H.
+ apply IHmulti.
Qed.
Lemma multistep_congr_2 : forall t1 t2 t2',
value t1 ->
t2 -->* t2' ->
P t1 t2 -->* P t1 t2'.
Proof.
Admitted.
value t1 ->
t2 -->* t2' ->
P t1 t2 -->* P t1 t2'.
Proof.
Admitted.
☐
これらの補題を使えば、後は単純な帰納法で証明できます。
「定理」: step 関数は正規化性を持つ。つまり、
任意のtに対して、あるt'があって、tからステップを進めるとt'に到達し、
かつt'は正規形である、が成立する。
「証明スケッチ」:項についての帰納法を使う。考える対象は2つの場合である:
- あるnについて t = C n である場合。
このとき、tはステップを進めることができないことから、
t' = t である。左辺は反射性から導出され、
右辺は、(a)(nf_same_as_valueより)値は正規形であること、
(b)(v_constより)tは値であること、から導出される。
- あるt1、t2について、t = P t1 t2 である場合。
帰納仮定よりt1とt2はそれぞれ正規形t1'とt2'を持つ。
(nf_same_as_valueより)正規形は値であったから、
ある n1、n2について、t1' = C n1 かつ t2' = C n2
である。t1とt2についての -->* 導出を、 multi_congr_1 と multi_congr_2 によって組合せると、
P t1 t2 が幾つかのステップで C (n1 + n2) に簡約されることを証明できる。
Theorem step_normalizing :
normalizing step.
Proof.
unfold normalizing.
induction t.
-
exists (C n).
split.
+ apply multi_refl.
+
rewrite nf_same_as_value. apply v_const.
-
destruct IHt1 as [t1' [Hsteps1 Hnormal1]].
destruct IHt2 as [t2' [Hsteps2 Hnormal2]].
rewrite nf_same_as_value in Hnormal1.
rewrite nf_same_as_value in Hnormal2.
inversion Hnormal1 as [n1 H1].
inversion Hnormal2 as [n2 H2].
rewrite <- H1 in Hsteps1.
rewrite <- H2 in Hsteps2.
exists (C (n1 + n2)).
split.
+
apply multi_trans with (P (C n1) t2).
* apply multistep_congr_1. apply Hsteps1.
* apply multi_trans with
(P (C n1) (C n2)).
{ apply multistep_congr_2. apply v_const. apply Hsteps2. }
apply multi_R. { apply ST_PlusConstConst. }
+
rewrite nf_same_as_value. apply v_const.
Qed.
小さなプログラミング言語に対して2つの異なった方法(ビッグステップとスモールステップ)で操作的意味論を定義したので、
その2つの定義が本当に同じものを定義しているのかを考えるのは意味があります!
実際に定義は一致しているのですが、それを示すにはもう少し準備が必要です。
詳細は練習問題として残しています。
The key ideas in the proof can be seen in the following picture:
P t1 t2 --> (by ST_Plus1)
P t1' t2 --> (by ST_Plus1)
P t1'' t2 --> (by ST_Plus1)
...
P (C n1) t2 --> (by ST_Plus2)
P (C n1) t2' --> (by ST_Plus2)
P (C n1) t2'' --> (by ST_Plus2)
...
P (C n1) (C n2) --> (by ST_PlusConstConst)
C (n1 + n2)
That is, the multistep reduction of a term of the form P t1 t2
proceeds in three phases:
To formalize this intuition, you'll need to use the congruence
lemmas from above (you might want to review them now, so that
you'll be able to recognize when they are useful), plus some basic
properties of -->*: that it is reflexive, transitive, and
includes -->.
- First, we use ST_Plus1 some number of times to reduce t1 to a normal form, which must (by nf_same_as_value) be a term of the form C n1 for some n1.
- Next, we use ST_Plus2 some number of times to reduce t2 to a normal form, which must again be a term of the form C n2 for some n2.
- Finally, we use ST_PlusConstConst one time to reduce P (C n1) (C n2) to C (n1 + n2).
Proof.
Admitted.
☐
☐
For the other direction, we need one lemma, which establishes a
relation between single-step reduction and big-step evaluation.
Lemma step__eval : forall t t' n,
t --> t' ->
t' ==> n ->
t ==> n.
Proof.
intros t t' n Hs. generalize dependent n.
Admitted.
t --> t' ->
t' ==> n ->
t ==> n.
Proof.
intros t t' n Hs. generalize dependent n.
Admitted.
☐
The fact that small-step reduction implies big-step evaluation is
now straightforward to prove, once it is stated correctly.
The proof proceeds by induction on the multi-step reduction
sequence that is buried in the hypothesis normal_form_of t t'.
Make sure you understand the statement before you start to
work on the proof.
Theorem multistep__eval : forall t t',
normal_form_of t t' -> exists n, t' = C n /\ t ==> n.
Proof.
Admitted.
normal_form_of t t' -> exists n, t' = C n /\ t ==> n.
Proof.
Admitted.
☐
練習問題: ★★★, standard, optional (interp_tm)
☐
Module Combined.
Inductive tm : Type :=
| C : nat -> tm
| P : tm -> tm -> tm
| tru : tm
| fls : tm
| test : tm -> tm -> tm -> tm.
Inductive value : tm -> Prop :=
| v_const : forall n, value (C n)
| v_tru : value tru
| v_fls : value fls.
Reserved Notation " t '-->' t' " (at level 40).
Inductive step : tm -> tm -> Prop :=
| ST_PlusConstConst : forall n1 n2,
P (C n1) (C n2) --> C (n1 + n2)
| ST_Plus1 : forall t1 t1' t2,
t1 --> t1' ->
P t1 t2 --> P t1' t2
| ST_Plus2 : forall v1 t2 t2',
value v1 ->
t2 --> t2' ->
P v1 t2 --> P v1 t2'
| ST_IfTrue : forall t1 t2,
test tru t1 t2 --> t1
| ST_IfFalse : forall t1 t2,
test fls t1 t2 --> t2
| ST_If : forall t1 t1' t2 t3,
t1 --> t1' ->
test t1 t2 t3 --> test t1' t2 t3
where " t '-->' t' " := (step t t').
前には、plus式とif式について、以下のことを別々に証明しました...
結合した言語について、これら二つの性質を形式的に証明、または反証しなさい。
(つまり、定理として性質が成り立つ、または成り立たない、と明言した上でそれを示すのです。)
- ステップ関係が部分関数であること(つまり、決定性を持つこと)。
- すべての項が値であるか、1ステップ進むことができるかを主張する強進行補題。
☐
ここではより本気の例として、Impの操作的意味論のスモールステップ版を示します。
The small-step reduction relations for arithmetic and
boolean expressions are straightforward extensions of the tiny
language we've been working up to now. To make them easier to
read, we introduce the symbolic notations -->a and -->b for
the arithmetic and boolean step relations.
ここでは、わざわざブール値の定義をする必要がありません。
なぜならば、以下の -->b の定義にはブール値の定義が必要ないからです(なぜでしょう?)。
言語がもうちょっと大きかったら必要になったかもしれません(なぜでしょう?)。
Reserved Notation " t '/' st '-->a' t' "
(at level 40, st at level 39).
Inductive astep : state -> aexp -> aexp -> Prop :=
| AS_Id : forall st i,
AId i / st -->a ANum (st i)
| AS_Plus1 : forall st a1 a1' a2,
a1 / st -->a a1' ->
(APlus a1 a2) / st -->a (APlus a1' a2)
| AS_Plus2 : forall st v1 a2 a2',
aval v1 ->
a2 / st -->a a2' ->
(APlus v1 a2) / st -->a (APlus v1 a2')
| AS_Plus : forall st n1 n2,
APlus (ANum n1) (ANum n2) / st -->a ANum (n1 + n2)
| AS_Minus1 : forall st a1 a1' a2,
a1 / st -->a a1' ->
(AMinus a1 a2) / st -->a (AMinus a1' a2)
| AS_Minus2 : forall st v1 a2 a2',
aval v1 ->
a2 / st -->a a2' ->
(AMinus v1 a2) / st -->a (AMinus v1 a2')
| AS_Minus : forall st n1 n2,
(AMinus (ANum n1) (ANum n2)) / st -->a (ANum (minus n1 n2))
| AS_Mult1 : forall st a1 a1' a2,
a1 / st -->a a1' ->
(AMult a1 a2) / st -->a (AMult a1' a2)
| AS_Mult2 : forall st v1 a2 a2',
aval v1 ->
a2 / st -->a a2' ->
(AMult v1 a2) / st -->a (AMult v1 a2')
| AS_Mult : forall st n1 n2,
(AMult (ANum n1) (ANum n2)) / st -->a (ANum (mult n1 n2))
where " t '/' st '-->a' t' " := (astep st t t').
Reserved Notation " t '/' st '-->b' t' "
(at level 40, st at level 39).
Inductive bstep : state -> bexp -> bexp -> Prop :=
| BS_Eq1 : forall st a1 a1' a2,
a1 / st -->a a1' ->
(BEq a1 a2) / st -->b (BEq a1' a2)
| BS_Eq2 : forall st v1 a2 a2',
aval v1 ->
a2 / st -->a a2' ->
(BEq v1 a2) / st -->b (BEq v1 a2')
| BS_Eq : forall st n1 n2,
(BEq (ANum n1) (ANum n2)) / st -->b
(if (n1 =? n2) then BTrue else BFalse)
| BS_LtEq1 : forall st a1 a1' a2,
a1 / st -->a a1' ->
(BLe a1 a2) / st -->b (BLe a1' a2)
| BS_LtEq2 : forall st v1 a2 a2',
aval v1 ->
a2 / st -->a a2' ->
(BLe v1 a2) / st -->b (BLe v1 a2')
| BS_LtEq : forall st n1 n2,
(BLe (ANum n1) (ANum n2)) / st -->b
(if (n1 <=? n2) then BTrue else BFalse)
| BS_NotStep : forall st b1 b1',
b1 / st -->b b1' ->
(BNot b1) / st -->b (BNot b1')
| BS_NotTrue : forall st,
(BNot BTrue) / st -->b BFalse
| BS_NotFalse : forall st,
(BNot BFalse) / st -->b BTrue
| BS_AndTrueStep : forall st b2 b2',
b2 / st -->b b2' ->
(BAnd BTrue b2) / st -->b (BAnd BTrue b2')
| BS_AndStep : forall st b1 b1' b2,
b1 / st -->b b1' ->
(BAnd b1 b2) / st -->b (BAnd b1' b2)
| BS_AndTrueTrue : forall st,
(BAnd BTrue BTrue) / st -->b BTrue
| BS_AndTrueFalse : forall st,
(BAnd BTrue BFalse) / st -->b BFalse
| BS_AndFalse : forall st b2,
(BAnd BFalse b2) / st -->b BFalse
where " t '/' st '-->b' t' " := (bstep st t t').
コマンドの意味論は興味深い部分です。
うまくやるために2つの小さなトリックを使います:
- SKIP を「コマンド値」("command value")として使います。
つまり、正規形に逹したコマンドです。
- 代入コマンドはSKIPに簡約されます(その際に状態を更新します)。
- コマンド合成は、左側の部分コマンドがSKIPに簡約されるのを待って、
それを捨ててしまいます。そして続けて右側の部分コマンドの簡約をします。
- 代入コマンドはSKIPに簡約されます(その際に状態を更新します)。
- WHILEコマンドの簡約は、条件文とそれに続く同じWHILEコマンドになります。
(後者の効果を得るには他にもいろいろな方法がありますが、
いずれも、もとのWHILEコマンドをどこかに保存して、ループ本体の1コピーを簡約することには変わりありません。)
Reserved Notation " t '/' st '-->' t' '/' st' "
(at level 40, st at level 39, t' at level 39).
Open Scope imp_scope.
Inductive cstep : (com * state) -> (com * state) -> Prop :=
| CS_AssStep : forall st i a a',
a / st -->a a' ->
(i ::= a) / st --> (i ::= a') / st
| CS_Ass : forall st i n,
(i ::= (ANum n)) / st --> SKIP / (i !-> n ; st)
| CS_SeqStep : forall st c1 c1' st' c2,
c1 / st --> c1' / st' ->
(c1 ;; c2) / st --> (c1' ;; c2) / st'
| CS_SeqFinish : forall st c2,
(SKIP ;; c2) / st --> c2 / st
| CS_IfStep : forall st b b' c1 c2,
b / st -->b b' ->
TEST b THEN c1 ELSE c2 FI / st
-->
(TEST b' THEN c1 ELSE c2 FI) / st
| CS_IfTrue : forall st c1 c2,
TEST BTrue THEN c1 ELSE c2 FI / st --> c1 / st
| CS_IfFalse : forall st c1 c2,
TEST BFalse THEN c1 ELSE c2 FI / st --> c2 / st
| CS_While : forall st b c1,
WHILE b DO c1 END / st
-->
(TEST b THEN c1;; WHILE b DO c1 END ELSE SKIP FI) / st
where " t '/' st '-->' t' '/' st' " := (cstep (t,st) (t',st')).
Close Scope imp_scope.
最後に、この定義スタイルの力を示すために、Imp に並行動作のコマンドを拡張しましょう。
このコマンドは2つのサブコマンドを並行実行し、両者が終了した時点で終了します。
スケジューリングの予測不能性を反映して、
サブコマンドのアクションは任意の順序でインターリーブします。
しかし、同じメモリをシェアし、同じ変数を読み書きすることでコミュニケーションできます。
Module CImp.
Inductive com : Type :=
| CSkip : com
| CAss : string -> aexp -> com
| CSeq : com -> com -> com
| CIf : bexp -> com -> com -> com
| CWhile : bexp -> com -> com
| CPar : com -> com -> com.
Notation "'SKIP'" :=
CSkip.
Notation "x '::=' a" :=
(CAss x a) (at level 60).
Notation "c1 ;; c2" :=
(CSeq c1 c2) (at level 80, right associativity).
Notation "'WHILE' b 'DO' c 'END'" :=
(CWhile b c) (at level 80, right associativity).
Notation "'TEST' b 'THEN' c1 'ELSE' c2 'FI'" :=
(CIf b c1 c2) (at level 80, right associativity).
Notation "'PAR' c1 'WITH' c2 'END'" :=
(CPar c1 c2) (at level 80, right associativity).
Inductive cstep : (com * state) -> (com * state) -> Prop :=
| CS_AssStep : forall st i a a',
a / st -->a a' ->
(i ::= a) / st --> (i ::= a') / st
| CS_Ass : forall st i n,
(i ::= (ANum n)) / st --> SKIP / (i !-> n ; st)
| CS_SeqStep : forall st c1 c1' st' c2,
c1 / st --> c1' / st' ->
(c1 ;; c2) / st --> (c1' ;; c2) / st'
| CS_SeqFinish : forall st c2,
(SKIP ;; c2) / st --> c2 / st
| CS_IfStep : forall st b b' c1 c2,
b /st -->b b' ->
(TEST b THEN c1 ELSE c2 FI) / st
--> (TEST b' THEN c1 ELSE c2 FI) / st
| CS_IfTrue : forall st c1 c2,
(TEST BTrue THEN c1 ELSE c2 FI) / st --> c1 / st
| CS_IfFalse : forall st c1 c2,
(TEST BFalse THEN c1 ELSE c2 FI) / st --> c2 / st
| CS_While : forall st b c1,
(WHILE b DO c1 END) / st
--> (TEST b THEN (c1;; (WHILE b DO c1 END)) ELSE SKIP FI) / st
| CS_Par1 : forall st c1 c1' c2 st',
c1 / st --> c1' / st' ->
(PAR c1 WITH c2 END) / st --> (PAR c1' WITH c2 END) / st'
| CS_Par2 : forall st c1 c2 c2' st',
c2 / st --> c2' / st' ->
(PAR c1 WITH c2 END) / st --> (PAR c1 WITH c2' END) / st'
| CS_ParDone : forall st,
(PAR SKIP WITH SKIP END) / st --> SKIP / st
where " t '/' st '-->' t' '/' st' " := (cstep (t,st) (t',st')).
Definition cmultistep := multi cstep.
Notation " t '/' st '-->*' t' '/' st' " :=
(multi cstep (t,st) (t',st'))
(at level 40, st at level 39, t' at level 39).
この言語で特に興味深い性質は、次のプログラムが変数Xに任意の値を入れた状態で停止させられるというものです。
特に、Xを0にして停止できます:
Example par_loop_example_0:
exists st',
par_loop / empty_st -->* SKIP / st'
/\ st' X = 0.
Proof.
eapply ex_intro. split.
unfold par_loop.
eapply multi_step. apply CS_Par1.
apply CS_Ass.
eapply multi_step. apply CS_Par2. apply CS_While.
eapply multi_step. apply CS_Par2. apply CS_IfStep.
apply BS_Eq1. apply AS_Id.
eapply multi_step. apply CS_Par2. apply CS_IfStep.
apply BS_Eq. simpl.
eapply multi_step. apply CS_Par2. apply CS_IfFalse.
eapply multi_step. apply CS_ParDone.
eapply multi_refl.
reflexivity. Qed.
Xを2にして停止できます:
Example par_loop_example_2:
exists st',
par_loop / empty_st -->* SKIP / st'
/\ st' X = 2.
Proof.
eapply ex_intro. split.
eapply multi_step. apply CS_Par2. apply CS_While.
eapply multi_step. apply CS_Par2. apply CS_IfStep.
apply BS_Eq1. apply AS_Id.
eapply multi_step. apply CS_Par2. apply CS_IfStep.
apply BS_Eq. simpl.
eapply multi_step. apply CS_Par2. apply CS_IfTrue.
eapply multi_step. apply CS_Par2. apply CS_SeqStep.
apply CS_AssStep. apply AS_Plus1. apply AS_Id.
eapply multi_step. apply CS_Par2. apply CS_SeqStep.
apply CS_AssStep. apply AS_Plus.
eapply multi_step. apply CS_Par2. apply CS_SeqStep.
apply CS_Ass.
eapply multi_step. apply CS_Par2. apply CS_SeqFinish.
eapply multi_step. apply CS_Par2. apply CS_While.
eapply multi_step. apply CS_Par2. apply CS_IfStep.
apply BS_Eq1. apply AS_Id.
eapply multi_step. apply CS_Par2. apply CS_IfStep.
apply BS_Eq. simpl.
eapply multi_step. apply CS_Par2. apply CS_IfTrue.
eapply multi_step. apply CS_Par2. apply CS_SeqStep.
apply CS_AssStep. apply AS_Plus1. apply AS_Id.
eapply multi_step. apply CS_Par2. apply CS_SeqStep.
apply CS_AssStep. apply AS_Plus.
eapply multi_step. apply CS_Par2. apply CS_SeqStep.
apply CS_Ass.
eapply multi_step. apply CS_Par1. apply CS_Ass.
eapply multi_step. apply CS_Par2. apply CS_SeqFinish.
eapply multi_step. apply CS_Par2. apply CS_While.
eapply multi_step. apply CS_Par2. apply CS_IfStep.
apply BS_Eq1. apply AS_Id.
eapply multi_step. apply CS_Par2. apply CS_IfStep.
apply BS_Eq. simpl.
eapply multi_step. apply CS_Par2. apply CS_IfFalse.
eapply multi_step. apply CS_ParDone.
eapply multi_refl.
reflexivity. Qed.
より一般に...
Lemma par_body_n__Sn : forall n st,
st X = n /\ st Y = 0 ->
par_loop / st -->* par_loop / (X !-> S n ; st).
Proof.
Admitted.
st X = n /\ st Y = 0 ->
par_loop / st -->* par_loop / (X !-> S n ; st).
Proof.
Admitted.
☐
Lemma par_body_n : forall n st,
st X = 0 /\ st Y = 0 ->
exists st',
par_loop / st -->* par_loop / st' /\ st' X = n /\ st' Y = 0.
Proof.
Admitted.
st X = 0 /\ st Y = 0 ->
exists st',
par_loop / st -->* par_loop / st' /\ st' X = n /\ st' Y = 0.
Proof.
Admitted.
☐
... 上のループはXに任意の値を入れた状態で終了できます。
Theorem par_loop_any_X:
forall n, exists st',
par_loop / empty_st -->* SKIP / st'
/\ st' X = n.
Proof.
intros n.
destruct (par_body_n n empty_st).
split; unfold t_update; reflexivity.
rename x into st.
inversion H as [H' [HX HY]]; clear H.
exists (Y !-> 1 ; st). split.
eapply multi_trans with (par_loop,st). apply H'.
eapply multi_step. apply CS_Par1. apply CS_Ass.
eapply multi_step. apply CS_Par2. apply CS_While.
eapply multi_step. apply CS_Par2. apply CS_IfStep.
apply BS_Eq1. apply AS_Id. rewrite t_update_eq.
eapply multi_step. apply CS_Par2. apply CS_IfStep.
apply BS_Eq. simpl.
eapply multi_step. apply CS_Par2. apply CS_IfFalse.
eapply multi_step. apply CS_ParDone.
apply multi_refl.
rewrite t_update_neq. assumption. intro X; inversion X.
Qed.
End CImp.
A Small-Step Stack Machine
Definition stack := list nat.
Definition prog := list sinstr.
Inductive stack_step : state -> prog * stack -> prog * stack -> Prop :=
| SS_Push : forall st stk n p',
stack_step st (SPush n :: p', stk) (p', n :: stk)
| SS_Load : forall st stk i p',
stack_step st (SLoad i :: p', stk) (p', st i :: stk)
| SS_Plus : forall st stk n m p',
stack_step st (SPlus :: p', n::m::stk) (p', (m+n)::stk)
| SS_Minus : forall st stk n m p',
stack_step st (SMinus :: p', n::m::stk) (p', (m-n)::stk)
| SS_Mult : forall st stk n m p',
stack_step st (SMult :: p', n::m::stk) (p', (m*n)::stk).
Theorem stack_step_deterministic : forall st,
deterministic (stack_step st).
Proof.
unfold deterministic. intros st x y1 y2 H1 H2.
induction H1; inversion H2; reflexivity.
Qed.
Definition stack_multistep st := multi (stack_step st).
Exercise: 3 stars, advanced (compiler_is_correct)
Definition compiler_is_correct_statement : Prop
. Admitted.
Theorem compiler_is_correct : compiler_is_correct_statement.
Proof.
Admitted.
☐
Coq でプログラミング言語の定義を扱っていると、ある具体的な項がどのように簡約されるか知りたいことがよくあります。
t -->* t' という形のゴールを、 t が具体的な項で t' が未知の場合に証明するときです。
このような証明は手でやるには退屈すぎます。
例えば、スモールステップ簡約の関係 astep を使って算術式を簡約することを考えてみましょう。
Example step_example1 :
(P (C 3) (P (C 3) (C 4)))
-->* (C 10).
Proof.
apply multi_step with (P (C 3) (C 7)).
apply ST_Plus2.
apply v_const.
apply ST_PlusConstConst.
apply multi_step with (C 10).
apply ST_PlusConstConst.
apply multi_refl.
Qed.
証明では、正規形になるまで multi_step を繰り返し適用します。
幸い、証明の途中に出てくる部分は、適切なヒントを与えてやれば auto で解けそうです。
Hint Constructors step value.
Example step_example1' :
(P (C 3) (P (C 3) (C 4)))
-->* (C 10).
Proof.
eapply multi_step. auto. simpl.
eapply multi_step. auto. simpl.
apply multi_refl.
Qed.
下の Tactic Notation 定義はこのパターンを表現したものです。
それに加えて、1ステップ毎にそのときのゴールを表示します。
これは、項がどのように簡約されるか利用者が追えるようにするためです。
Tactic Notation "print_goal" :=
match goal with |- ?x => idtac x end.
Tactic Notation "normalize" :=
repeat (print_goal; eapply multi_step ;
[ (eauto 10; fail) | (instantiate; simpl)]);
apply multi_refl.
Example step_example1'' :
(P (C 3) (P (C 3) (C 4)))
-->* (C 10).
Proof.
normalize.
normalize内のprint_goalが簡約の様子を表示する。
(P (C 3) (P (C 3) (C 4)) -->* C 10) (P (C 3) (C 7) -->* C 10) (C 10 -->* C 10)
Qed.
また、存在量化された変数を入れたゴールから始めることで、normalize タクティックは項の正規形を計算できます。
Example step_example1''' : exists e',
(P (C 3) (P (C 3) (C 4)))
-->* e'.
Proof.
eapply ex_intro. normalize.
ここでのトレースは以下のようになります。
(P (C 3) (P (C 3) (C 4)) -->* ?e') (P (C 3) (C 7) -->* ?e') (C 10 -->* ?e')ここで ?e' は eapply で作られた変数です。
Qed.
☐
☐