講義資料 - Toyama

2015 年度 情報論理学 講義資料 (1)
青戸等人
はじ めに
1
次の 3 つの等式を 満たす演算を も つ集合のこ と を 群と いう .


 +(0, x) ≈ x

+(−(x), x) ≈ 0


+(+(x, y), z) ≈ +(x, +(y, z))
こ こ で, x, y, z は変数で, +, −, 0 は集合上の演算である . (本講義では, = の代わり
に ≈ を 対象言語の等号に 用いる . ) 通常, +, −, 0 は実数や整数の加算, 符号反転,
零に 使われる こ と が多いが, 群論では, こ れら に 限定せず, 一般に 上の等式が満た
さ れる も のすべて を 対象に し て , ど んな 性質が成立する かを 調べる .
こ のよ う な 等式の公理から ど のよ う な 等式が導かれる か, を 考え る 枠組みが等式
論理 (equational logic) と よ ばれる も のである . 例え ば, 群の公理のモデルは” 群” と
よ ばれ, 他に も 束の公理のモデルは” 束” と よ ばれる . 一般に , こ のよ う な 等式公理
のモデルは代数と よ ばれ, そ の数学理論がいろ いろ 詳し く 調べら れて おり , 例え ば
群や束であれば, 群論や束論と し て 知ら れて いる .
こ の講義では, 等式理論にも と づく 計算モデルである 項書き 換え システム につい
て , いく つかのト ピ ッ ク ス を 取り 上げ講義する . 等式のみを 対象に する と いう こ と
はと て も 限定的な 気がする かも し れな いが, 項表現に 変数束縛を 導入する な ど し て ,
ラ ム ダ計算な ど を 含むよ う な , よ り 表現力を 高めた項書き 換え シス テム に ついて の
研究も 精力的に 進めら れて いる . ま た, 述語論理の自動証明な ど に おけ る 等式特有
の取り 扱いに おいて も 書き 換え シス テム の技術が応用さ れて いる .
ま た, 表面的な 内容だけ でな く , 細かい証明も 触れて も ら う し , SML#1 を 用い
たプロ グラ ム を 作成し な がら , 理解を 深めて も ら う . 本文中のプロ グラ ム は, http:
//www.nue.riec.tohoku.ac.jp/user/aoto/15LogicG/ に置く 2. SML 言語につい
て は, いろ いろ な 教科書があ る と 思う が, [2], [3] だけ 挙げて おく . 項書き 換え シス
テム について は, (お話的な ) 解説は [4] にある が, き ち んと し た教科書は日本語では
な い. 英語では [1] はき ち んと し た 内容で比較的読みやすいが, 内容はだいぶ古く ,
理論的な 本を 読み慣れて いな い人に はだいぶ難し い.
関数記号と 変数
2
演算を 表わす記号を 関数記号 (function symbol ) と よ ぶ. 関数記号は, そ れぞれ, 引
数の個数が決ま っ て いる も のと する . こ の数を ア リ ティ と いう . 関数記号 f のア リ
SML#は東北大学電気通信研究所大堀研究室で開発さ れて いる SML 言語のコ ン パイ ラ
http://www.pllab.riec.tohoku.ac.jp/smlsharp/ja/
2
プロ グラ ム はこ れを 参考に せずに /使わずに 書いて も , ま っ たく 構わな い.
1
1
ティ を arity(f ) と 記す. 特に , ア リ ティ は 0 でも よ いも のと する . ア リ ティ が 0 の
関数記号と は, つま り , 定数 (constant) のこ と に な る .
等式公理を 表わすために は, 関数記号の他に , 変数 (variable) が必要と な る . ま
た, 変数は (可算) 無限個ある も のと する . 従っ て , 必要に な る たびに , ま だ使われ
て いな い変数を いつでも 用意でき る .
ま た, 同じ 記号が関数記号かつ変数にな る こ と はな いも のと する . つま り , 関数
記号の集合を F , 変数の集合を V と する と き , F ∩ V = ∅ が成立する . 以下では, 特
に断わら な い限り , 変数全体の集合を V と 記し , 関数記号全体の集合を F と 表わす.
変数や関数記号を プロ グラ ム で扱う ために , こ れら のデータ 型を 定義し よ う .
ま ず, 変数に関する データ 型や関数を おく Var スト ラ ク チャ を 用意する . 変数は
x1 や y2 のよ う に , 変数名と 自然数のイ ン デッ ク ス の対から 構成さ れる も のと する .
自然数のイ ン デッ ク スは変数を 新し いも のにする と き に便利. Var.name は変数名の
部分を , Var.index はイ ン デッ ク ス を 取り 出す.
1
2
3
4
5
6
7
(* var.sml *)
signature VAR =
sig
eqtype key
val name: key -> string
val index: key -> int
end
8
9
10
11
12
13
14
structure Var : VAR =
struct
type key = string * int
fun name (x,i) = x
fun index (x,i) = i
end
同様にし て , 関数記号に関する データ 型や関数を おく Fun スト ラ ク チャ を 用意す
る . 関数記号の場合は, 関数名そ のも のを 関数記号と し よ う .
1
2
3
4
5
(* fun.sml *)
signature FUN =
sig
eqtype key
end
6
7
8
9
10
structure Fun : FUN =
struct
type key = string
end
さ て , 変数や関数記号を プロ グラ ム 上で入力する には, 文字列から 入力する のが
便利であ る . そ こ で, “変数 ⇄ 文字列”, “関数記号 ⇄ 文字列” と いう 変換を 行う
プロ グラ ム を 用意し て おく .
変数は, _[1-9][0-9]* (つま り , ア ン ダース コ ア “_” に 1∼ 9 から 始ま る 数字列
が続いたも の ) が末尾にある と き , こ の部分を 自然数のイ ン デッ ク スと し て 扱う こ と
2
にする (ただし , その前の変数名の部分が空文字列のと き は例外と する ). そのよ う な
末尾がな いと き のイ ン デッ ク スは 0 と 定める . 関数記号では, 文字列がそのま ま 関数
記号と 対応する . toString 関数は, 変数や関数記号を 文字列へ変換し , fromString
関数は, 文字列を 変数や関数記号へ変換する .
1
2
3
4
5
6
7
(* var.sml *)
signature VAR =
sig
...
val toString: key -> string
val fromString: string -> key
end
8
9
10
11
12
13
14
structure Var : VAR =
struct
...
fun toString (x,i) = if i = 0 then x
else if Int.> (i, 0) then x ^ "_" ^ (Int.
toString i)
else raise Fail "Error: Var.toString: var index
out of range"
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
1
2
3
4
5
6
7
(* .+_[1-9][0-9]* と な っ て いれば末尾を index と する *)
fun fromString str =
let val ss = Substring.full str
val (body,index) = Substring.splitr Char.isDigit ss
val isProperIndexing = Int.> (Substring.size body, 1)
andalso Substring.sub (body, Substring.
size body - 1) = #"_"
andalso not (Substring.isEmpty index)
andalso Substring.sub (index, 0) <> #"0"
in if isProperIndexing
then (Substring.string (Substring.trimr 1 body),
valOf (Int.fromString (Substring.string index)))
else (str,0)
end
end
(* fun.sml *)
signature FUN =
sig
...
val toString: key -> string
val fromString: string -> key
end
8
9
10
11
12
structure Fun : FUN =
struct
...
fun toString key = key
3
13
14
fun fromString str = str
end
最後に , 以上のプロ グラ ム の実行例を いく つか示す. ソ ース フ ァ イ ルを イ ン タ
プリ タ で扱う た めに は use ディ レ ク ティ ブを 使う . いま は 2 つし かフ ァ イ ルがな い
(var.sml, fun.sml) が, こ れから 増え て く る と 何度も フ ァ イ ルを ロ ード する のは大
変な ので, 必要な すべて のソ ース フ ァ イ ルを 1 度に 読み込むた めに , 以下の内容の
フ ァ イ ル use.sml を 用意し て おく .
1
2
3
(* use.sml *)
_use "var.sml"
_use "fun.sml"
SML#では, use ディ レ ク ティ ブはイ ン タ プリ タ のト ッ プ環境でし か使え な いの
で, フ ァ イ ルの中では代わり に _use を 使う 必要がある .
以下は, Unix 環境でイ ン タ プリ タ を 動かし た様子.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(* 実行例 *)
$ ls
(* シェ ル環境 *)
fun.sml use.sml var.sml
$ smlsharp
SML# version 1.2.0 (2012-11-14 18:25:26 JST) for x86-linux
# use "use.sml";
signature FUN =
...
...
...
# Var.fromString "x_12";
val it = ("x", 12) : string * int
# Var.fromString "x_012";
val it = ("x_012", 0) : string * int
# Var.fromString "_12";
val it = ("_12", 0) : string * int
# Fun.fromString "+";
val it = "+" : string
#
(* Ctrl-D で終了*)
$
(* シェ ル環境に 戻っ た *)
演習 1 SML#コ ン パイ ラ を イ ン スト ールせよ . イ ン タ プリ タ で, val.sml, fun.sml
を 読み込み, テス ト せよ .
3
項 (term)
等号はある 対象と ある 対象の間に 成立する 関係である . こ の対象を 表わす記号列を
項と いう . 群の公理に おけ る 等号の左辺や右辺は項である .
定義 2 (項) 項 (term) を 以下のよ う に 帰納的に 定義する .
(1) 変数は項である .
(2) f を ア リ ティ n の関数記号, t1 , . . . , tn を 項と する と き , f (t1 , . . . , tn ) は項である .
4
特に , (2) で, n = 0 のと き は f と 書く .
定義から 明ら かな よ う に , 項は変数全体の集合 V と 関数記号全体の集合 F から
定ま る . つま り , V と F は項集合のパラ メ ータ に な っ て いる から , 項全体の集合を
T(F, V) と 記すこ と に する .
例 3 (項の例) F = {+, −, 0} と する と , −(0) や +(0, x0 ), −(+(0, x0 )) は, 項の例
である .
項に 関する データ 型や関数を おく Term ス ト ラ ク チャ を 用意し よ う .
1
2
3
4
5
6
7
(* term.sml *)
signature TERM =
sig
type var_key = Var.key
type fun_key = Fun.key
datatype term = Var of var_key | Fun of fun_key * term list
end
8
9
10
11
12
13
structure Term : TERM =
struct
type var_key = Var.key
type fun_key = Fun.key
datatype term = Var of var_key | Fun of fun_key * term list
項を 表わすデータ 型 term と し て は, 関数記号の下に , 項のリ ス ト を 持て る よ う
にする . プロ グラ ム 内では制約を 受け な いが, こ の項のリ スト の長さ は, (暗黙的に )
関数記号のア リ ティ と 対応する こ と と 約束する .
演習 4 Term スト ラ ク チャ に, 与え ら れた項が変数かを 返す isVar:
およ び変数でな いかを 返す isFun: term -> bool を 追加せよ .
term -> bool
演習 5 Term ス ト ラ ク チャ に , args x = [ ], args f (t1 , . . . , tn ) = [t1 , . . . , tn ] と な る
よ う な , 与え ら れた項の引数を 返す関数 args: term -> term list を 追加せよ .
定義 6 (根記号) 項 t の根記号 root(t) を 以下のよ う に 定める .
x (t = x ∈ V の場合)
root(t) =
f (t = f (t1 , . . . , tn ) の場合)
例 7 root(+(x, +(y, 0))) = +.
項 t から root(t) を 計算する 関数を 書こ う . root 関数を 用意する ために は, 変数
と 関数記号を 一緒に 扱う 必要があ る . そ こ で, F ∪ V を 表わすた めのデータ 型と し
て , Symbol ス ト ラ ク チャ を 用意し , symbol 型を 定義し て おく .
5
1
2
3
4
5
(* symbol.sml *)
signature SYMBOL =
sig
datatype symbol = V of Var.key | F of Fun.key
end
6
7
8
9
10
structure Symbol : SYMBOL =
struct
datatype symbol = V of Var.key | F of Fun.key
end
演習 8 Term ス ト ラ ク チャ に , 与え ら れた 項の根記号を 返す関数 root:
Symbol.symbol を 追加せよ .
term ->
定義 9 (項の変数集合) t を 項と する と き , V(t) を 以下のよ う に 定義する .
{x}
(t = x ∈ V の場合)
V(t) = S
1≤i≤n V(ti ) (t = f (t1 , . . . , tn ) の場合)
例 10 V(+(x, +(y, 0))) = {x, y}.
V(t) を 計算する 関数を 書こ う . こ のために , ま ず, ListUtil ス ト ラ ク チャ を 用
意し , その中に, 集合演算を リ スト を 使っ て 実現する 関数を 用意する . member x xs
は要素 x がリ ス ト xs に 含ま れて いる かを 判定する . add x xs は要素 x が xs に 含ま
れて いな いと き だけ リ ス ト に x を 追加する . つま り , xs に 要素が 2 重に 含ま れて い
な いな ら , 追加し た 後も 要素は 2 重に 含ま れな い. union xs ys は, 2 つのリ ス ト
xs と ys を , 同じ 要素が重複さ れな いよ う に 結合する .
1
2
3
4
5
6
7
(* list_util.sml *)
signature LIST_UTIL =
sig
val member: ’’a -> ’’a list -> bool
val add: ’’a -> ’’a list -> ’’a list
val union: ’’a list * ’’a list -> ’’a list
end
8
9
10
11
12
structure ListUtil : LIST_UTIL =
struct
fun member x [] = false
| member x (y::ys) = x = y orelse member x ys
13
14
15
(* 集合と し て の演算 *)
fun add x ys = if member x ys then ys else x::ys
16
17
...
18
19
end
6
演習 11 ListUtil ス ト ラ ク チャ に , 関数 union を 補え .
演習 12 Term ス ト ラ ク チャ に , t から V(t) を 返す関数 vars:
list を 追加せよ .
term -> var key
最後に , 変数や関数記号に ついて 行っ たよ う に , “項 ⇄ 文字列” の変換関数を 用
意する .
ま ず, 変数と 関数記号を 区別する 必要がある が, 文字列のう ち , 先頭に ?がつい
て いる も のを 変数と し , そ う でな いも のは関数記号と する .
1
2
3
4
5
6
7
(* symbol.sml *)
signature SYMBOL =
sig
...
val toString : symbol -> string
val fromString : string -> symbol
end
8
9
10
11
12
13
structure Symbol : SYMBOL =
struct
...
fun toString (V x) = "?" ^ (Var.toString x)
| toString (F f) = (Fun.toString f)
14
15
16
17
18
19
fun fromString str =
if String.isPrefix "?" str
then V (Var.fromString (String.substring (str, 1, (String.size
str) - 1)))
else F (Fun.fromString str)
end
文字列から 項を 得る に は, 字句解析・ 構文解析が必要に な る . 字句解析では, 3
つ特殊文字 “(”,“)”, “,” を 切り 出すと と も に , こ れら の特殊文字と 空白を 区切り と
し て 得ら れる 文字列の列に 分解する . 構文解析は, 以下の項の構文に あっ て いる か
を チェッ ク する . fsym, vsym は, そ れぞれ関数記号と な る 文字列, 変数と な る 文字
列を 表わす. プロ グラ ム の詳細は省略する . lexical.sml に は字句解析のツ ール,
parsing.sml に は構文解析のツ ールが入っ て いる .
1
2
3
4
1
2
3
4
5
6
term ::= fsym "(" termlist | atom
termlist ::= termseq ")"
termseq ::= term "," termseq | term
atom ::= fsym | vsym
signature TERM =
sig
...
val toString: term -> string
val fromString: string -> term
end
7
最後に , 実行例を 示す.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
4
$ ls
fun.sml lexical.sml list_util.sml parsing.sml symbol.sml
sml use.sml var.sml
$ smlsharp
SML# version 1.2.0 (2012-11-14 18:25:26 JST) for x86-linux
# use "use.sml";
functor Lexical
...
...
# val t0 = Term.fromString "?x";
val t0 = _ : Term.term
# val t1 = Term.fromString "+(s(?x),?x)";
val t1 = _ : Term.term
# Term.toString t0;
val it = "?x" : string
# Term.toString t1;
val it = "+(s(?x),?x)" : string
# Term.isFun t0;
val it = false : bool
# Term.isFun t1;
val it = true : bool
# Symbol.toString (Term.root t0);
val it = "?x" : string
# Symbol.toString (Term.root t1);
val it = "+" : string
# List.map Term.toString (Term.args t1);
val it = ["s(?x)", "?x"] : string list
# List.map Var.toString (Term.vars t1);
val it = ["x"] : string list
#
(* Ctrl-D で終了 *)
$
(* シェ ル環境に も ど っ た *)
term.
代入 (substitution)
変数は項で具体化さ れる . こ れを 行う のが代入.
定義 13 関数 σ : V → T(F, V) で, 集合 {x | σ(x) 6= x} が有限であ る も のを 代
入 (substitution) と よ ぶ. 集合 {x | σ(x) 6= x} を 代入 σ の定義域 (domain) と よ び,
dom(σ) と 記す.
dom(σ) = {x1 , . . . , xn } であ る と き , 特に , σ を {x1 := σ(x1 ), . . . , xn := σ(xn )}
のよ う に も 書く .
例 14 σ = {x := 0, y := +(x, z)} は代入. こ のと き , σ(x) = 0, σ(y) = +(x, z) と
な る . ま た, w ∈
/ {x, y} である 任意の変数 w に ついて は, σ(w) = w と な る .
8
定義 15 代入 σ の準同型拡張と は, 以下のよ う に 定義さ れる 関数 σ̂ : T(F, V) →
T(F, V) を いう .
σ(t)
t ∈ V のと き
σ̂(t) =
f (σ̂(t1 ), . . . , σ̂(tn )) t = f (t1 , . . . , tn ) のと き
代入 σ と そ の準同型拡張 σ̂ を 同一視し て , σ̂ と 書く べき と き も σ を 用いる .
例 16 σ = {y := +(x, z)} と する と , σ(+(+(0, x), y)) = +(+(0, x), +(x, z)) と な り ,
σ(+(y, y)) = +(+(x, z), +(x, z)) と な る .
こ のために 代入を 扱う ために , 連想リ ス ト (association list) に 関する プロ グラ ム
を 用意する .
連想リ スト は, (key, value) な る 対を 要素にも つよ う な リ スト で, 鍵 (key) から 値
(value) への対応を 保持し て おく ためのデータ 構造である . 単な る 対のリ ス ト と 異な
り , 1 つの鍵に 対応する 値は 1 つし か保持し な いと いう 制限がある . つま り ,
条件: リ ス ト 中の任意の 2 つの異な る 要素 (x, n) と (y, m) に ついて , x 6= y と な る
が成立する よ う に メ ン テナン ス を する . な お, こ の条件は,
条件: リ ス ト 中の任意の要素 (x, n) と (y, m) に ついて , x = y な ら ば n = m.
と も 書け る .
ま ず, AssocList スト ラ ク チャ を 用意し , その中に, 連想リ スト を 対のリ スト を
使っ て 実現する 関数を 用意する . find k xs は k を 鍵と する 要素が連想リ スト xs に
含ま れて いる かを 判定し , (k,v) が要素と な っ て いれば SOME v を , k を 鍵と する 要
素がな け れば NONE を 返す.
add (k,v) xs は, 連想リ ス ト に (k,v) を 追加する . も し (k,v) がすでに 要素と
な っ て いれば重複を 避け る た めに 実際に は追加し な いので, SOME xs を 返す. も し
(k,w) で, w が v と は異な る よ う な 要素が xs に 入っ て いた と き に は, (k,v) を 付け
加え る こ と は出来な い. こ のと き は NONE を 返す. 最後に , k を 鍵と する よ う な 要素
が xs に含ま れて いな いと き には, (k,v) を リ スト に追加し て SOME ((k,v)::xs) を
返す.
1
2
3
4
5
6
(* assoc_list.sml *)
signature ASSOC_LIST =
sig
val find: ’’a -> (’’a * ’b) list -> ’b option
val add: (’’a * ’’b) -> (’’a * ’’b) list -> (’’a * ’’b) list
option
end
7
8
9
10
11
12
13
14
15
16
structure AssocList : ASSOC_LIST=
struct
fun find x [] = NONE
| find x ((k,v)::ys) = if x = k then SOME v else find x ys
fun add (k,v) ys = case find k ys of
SOME w => if v = w then SOME ys
else NONE
| NONE => SOME ((k,v)::ys)
end
9
最後に, 代入に関する 関数を 置く Subst スト ラ ク チャ を 用意する . こ こ では, find
関数し か使っ て いな いが, add 関数は後に 用いる . apply 関数は, 代入を 項に 適用
する 関数である .
1
2
3
4
5
6
(* subst.sml *)
signature SUBST =
sig
type subst
val apply: subst -> Term.term -> Term.term
end
7
8
9
10
11
12
13
14
15
16
17
18
19
20
structure Subst: SUBST =
struct
local
structure AL = AssocList
structure L = List
structure T = Term
in
type subst = (Var.key * Term.term) list
fun apply sigma (T.Var x) = (case AL.find x sigma of
SOME t => t | NONE => T.Var x)
| apply sigma (T.Fun (f,ts)) = T.Fun (f, L.map (apply sigma) ts)
end (* of local *)
end
以下は代入の実行例.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ ls
assoc_list.sml lexical.sml
parsing.sml symbol.sml use.sml
fun.sml
list_util.sml subst.sml
term.sml
var.sml
$ smlsharp
SML# version 1.2.0 (2012-11-14 18:25:26 JST) for x86-linux
# use "use.sml";
...
...
# val sigma = [(Var.fromString "x", Term.fromString "0"), (Var.
fromString "y", Term.fromString "+(?x,?y)")];
val sigma = [(("x", 0), _), (("y", 0), _)] : ((string * int) * Term.
term) list
# Subst.apply sigma (Term.fromString "+(s(?x),?y)");
val it = _ : Term.term
# Term.toString it;
val it = "+(s(0),+(?x,?y))" : string
#
(* Ctrl-D で終了*)
$
(* シェ ル環境に 戻っ た *)
10
References
[1] F. Baader and T. Nipkow. Term Rewriting and All That. Cambridge University
Press, 1998.
[2] 大堀 淳. プロ グラ ミ ン グ言語 Standard ML 入門. 共立出版株式会社, 2001.
[3] L. C. Paulson. ML for the Working Programmer, 2nd ed. Cambridge University
Press, 1996.
[4] 外山 芳人. 項書き 換えシステム入門 -完備化によ る 定理自動証明-. http://www.
nue.riec.tohoku.ac.jp/lab-intro/TRS-intro/
11