8.1 The goalstack (1)

 前回述べたように,HOL Lightでは,goalの集まりであるgoalstackを用いて対話的に証明が作成できます.具体的には,*_TAC の名前を持つ,tactic(戦略関数) を,必要に応じてやり直し・訂正を加えながら,goalstackに段階的に適用することで,その中にある自明でないgoalを消していくわけです.

 まず,g (goal) 関数(: term -> goalstack = )を用いてinitial goal(最終目標のことですが,subgoalsに展開する前の初期状態と言う意味でinitial goal)を設定しましょう.

# g ( `2 <= n /\ n <= 2 ==> f(2,2) + n < f(n,n) + 7` );;
Warning: Free variables in goal: f, n
val it : goalstack = 1 subgoal (1 total)

`2 <= n /\ n <= 2 ==> f (2,2) + n < f (n,n) + 7`

 これで改めて g を使うまで,このtermが証明の対象として固定されました.1 subgoal (1 total) は,今表示しているsubgoalの個数,および,表示していないものも含めたsubgoalの個数を表しています.

 以下,e (expand) 関数(: tactic -> goalstack = )により,tactic を subgoal に適用してgoalstackを変化させながら証明を進めて行きます.
 とりあえず,p==>q の形なので,|- p==>q から p |- q に上る(証明では下る)tactic である DISCH_TAC (discharge) を用いてみます.

# e ( DISCH_TAC );;
val it : goalstack = 1 subgoal (1 total)

0 [`2 <= n /\ n <= 2`]

`f (2,2) + n < f (n,n) + 7`

 いわゆる証明論を知っていれば,この書式からHOL Lightは自然演繹に基づくシステムであり,今の処理は,最後に落ちる仮定を復元するステップだとわかるでしょう.

 しかし,よく見ると(笑),まず前件を等式にconvした方が良さそうですから,ワンステップ下り,元に戻しましょう.それには,b() ( back up ) 関数を用いて

# b();;
val it : goalstack = 1 subgoal (1 total)

`2 <= n /\ n <= 2 ==> f (2,2) + n < f (n,n) + 7`

とします.
 で,conversion から,それをsubgoalに適用する tactic を生成する CONV_TAC という関数(: conv -> tactic = ) を用いるのですが,既存のtheoremから使えそうなものを探すと

# search[ `a<=b /\ b<=a` ];;
val it : (string * thm) list =
[("LE_ANTISYM", |- !m n. m <= n /\ n <= m <=> m = n)]

がありますので,これをREWRITE_CONVで参照するtactic CONV_TAC ( REWRITE_CONV [LE_ANTISYM] ) を e で評価してみると

# e ( CONV_TAC ( REWRITE_CONV [LE_ANTISYM] ) );;
val it : goalstack = 1 subgoal (1 total)

`2 = n ==> f (2,2) + n < f (n,n) + 7`

となります.
 この利用法はよくあるので (CONV_TAC o REWRITE_CONV) は REWRITE_TAC(: thm list -> tactic = ) として定義されています(最も内側のみを書き換えるONCE_REWRITE_TACもあります). 従って

# b();;
val it : goalstack = 1 subgoal (1 total)

`2 <= n /\ n <= 2 ==> f (2,2) + n < f (n,n) + 7`

# e ( REWRITE_TAC [LE_ANTISYM] ) ;;
val it : goalstack = 1 subgoal (1 total)

`2 = n ==> f (2,2) + n < f (n,n) + 7`

ともできます.
 次に,2とnが等しいことから,f(2,2)とf(n,n)も等しいことを示します.それには先のDISCH_TACにより`2=n`を仮定にまわし,仮定を参照に加えるASM_REWRITE_TAC(: thm list -> tactic = )でsubgoalを書き換えるのが自然でしょう(ONCE/PURE_REWRITE_TACにも,そのASM_版ONCE/PURE_ASM_REWRITE_TACがあります).

# e ( DISCH_TAC ) ;;
val it : goalstack = 1 subgoal (1 total)

0 [`2 = n`]

`f (2,2) + n < f (n,n) + 7`
# e ( ASM_REWRITE_TAC [] ) ;;
val it : goalstack = 1 subgoal (1 total)

0 [`2 = n`]

`f (n,n) + n < f (n,n) + 7`

 おっと,可笑しなsubgoalが出てきましたね.お判りのように,仮定は`2=n`なので,subgoalの全ての2がnに書き換えられてしまったのです.
 ということで,2ステップ下って

# b();;
val it : goalstack = 1 subgoal (1 total)

0 [`2 = n`]

`f (2,2) + n < f (n,n) + 7`

# b();;
val it : goalstack = 1 subgoal (1 total)

`2 = n ==> f (2,2) + n < f (n,n) + 7`

とし,`2=n`を`n=2`にrewriteしましょう.

# search[ `a=b <=> b=a` ];;
Warning: inventing type variables
val it : (string * thm) list = [("EQ_SYM_EQ", |- !x y. x = y <=> y = x)]
# e ( REWRITE_TAC [EQ_SYM_EQ] );;
val it : goalstack = 1 subgoal (1 total)

`n = 2 ==> f (2,2) + n < f (n,n) + 7`

そして先ほどと同じく

# e ( DISCH_TAC ) ;;
val it : goalstack = 1 subgoal (1 total)

0 [`n = 2`]

`f (2,2) + n < f (n,n) + 7`

# e ( ASM_REWRITE_TAC[] ) ;;
val it : goalstack = 1 subgoal (1 total)

0 [`n = 2`]

`f (2,2) + 2 < f (2,2) + 7`

 このsubgoalをARITH_RULEで証明できれば,結果がtrueとなり,証明全体も完成します.
 ところが,ARITH_RULEは,tmを受け取り|- tmを返す,proverなので,まず

(EQT_INTRO o ARITH_RULE)

のように,|- tm =(<=>) tm'を返す,conversionにして,CONV_TACに渡さなくてはなりません.
 ところが有難いことに,CONV_TACは,proverにも適用可能であり

# e ( CONV_TAC ( ARITH_RULE ) ) ;;
val it : goalstack = No subgoals

となり,このCONV_TAC (ARITH_RULE) は ARITH_TAC としても定義されています.同様に,proverである MESON を CONV_TAC で tactic にしたMESON_TAC,更に仮定も参照する ASM_MESON_TAC[] もあります.

 No subgoalsとは,示すべき目標が全て証明されて残っていません,つまり,証明完了ということです.得られたtheoremは

# top_thm();;
val it : thm = |- 2 <= n /\ n <= 2 ==> f (2,2) + n < f (n,n) + 7

のように参照できます.

 なお,上記では,`n=2`を仮定にまわしましたが,subgoalの中で処理することもできます.

...
`2 <= n /\ n <= 2 ==> f (2,2) + n < f (n,n) + 7`

# e ( SIMP_TAC [ LE_ANTISYM ; EQ_SYM_EQ ] );;
val it : goalstack = 1 subgoal (1 total)

`n = 2 ==> f (2,2) + 2 < f (2,2) + 7`

 このSIMP_TAC(: thm list -> tactic = ) は,ときにREWRITE_TACよりも強力です.
 
 以上を一気に書くと

g ( `2 <= n /\ n <= 2 ==> f (2,2) + n < f (n,n) + 7` );;
e ( SIMP_TAC [ LE_ANTISYM ; EQ_SYM_EQ ] );;
e ( ARITH_TAC );;
let th1 = top_thm();;

となり,これは,e 関数の tactics は THEN により一つの tactic に結合して

g ( `2 <= n /\ n <= 2 ==> f (2,2) + n < f (n,n) + 7` );;
e ( SIMP_TAC [ LE_ANTISYM ; EQ_SYM_EQ ] THEN
ARITH_TAC );;
let th1 = top_thm();;

ともできます.この書式は単なる略記ではなく,各tacticを複数のsubgoalの全てに作用させる働きがあります.また,THENのようにtacticを返す関数を一般にtacticalといい,conversionに対するconversionalと同じ位置付けになります.

 更に prove(: term * tactic -> thm = ) を用いると

# let th1 = prove( `2 <= n /\ n <= 2 ==> f (2,2) + n < f (n,n) + 7` , SIMP_TAC [ LE_ANTISYM ; EQ_SYM_EQ ] THEN ARITH_TAC );;
val th1 : thm = |- 2 <= n /\ n <= 2 ==> f (2,2) + n < f (n,n) + 7

ともできます.