Ⅰ 教育学研究科履修案内 - 横浜国立大学 · 専修免許状の教科に対応する主として教科教育学の科目群である。この科目群は、新たな教育実践の開発・創出に向
計算機数学 II (教科書補足,まとめ,演習問題と解...
Transcript of 計算機数学 II (教科書補足,まとめ,演習問題と解...
計算機数学 II (教科書補足,まとめ,演習問題と解答例)
教科書: 「計算論」高橋正子 著,近代科学社
1 計算可能な関数
1.1 計算とは何か?
1.2 while プログラム
教科書の記述に関わらず,while プログラムの構文を次のように定める.
プログラム ::= prog プログラム名 [(変数の列)]
[変数宣言]
[文の並び]
endprog(式)
変数宣言 ::= var 変数の列;
変数の列 ::= 変数, 変数, ... , 変数
文の並び ::= 文 文 ... 文
文 ::= 変数 := 式;
文 ::= if 条件 [then 文の並び] [else 文の並び] endif;
文 ::= while 条件 do [文の並び] endwhile;
構文定義中の [ …… ] は …… が省略できることを示す.「文」の書き方は 3 通りあるが,それぞれに従って書かれた文を上から代入文,if 文,while 文と呼ぶ.教科書からの変更は,次の点である.
・プログラム全体を prog と endprog でくくり,名前を付ける.
・input 文を使用しない.その代わりにプログラム名の後に,そのプログラムのパラメタを括弧でく
くって,変数として並べる.
・output文を使用しない.その代わりに,プログラムが終了したとき (プログラムの実行が endprog(式)
にたどりついたとき)のカッコ内の式の値をプログラムからの出力と定める.・入力パラメタ以外でプログラム中で使う変数は,var によりすべて先頭で宣言することにする.こ
れは,現実のプログラム言語に近づけるためと,後での説明を簡素化するためである.宣言された
変数の初期値はすべて 0 と定める.これはプログラムが停止したとき,どんな場合にもその計算値が確定するようにするためである.
・while 文,if 文の終わりを示すために,明示的に endwhile と endif という記述を用い, 文の並びを [ ] で囲むということはしない.
while プログラムの扱うデータは,自然数である.普通,日本の小中学校では 0 は自然数ではないと教えるが,本講義に関する限り,0 も自然数であると考える.したがって,while プログラムのデータには 0 が含まれる.データ間の基本演算は,加減乗除 (+ − × ÷)とし,それぞれプログラム中では記
1
号 +,-,*,/ で表す.ただし,x < y のとき,x−y = 0 と定める.割り算は,余りを切り捨てるものとする.さらに x÷ 0 = x と定める.教科書では,基本判定条件として等式以外を書くことを許してい
ないが,プログラムを分かりやすくするために,<,>,≤,≥,�= をそれぞれ >,<,=<,>=,>< と書
いて自由に用いることにする.しかし,例えば x < y は x+1-y = 0,x =< y は x-y = 0,x >< y は
(x+1-y)*(y+1-x) = 0 の略記と考える.
既にできているプログラムを,別のプログラムの中でその名前を記述することで利用することができ
る.この仕組みをサブルーチン呼び出しといい,呼び出されるプログラムをサブルーチンという.
以下の例は,上の構文に従っている.ただし,教科書の構文に従って,書かれたプログラムも間違い
とはしない.
例 1.1 (除算). 引き算と足し算だけで割り算を実行する while プログラム.
prog div(x,y)
var output;
if y = 0
then
output := x;
else
output := 0;
while x >= y do
x := x - y; output := output + 1;
endwhile;
endif;
endprog(output)
例 1.2 (乗算). 足し算と引き算だけで,掛け算を行う while プログラム
prog mult(x,y)
var output;
output := 0;
while y > 0 do
output := output + x; y := y - 1;
endwhile;
endprog(output)
問題 1.3 (べき乗). 自然数 x の k 乗 xk を求める while プログラム power(x, k) を作れ.任意の x に
対して x0 = 1 であるとしてよい.
問題 1.4 (階乗). 自然数 x の階乗 x! を求める while プログラム facto(x) を作れ.
問題 1.5 (平方根). 自然数 x に対して �√x� を求める while プログラム sqrt(x) を作れ.ここで,�x�は,x の少数部分を切り捨てた数,つまり x を超えない最大の整数を表すものとする.n = �√x� であるのは n2 ≤ x < (n + 1)2 であることに他ならないことに注意せよ.
2
問題 1.6 (対数). 自然数 xに対して,�log2(x+1)�を求める whileプログラム log2(x)を作れ.ここで,�x� は,x 以上の最小の整数を表すものとする.n = �log2(x +1)�であるのは n− 1 < log2(x +1) ≤ n
であることに他ならないから,x > 0 の場合,この問題は 2n−1 ≤ x < 2n なる n を求めるプログラム
を作れというのと同じであることに注意せよ.
問題 1.7 (最大公約数). 自然数 m, n の最大公約数を gcd(m,n) と記す.当然,gcd(m, n) = gcd(n, m)である.便宜上,gcd(0, m) = gcd(m, 0) = m と定義する.また,0 < m ≤ n のとき,gcd(m,n) =gcd(m,n − m) である.これらの定義を性質や利用して m, n の最大公約数を求める while プログラムgcd(m,n) を作れ.
問題 1.8 (フィボナッチ数). f0 = 0,f1 = 1,fn+2 = fn+1 + fn で定義される数 fn をフィボナッチ数
という.例えば f2 = f1 + f0 = 1 + 0 = 1,f3 = f2 + f1 = 1 + 1 = 1 である.n + 1 番目のフィボナッチ数 fn を求める while プログラム fibo(n) を作れ.
問題 1.9 (数字の取り出し). 自然数 x の 10 進表記が δnδn−1 · · ·δ1δ0 とする.x の第 i + 1 桁の数字 δi
を求める while プログラム digit(x, i) を作れ.i > n の場合,digit(x, i) = 0 と定める.
定理 1.10 (教科書の定理 1.2.1). 任意の N プログラム P に対して,fp を計算する while プログラムがある.
系 1.11 (教科書の系 1.2.2). 任意の N プログラム P に対して,fp を計算する while プログラムで,while 文を高々 1 回しか使わないものがある.
例 1.12. たとえば,教科書 12 ページの図 3 の N プログラムを標準的な手法で while プログラムに変換すると,次のようになる.
prog example(x)
var pc, y;
pc := 0;
while pc < 5 do
if pc = 0 then if A then pc := 5; else pc := 1; endif; endif;
if pc = 1 then B; pc := 2; endif;
if pc = 2 then if C then pc := 4; else pc := 3; endif; endif;
if pc = 3 then D; pc := 0; endif;
if pc = 4 then E; pc := 5; endif;
endwhile;
endprog(y)
pc は,元の N プログラムのどの部分を実行しているかを示す変数である.実際のコンピュータでも同
じような仕組みが使われていて,変数 pc の役割をするものをプログラムカウンタという.
3
1.3 原始帰納的関数
以降,x1, . . . , xn を �x と略記する.
次の関数および関数の作成法について考える。
• 初期関数
zero() = 0
succ(x) = x + 1
projni (x1, x2, . . . , xn) = xi (1 ≤ i ≤ n)
• 関数の合成関数 g : N
m → N,g1, . . . , gm : Nn → N を用いて
f(�x) = g(g1(�x), . . . , gm(�x))
なる関数 f : Nn → N を作る.
• 原始帰納法関数 g : N
n → N,h : Nn+2 → N を用いて
f(�x, 0) = g(�x), f(�x, y + 1) = h(�x, y, f(�x, y))
なる関数 f : Nn+1 → N を作る.
定義 1.13. 初期関数に関数の合成と原始帰納法のみを有限回繰り返し適用して得られる関数を原始帰納
的関数と言う.また,関数のこのような定義法を原始帰納的定義と言う.
例 1.14 (定数関数). 定数関数は原始帰納的である.
例 1.15 (前者関数). pred(x) = x−1 の原始帰納的定義.
pred(0) = 0 = zero(), pred(y + 1) = y = proj21(y, pred(y))
例 1.16 (加算). add(x, y) = x + y の原始帰納的定義.
add(x, 0) = proj11(x) = x, add(x, y + 1) = succ(proj3
3(x, y, add(x, y)) = (x + y) + 1
例 1.17 (乗算). mult(x, y) = xy の原始帰納的定義.
mult(x, 0) = zero() = 0,
mult(x, y + 1) = add(proj33(x, y, mult(x, y)), proj3
1(x, y, mult(x, y))) = xy + x
関数の合成や原始帰納法を厳密に使うのは煩わしい.そこで次のどちらかの場合,関数 f を原始帰納
的関数と呼ぶ.
• f(x1, . . . , xn) の値が x1, . . . , xn と既に原始帰納的と分かっている関数のみで表記できる場合.
4
• f(x1, . . . , xn, 0) の値が x1, . . . , xn と既に原始帰納的と分かっている関数のみで表記でき,かつ
f(x1, . . . , xn, y + 1) の値が x1, . . . , xn, y, f(x1, . . . , xn, y) と既に原始帰納的と分かっている関数のみで表記できる場合.
また,そのような f の値の表記を f の原始帰納的定義と呼ぶ.
例 1.18 (減算). sub(x, y) = x−y の原始帰納的定義.
sub(x, 0) = x, sub(x, y + 1) = pred(sub(x, y))
例 1.19 (繰り返し). 上の加算,乗算,減算の原始帰納的定義の例はすべて似たような構造をしているが,一般に原始帰納的関数 g : N
n → N,h : Nn+1 → N に対し,次のように h を繰り返し適用すること
で計算できる関数 f を考える.
f(�x, n) = h(�x, h(�x, · · ·h(�x︸ ︷︷ ︸n
, g(�x)) · · · ))
このときは f は
f(�x, 0) = g(�x), f(�x, n + 1) = h(�x, f(�x, n))
と定義できるので原始帰納的になる.
以降,記述の簡便化のために,Kenneth Iverson が考案した次の記法を用いる.P を任意の命題とす
るとき,[P ] で 0 か 1 の値を表す.[P ] の値は,P が正しいとき 1,正しくないとき 0 である.
例 1.20 (特性関数). 集合 A の特性関数 fA は,fA(x) = [x ∈ A] と表せる.
例 1.21 (クロネッカーの δ). クロネッカーの δ は,δij = [i = j] と表せる.従って,単位行列 In は,
i, j-成分が [i = j] であるような n-次正方行列だといえる.
関数 f : Nm → Nで値 f(x1, . . . , xm)が常に 0または 1であるものを述語 (predicate)と呼ぶ.Iverson
の記法により,下記の述語をはじめとして,さまざまな関数がきわめて簡潔に表現できる.
例 1.22 (大小比較). leq?(x, y) = [x ≤ y] の原始帰納的定義.
leq?(x, y) = sub(1, sub(x, y))
例 1.23 (等値). eq?(x, y) = [x = y] の原始帰納的定義.
eq?(x, y) = sub(1, add(sub(x, y), sub(y, x)))
以下では,pred, add, sub, mult, leq? など,前の問や教科書により原始帰納的だということが分かっている関数は,自由に用いていい.
問題 1.24 (原始帰納法 —差—). 自然数の差を計算する関数 diff(x, y) = |x− y| の原始帰納的定義を与えよ.
問題 1.25 (原始帰納法 —べき乗—). 関数 power(x, y) = xy の原始帰納的定義を与えよ.
5
問題 1.26 (原始帰納法 —階乗—). 関数 facto(x) = x! の原始帰納的定義を与えよ.
問題 1.27 (原始帰納法 —最大,最小—). 次の関数 max, min の原始帰納的定義を与えよ.
max(x, y) =
⎧⎨⎩
y x ≤ y
x x > y, min(x, y) =
⎧⎨⎩
x x ≤ y
y x > y
問題 1.28 (原始帰納法 —総和,総積—). f(x) を原始帰納的関数とするとき,f(0) から f(y − 1) までの総和
g(y) =∑
0≤x<y
f(x) = f(0) + · · ·+ f(y − 1)
と総積
h(y) =∏
0≤x<y
f(x) = f(0) × · · · × f(y − 1)
の原始帰納的定義を与えよ.
問題 1.29 (原始帰納法 —場合分け 1—). 関数 if(p, y, z) =
⎧⎨⎩
y p > 0
z p ≤ 0の原始帰納的定義を与えよ.
この関数 if を用いれば,上の max,min は,
max(x, y) = if(leq?(x, y), y, x), min(x, y) = if(leq?(x, y), x, y)
と書ける.
問題 1.30 (原始帰納法 —場合分け 2—). 関数 casen(i, �x) =
⎧⎨⎩
xi 1 ≤ i ≤ n
0 上記以外の原始帰納的定義を与
えよ.
問題 1.31 (原始帰納法 —論理演算—). [P ], [Q]を原始帰納的述語とする.述語
[P でない], [P または Q], [P かつ Q]
の原始帰納的定義を与えよ.
問題 1.32 (原始帰納法 —限定存在,限定全称—). [P (x)] を原始帰納的述語とする.次の述語 [∃x <
y P (x)], [∀x < y P (x)] の原始帰納的定義を与えよ.
∃x < y P (x) ⇐⇒ P (0) または P (1) または · · · または P (y − 1)
∀x < y P (x) ⇐⇒ P (0) かつ P (1) かつ · · · かつ P (y − 1)
問題 1.33 (原始帰納法 —素数判定—). x が素数かどうかを判定する述語 prime?(x) = [x は素数] の帰納的定義を与えよ.
6
例 1.34 (限定 µ-演算). p を N 上の n + 1 変数の原始帰納的述語とする.このとき,次の関数 f :N
n+1 → N は,原始帰納的である (この f を µz<xp(x1, . . . , xn, z) と記す).
f(�x, x) =
⎧⎨⎩
y p(�x, 0) = · · · = p(�x, y − 1) = 0, p(�x, y) = 1, y < x
x 上のような y が存在しないとき
なぜなら,述語 q を
q(�x, x) = [∀z < x (p(�x, z) = 0) かつ p(�x, z) = 0]
で定義すると,0 以上 x 以下の任意の y について p(�x, y) の値が 0 となるなら q(�x, x) = 1 であり,そうでないなら q(�x, x) = 0 となる.つまり x = 0, 1, 2, . . . に対する q(�x, x) の値は,x が p(�x, y) = 1 なる最初の y に達する前は 1 であり,達してから以後はずっと 0 である.従って,
f(�x, x) =∑y<x
q(�x)
により,f の原始帰納的定義が得られる.
例 1.35 (除算). 限定 µ-演算を用いて除算 div が簡単に定義できる.
div(x, y) = µz<x[x < (z + 1)y]
ただし,この定義では常に div(x, 0) = x である.
問題 1.36 (原始帰納法 —限定 ρ-演算—). p を N 上の n+1 変数の原始帰納的述語とする.このとき,次の関数 f : N
n+1 → N の原始帰納的定義を与えよ (この f を ρz<xp(x1, . . . , xn, z) と記す).
f(�x, x) =
⎧⎨⎩
y p(�x, y) = 1, p(�x, y + 1) = · · · = p(�x, x− 1) = 0, y < x
0 上のような y が存在しないとき
問題 1.37 (原始帰納法 —剰余—). 関数 mod(x, y) = x を y で割った余り の原始帰納的定義を与えよ.
y = 0 の場合,mod(x, y) の値は何になってもかまわない.
問題 1.38 (原始帰納法 —対数—). log2(x) (問題 1.6)の原始帰納的定義を与えよ.
問題 1.39 (原始帰納法 —整除可能性—). div?(x, y) = [x が y で割り切れる] の原始帰納的定義を与えよ.
pr(i) を i +1 番目に小さい素数とする.つまり,pr(0) = 2,pr(1) = 3, pr(2) = 5, . . . である.0 でない自然数 x の素因数分解を pr(0)x0pr(1)x1pr(2)x2 . . . とすると,x0, x1, x2, . . . は x から一意に定まる.
問題 1.40 (原始帰納法 —べき—). 自然数 x に対して,x が y で何回割り切れるかを数える関数
ord(x, y) の原始帰納的定義を与えよ.ただし,x = 0 または y = 1 に対しては ord(x, y) はどんな値になってもいい.
問題 1.41 (原始帰納法 —最大の素因数—). 自然数 x に対して,その最大の素因数,すなわち x を
割り切る最大の素数を求める関数 max-fact(x) の原始帰納的定義を与えよ.ただし,x ≤ 1 の場合,max-fact(x) = x とせよ.
7
定理 1.42. 原始帰納的関数は while プログラムで計算することができる.証明 関数定義に関する帰納法による.まず,初期関数は,それぞれ次の while プログラムで計算できる.
prog zero
endprog(0)
prog suc(x)
endprog(x+1)
prog p(x1, x2, ... , xn)
endprog(xi)
また,g : Nm → N,gi : N
n → N (i = 1, . . . , m) を計算する while プログラムを g, gi とするとき,合成関数 f(�x) = g(g1(�x), . . . , gm(�x)) は,次の while プログラムで計算できる.
prog f(x1, ... ,xn)
endprog(g(g1(x1, ... ,xn), ... ,gm(x1, ... ,xn)))
最後に関数 g : Nn → N,h : N
n+2 → N をを計算する while プログラムを g, h とするとき,
f(�x, 0) = g(�x), f(�x, y + 1) = h(�x, y, f(�x, y))
なる関数 f : Nn+1 → N は次の while プログラムで計算できる.
prog f(x1, ... ,xn,y)
var i, output;
i := 0;
output := g(x1, ... , xn);
while y > i do
output := h(x1, ... ,xn,i,output); i := i + 1;
end_while;
endprog(output)
原始帰納的でない関数が存在する.
例 1.43. たとえば,原始帰納的定義の全体は可算集合だから,各定義に番号を振る.このとき,異なる定義に異なる番号を振るようにすれば,どう番号を付けるかは問わない.空き番号があってもいい.番
号 n の定義を持つ関数を fn とするとき,2 引数関数 F を次のように定義する.
F (n, x) def=
⎧⎨⎩
fn(x, . . . , x) + 1 番号 n の定義が存在する場合
0 番号 n の定義が存在しない場合
F (n, x)はどんな番号付けに対しても原始帰納的でない.なぜなら,仮に F が原始帰納的であったとし,
その定義番号 (の一つ)を m とすれば,fm = F だから,
F (m,m) = fm(m, . . . , m) + 1 = F (m,m) + 1
8
これは矛盾である.
例 1.44. 次で定義される関数 A を Ackermann 関数と呼ぶ.
A(0, y) = y + 1 A(x + 1, 0) = A(x, 1) A(x + 1, y + 1) = A(x, A(x + 1, y))
すると A は原始帰納的でないことが,次の手順で示される.まず,定義から数学的帰納法により,
1. A(1, x) = x + 2 = 2 + (x + 3) − 32. A(2, x) = 2x + 3 = 2(x + 3) − 33. x + y + 1 ≤ A(x, y)4. A(x, y) < A(x, y + 1) ≤ A(x + 1, y)5.任意の a, b に対して,c が存在して,任意の y ∈ N に対して A(a, A(b, y)) ≤ A(c, y)
が示される.
(4) より A はどちらの引数についても狭義単調であることが分かるので,任意の原始帰納的関数 f に
対して,ある c ∈ N が存在して f(�x) ≤ A(c, max{�x}) となることが,原始帰納的定義の構造に関する帰納法で示される.実際,
• zero < 1 = A(0, 0),succ(x) = x + 1 = A(0, x),projni (�x) = xi < A(0, max{�x}) より,初期関
数については明らか.
• gj(�x) ≤ A(cj , max{�x}),h(y1, · · · , ym) ≤ A(d, max{y1, . . . , ym}) ならば,
h(g1(�x), . . . , gm(�x)) ≤ A(d, max{g1(�x), . . . , gm(�x)})≤ A(d, max{A(c1, max{�x}), . . . , A(cm, max{�x})})≤ A(d, A(max{c1, . . . , cm}, max{�x}))
だから,(5) により c が存在して,h(g1(�x), . . . , gm(�x)) ≤ A(c, max{�x}) である.
• f が f(�x, 0) = g(�x),f(�x, y+1) = h(�x, y, f(�x, y))で定義され,g(�x) ≤ A(c0, max{�x}),h(�x, y, z) ≤A(c1, max{�x, y, z}) ならば,c2 > c1,c2 ≥ c0 なる c2 に対して f(�x, y) ≤ A(c2, max{�x}+ y) が帰納法で示される.実際,
– f(�x, 0) = g(�x) ≤ A(c0, max{�x}) ≤ A(c2, max{�x} + 0)
– f(�x, y) ≤ A(c2, max{�x} + y) を仮定すると,
f(�x, y + 1) = h(�x, y, f(�x, y)) ≤ A(c1, max{�x, y, f(�x, y)})≤ A(c2 − 1, A(c2, max{�x} + y)) = A(c2, max{�x} + y + 1)
である.したがって,(2) により,f(�x, y) ≤ A(c2, max{�x}+ y) ≤ A(c2, A(2, max{�x, y})) となり,(5) により f(�x, y) ≤ A(c, max{�x, y}) となる c が存在する.
よって,もし Aが原始帰納的であれば,A(x, y) ≤ A(c, max{x, y}) なる c が存在するが,x = y = c+1とおけば,A(c + 1, c + 1) ≤ A(c, c + 1) となり (4) に矛盾する.
9
問題 1.45. Ackermann 関数 A についての上の性質 (1)–(5) を証明せよ.また,A(3, x),A(4, x) がどんな値になるか考えよ.
解答例
1. x に関する帰納法で示す.A(1, 0) = A(0, 1) = 1 + 1 = 0 + 2 である.A(1, x) = x + 2 と仮定すると,A(1, x + 1) = A(0, A(1, x)) = A(0, x + 2) = (x + 2) + 1 = (x + 1) + 2.
2. x に関する帰納法で示す.A(2, 0) = A(1, 1) = 1 + 2 = 0 × 2 + 3 である.A(2, x) = 2x + 3 と仮定すると,A(2, x + 1) = A(1, A(2, x)) = A(1, 2x + 3) = 2x + 3 + 2 = 2(x + 1) + 3.
3. 〈x, y〉 に関する 2 重帰納法による.4. 〈x, y〉 に関する 2 重帰納法による.5. c ≥ a +2, b + 1 とすれば,A(c, y) ≥ A(c− 1, y + 1) ≥ A(c− 2, A(c− 1, y)) ≥ A(a, A(b, y)) である.
A(3, x) = 2x+3 − 3,A(4, x) = I(x + 3) − 3 である.ただし 22···2
︸ ︷︷ ︸x
を I(x) と表記する.
Ackermann 関数 A を while プログラムで計算することは可能である.それには,たとえば,自然数の有限列 n0, n1, . . . , nk に対して 2 進数
1 0 · · ·0︸ ︷︷ ︸nk
· · ·1 0 · · ·0︸ ︷︷ ︸n1
1 0 · · ·0︸ ︷︷ ︸n0
を割り当てることを考え,この 2 進数を code(n0, n1, . . . , nk) と記し,自然数列 n0, n1, . . . , nk の 2進コードと呼ぶ.このとき x = code(n0, n1, . . . , nk) と y から code(y, n0, n1, . . . , nk) を計算する関数 push(x, y) は,push(x, y) = (2x + 1)2y で与えられる.逆に x = code(y, n0, n1, . . . , nk) から y と
code(n0, n1, . . . , nk) とを計算する関数をそれぞれ top(x),pop(x) とすると,どちらも容易に while プログラムで書ける.
問題 1.46. 関数 top,pop を計算する while プログラムを書け.
これらを利用すれば,Ackermann 関数を計算する while プログラム ackermannは次のように書ける.
prog ackermann(x,y)
var st;
st := 0;
while st + x > 0 do
if x = 0
then y := y+1; x := top(st); st := pop(st);
else
if y = 0
then x := x-1; y := 1;
else y := y-1; st := push(st,x-1);
endif;
endif;
endwhile;
endprog(y+1)
10
1.4 帰納的関数
N 上の n + 1 変数の述語 p に対して
f(�x) =
⎧⎨⎩
y p(�x, 0) = · · · = p(�x, y − 1) = 0, p(�x, y) = 1
未定義 上のような y が存在しないとき
なる関数 f : Nn → N を,p の最小解関数と呼び,µzp(x1, . . . , xn, z) と記す.ある述語の最小解関数を
作る操作を µ-演算,最小化などという.初期関数に関数の合成と原始帰納法と µ-演算のみを繰り返し適用して得られる関数を帰納的関数と言う.ただし,µ-演算の適用は原始帰納的述語のみに制限する.
例 1.47 (整商). 次の関数 s-div を考える.
s-div(x, y) =
⎧⎨⎩
x ÷ y x が y で割り切れるとき
未定義 x が y で割り切れないとき
s-div は µ-演算を用いて,次のように定義できるので帰納的関数である.
s-div(x, y) = µzeq?(x, mult(y, z)) = µz[x = yz]
定理 1.48 (教科書の定理 1.4.2 の変形). 任意の帰納的関数は while プログラムで計算することができる.
証明 関数定義に関する帰納法による.まず,初期関数,合成,µ-演算については,定理 1.42 の場合と同じであるから,原始帰納的述語の最小解関数についてだけ示せばいい.原始帰納的述語 p を計算する
while プログラムを p とすると,その最小解関数 f は,次の while プログラム f で計算できる.
prog f(x1,...,xn)
var output;
output := 0;
while p(x1,...,xn,output) = 0 do
output := output + 1;
endwhile
endprog(output)
自然数列 x0, x1, . . . , xn−1 に対して,ある自然数 G(x0, x1, . . . , xn−1) を対応させ,かつこの G に対
して
Gi(G(x0, x1, . . . , xn−1)) = xi
を満たす関数 Gi があれば,G(x0, x1, . . . , xn−1)から元の自然数列 x0, x1, . . . , xn−1 が復元できる.この
ような G, G0, G1, . . . , Gn−1 が原始帰納的であるとき G(x0, x1, . . . , xn−1) を自然数列 x0, x1, . . . , xn−1
のゲーデル数という.例えば, ∏i<n
pr(i)xi+1
11
は,x0, x1, . . . , xn−1 のゲーデル数である.また,先に述べた自然数列の 2 進コード
code(x0, x1, . . . , xn−1) = 1 0 · · ·0︸ ︷︷ ︸xn−1
· · ·1 0 · · ·0︸ ︷︷ ︸x1
1 0 · · ·0︸ ︷︷ ︸x0
もゲーデル数である.
上に述べた 2 種のゲーデル数に対して,以下の問題に答えよ.
問題 1.49 (列の長さ). 自然数列 x0, x1, x2, . . . , xn−1 のゲーデル数 y からその列の長さ n を求める関
数 length(y) の原始帰納的定義を与えよ.
問題 1.50 (列の要素の取り出し). 自然数列 x0, x1, x2, . . . , xn−1 のゲーデル数 y からその i+1 番目の要素 xi,先頭の要素 x0 を求める関数 get(y, i), top(y) の原始帰納的定義をそれぞれ与えよ.
問題 1.51 (列の要素の書き換え). 自然数列 x0, x1, x2, . . . , xn−1 のゲーデル数 y からその i+1 番目の要素を a に変えた列 x0, x1, . . . , xi−1, a, xi+1, . . .,xn−1 のゲーデル数を求める関数 put(y, i, a) の原始帰納的定義を与えよ.
問題 1.52 (要素の削除). 自然数列 x0, x1, x2, . . . , xn−1 のゲーデル数 y に対して,その先頭の要素を
削除した x1, x2, . . . , xn のゲーデル数を計算する関数 pop(y) の原始帰納的定義を与えよ.
問題 1.53 (列の結合). 自然数列 x0, x1, x2, . . . , xm−1 のゲーデル数 x と,自然数列 y0, y1, y2, . . . , yn−1
のゲーデル数 y とから,それらをつないだ列 x0, x1, x2, . . . , xm−1, y0, y1, y2, . . . , yn−1 のゲーデル数を
計算する関数 append(x, y) の原始帰納的定義を与えよ.
問題 1.54. f(�x, y) を原始帰納的関数とする.g(�x, z) を長さ z の自然数列 f(�x, 0), . . . , f(�x, z − 1) のゲーデル数とすると,g の原始帰納的定義を与えよ.
定理 1.55 (教科書の定理 1.4.6 の変形). while プログラムで計算できる関数は帰納的関数である.
系 1.56 (Kleene の標準化定理,教科書の系 1.4.8). すべての帰納的関数は,適当な原始帰納的関数
f と原始帰納的述語 p を使って f(�x, µz p(�x, z)) と書ける.
12
1.5 再び,計算とは何か
以下のチューリング機械の例では,Q = {q0, q1, . . . , qn} とし,状態遷移図の中では,状態 qi をただ
番号 i を囲む丸によって示す.また,特に断らない限り q1 を初期状態,q0 を終了状態する.チューリ
ング機械の扱う記号は,Σ の要素 (入出力記号)を a,b などの英小文字とし,Γ \ Σ の要素 (補助記号)を A,B などの英大文字とする.特例として,空白記号は と記す.
問題 1.57 (チューリング機械 —カウントダウン—). 次の関数 pred を 2 進数で計算するチューリング機械を作れ.
pred(n) = n−1
このとき入出力記号 a,b をそれぞれ 2 進数字 0,1の代わりに用いよ.解答例 (基本方針) 右端の a を b に変えながら,左へ進む.b が現れたら,それを a に変えて終了.
・右端へ行く. (1 → 2)・a なら b に変えながら左へ進み b を探す.
・b が見つかったら a に変え,左端へ戻って終了. (2 → 3 → 0)・見つからなかったら,入力が a だけからなる列だった場合なので,b をすべて消し,a を書いて終
了. (2 → 4 → 0)
0
1 2
b/b, Ra/a, R
/ , L
a/b, L
b/a, L
b/b, La/a, L
/ , R/ , R
b/ , R/a, N
3
4
問題 1.58 (チューリング機械 —同数?—). a,b 列 x についての次の述語 eq : ab? を計算するチューリング機械を作れ.
eq : ab?(x) = [x の中の a と b の個数が同じ]
解答例 (基本方針) 左端の文字を消し,それが a なら b を,b なら a を右から探して X に変える.左
端に戻り,同じことを繰り返す.a と b が同時になくなれば,それらの個数は同じ.a か b の一方が余
れば,個数は異なる.
・左端の文字が X ならば消しながら右へ進む. ならば a と b の個数は同じなので b を書いて終了
(1 → 0).a, b なら消す (1 → 2, 1 → 3).
13
・a を探しながら右へ進む.見つかったら X に変える (2 → 4).見つからなかったら,a と b の個数
は異なる (2 → 5).・b を探しながら右へ進む.見つかったら X に変える (3 → 4).見つからなかったら,a と b の個数
は異なる (3 → 5).・左端へ戻る (4 → 1).・残った文字を全部消し,a を書いて終了 (5 → 0).
0
1
3 24
X/ , R
a/ , Rb/ , R
/b, N
a/a, R
X/X, Rb/b, R
X/X, R
b/X, L a/X, L
/ , L
/ , Lb/b, L
X/X, L
a/a, L
b/ , L
X/ , L
a/ , L
/ , R
/a, N5
文字列 w の左右を反転した文字列を wR と記す.
問題 1.59 (チューリング機械 —回文?—). 0,1-列 xが回文かどうかを判定する次の述語 reversible?を計算するチューリング機械を作れ.
reversible?(x) = [x = xR]
解答例 (基本方針) 左端の文字を消す.同じ文字が右端になければ回文でない.同じ文字が右端にあ
ればそれを消し,左端に戻る.これを繰り返して,空列または 1 文字の列になったら回文である.
(A) 空列なら回文だから,1 を書いて終了.それ以外なら,左端の文字を消して右端へ. (0,1,2)(B) 消したのが最後の文字なら,回文だから 1 を書いて終了.右端が消したのと違う文字なら,回文でないから,全文字を消去して 0 を書き終了.消したのと同じ文字なら,それを消し,左端に戻り,(A) を繰り返す. (3,4,5,6)
14
a/ , R
b/ , R
a/a, R
b/b, R
a/a, R
b/b, R
/b, N0
/ , L
/ , L
a/ , L
b/ , L
a/a, Lb/b, L
/ , R
/b, N
/b, N
/a, N
b/ , L
a/ , L
b/ , L
a/ , L
1
2
3
4
6
5
0 7
問題 1.60 (チューリング機械 —反転—). a,b 列 x に対して xR を計算するチューリング機械を作れ.
解答例 (基本方針) 右端の文字から順に X に変えながらコピーして行き,全文字のコピーが終わった
ら X を消去して終了.
・右端へ行く (1 → 2).・文字を X に変え,右端にコピーする (2 → 3 → 6, 2 → 4 → 6).・未コピー文字まで戻る. (6 → 2).・未コピーの文字がなくなれば,X を消して終了 (2 → 5 → 0).
1 2
4 3
5
b/b, R
a/a, R
/ , L
a/X, R b/X, R
a/a, R a/a, R/a, L /b, L
/ , R
b/b, Rb/b, R
X/X, R X/X, R
6
a/a, L
b/b, L
X/X, L
X/X, L
0
X/ , R
a/a, N
b/b, N
/ , N
問題 1.61 (チューリング機械 —反転コピー—). a,b 列 x に対して xxR を計算するチューリング機械
を作れ.
解答例 (基本方針) コピー前の文字 a, b をそれぞれ A,B に変えておき,それらを右から順に a, b に戻しながら,コピーして行く.
15
・a を A,b を B に変えながら,右端へ行く (1 → 2).・文字が A なら a,B なら b に戻して,右端に行く (2 → 3, 2 → 4).・文字をコピーし一番右の未コピー文字 (A または B)まで戻る (3 → 2, 4 → 2).・未コピーの文字 A,B がなくなれば終了 (2 → 0).
1 2
4 3
0
1/B, R
a/A, R
/ , L
A/a, R B/b, R
a/a, R a/a, R
/a, L /b, L
1/b, L
a/a, L
/ , R
b/b, Rb/b, R
問題 1.62 (チューリング機械 —コピー—). a,b列 xに対して xxを計算するチューリング機械を作れ.
定理 1.63 (計算可能性,教科書の定理 1.5.2). Nn から N への部分関数は,チューリング機械で計算
できるとき,かつそのときに限り,N-プログラムで計算できる,すなわち帰納的関数である.
16
1.6 計算不可能な関数と決定不能な問題
本節の議論を進めるには,まず,プログラムのコード化ということが必要になる.一般の N-プログラムや while プログラムをすべてコード化しようとすると,話が複雑になるので,一般のプログラムのクラスと同等の計算能力を持つある部分クラスを考え,コード化の対象をそこに属するプログラムだけに
制限することにする.
教科書では,「正規形の N-プログラム」を定義しているが,本メモでは「正規形の while プログラム」を定義することにする.これらが実質的に同じクラスを成すことは明らかだろう.
正規形 while プログラムの構文を次のように定義する.
正規形 while プログラム ::=
prog プログラム名 [(変数の並び)]
var [変数の並び,] pc;
pc := 自然数;
while pc > 0 do
番号付正規文 ... 番号付正規文
endwhile;
endprog(変数)
番号付正規文 ::= if pc = 自然数 then 変数 := 自然数; pc := 自然数; endif;
番号付正規文 ::= if pc = 自然数 then 変数 := 変数 + 1; pc := 自然数; endif;
番号付正規文 ::= if pc = 自然数 then
if 変数 = 変数 then pc := 自然数; else pc := 自然数; endif;
endif;
番号付正規文 ::= if pc = 自然数 then 変数 := 変数; pc := 自然数; endif;
定理 1.64 (正規形への変換). すべての N-プログラム,while プログラム,帰納的関数に対し,それと同じ計算をする正規形の while プログラムを構成することができる.
定理 1.10,1.55 により,この定理の証明は,帰納的関数に対してだけおこなえば十分である.証明は,基本的には定理 1.42,1.48 の証明と同じで,それに系 1.11 を導いたときアイディアを組み合わせればいい.
厳密な証明を与えるよりも,具体的な例を示したほうが,分かりやすいと思われるし,学習上もその
ほうが有益であるので,通常の帰納的関数を正規形の while プログラムに変換する例を示す.
例 1.65 (加算の正規形 while プログラム). add(x, y) = x + y の原始帰納的定義は
add(x, 0) = x, add(x, y + 1) = add(x, y) + 1
である.これを while プログラムに変換すると,例えば
prog add(x,y)
var i, output;
i := 0; % 1
17
output := x; % 2
while y > i do % 3
output := output + 1; % 4
i := i + 1; % 5
endwhile
endprog(output) % 0
だから,コメントとして各行に振ってある番号に基づいて,系 1.11 のアイディアを使って正規形に変換すると
prog add(x,y)
var i, output, pc;
pc := 1;
while pc > 0 do
if pc = 1 then i := 0; pc := 2; endif;
if pc = 2 then output := x; pc := 3; endif;
if pc = 3 then if x = i then pc = 0; else pc = 4; endif; endif;
if pc = 4 then output := output + 1; pc := 5; endif;
if pc = 5 then i := i + 1; pc := 3; endif;
endwhile
endprog(output)
となる.
例 1.66 (乗算の正規形 while プログラム). mult(x, y) = x + y の原始帰納的定義
mult(x, 0) = 0, mult(x, y + 1) = add(mult(x, y), x)
を while プログラムに変換すると,
prog mult(x,y)
var i, output;
i := 0; % 1
output := 0; % 2
while y > i do % 3
output := add(output,x) % 4
i := i + 1; % 5
endwhile
endprog(output) % 0
だから,同様に正規形への変換を試みると,
prog mult(x,y)
var i, output, pc;
18
pc := 1;
while pc > 0 do
if pc = 1 then i := 0; pc := 2; endif;
if pc = 2 then output := 0; pc := 3; endif;
if pc = 3 then if x = i then pc = 0; else pc = 4; endif; endif;
if pc = 4 then output := add(output,x); pc := 5; endif;
if pc = 5 then i := i + 1; pc := 3; endif;
endwhile
endprog(output)
となる.これではまだ,文番号 4 の add(output,x) の部分が正規形からはずれているが,add の正規
形プログラムを用いて,この部分を展開して埋め込み,
prog mult(x,y)
var i, output, x1, y1, i1, output1, pc;
pc := 1;
while pc > 0 do
if pc = 1 then i := 0; pc := 2; endif;
if pc = 2 then output := 0; pc := 3; endif;
if pc = 3 then if x = i then pc = 0; else pc = 4; endif; endif;
if pc = 4 then x1 := output, pc := 41; endif
if pc = 41 then y1 := x, pc := 42; endif
if pc = 42 then i1 := 0; pc := 43; endif;
if pc = 43 then output1 := x1; pc := 44; endif;
if pc = 44 then if x1 = i1 then pc = 40; else pc = 45; endif; endif;
if pc = 45 then output1 := output1 + 1; pc := 46; endif;
if pc = 46 then i1 := i1 + 1; pc := 44; endif;
if pc = 40 then output = output1; pc := 5; endif;
if pc = 5 then i := i + 1; pc := 3; endif;
endwhile
endprog(output)
とすれば最終的な正規形プログラムが得られる.埋め込みの際に必要なことは,変数名と文番号を適当
に変更して,埋め込み先のプログラムと重複が生じないようにすることであり,埋め込み部分の最初の
2 行と最後の 1 行で,計算に用いるパラメタと計算結果の受け渡しをおこなっている.
一般に,ある while プログラム A がサブルーチン B の呼び出しを含んでいても,B 自身の正規形へ
の変換が得られているなら,B の呼び出しすべてを上の例のように展開してしまえば,A を正規形に変
換する際の障害にはならない.従って,以下の例では,
if pc = 自然数 then 変数 := サブル-チン呼び出し; pc := 自然数; endif;
19
を複数の番号付正規文の略記法として認めることにする.
例 1.67 (除算の正規形 while プログラム). 例 1.1 の除算のプログラム
prog div(x,y)
var output;
if y = 0
then
output := x;
else
output := 0;
while x >= y do
x := x - y;
output := output + 1;
endwhile;
endif;
endprog(output)
を正規形に変換することを考える.sub(x, y) = x−y,geq(x, y) = [x ≥ y] とすれば,上記プログラムは
prog div(x,y)
var zero, output;
zero = 0; % 1
if y = zero % 2
then
output := x; % 3
else
output := 0; % 4
while geq(x,y) > zero do % 5
x := sub(x,y); % 6
output := output + 1; % 7
endwhile;
endif;
endprog(output) % 0
に変形できる.これから正規形に変換するのは簡単で,
prog div(x,y)
var tmp, zero, output, pc;
pc := 1;
while pc > 0 do
if pc = 1 then zero = 0; pc := 2; endif;
if pc = 2 then if y = zero then pc := 3; else pc := 4; endif; endif;
20
if pc = 3 then output := x; pc := 0; endif;
if pc = 4 then output := 0; pc := 5; endif;
if pc = 5 then tmp := geq(x,y); pc := 51; endif;
if pc = 51 then if g = zero then pc := 0; else pc := 6; endif; endif;
if pc = 6 then x := sub(x,y); pc := 7; endif;
if pc = 7 then output := output + 1; pc := 5; endif;
endwhile;
endprog(output)
と書ける.文番号 5 と 6 のサブルーチン呼び出しは,実際には展開されて,複数の番号付正規文になる.
問題 1.68 (正規形 while プログラム —最大公約数—). 問題 1.7 の最大公約数のプログラム gcd を
正規形 while プログラムに変換せよ。
さらに正規形 while プログラム P を,次のようにして自然数に変換する.プログラム中の pc 以外の
変数の個数を L とする.まず各変数に 0 から L − 1 までの番号を振る.このとき,入力パラメタの N
個の変数には先頭から順に 0 番から N − 1 番までをふる.それ以外の L − N 個の変数は宣言順に N
から L − 1 番までをふる.さらに,番号付正規文は,4 通りのタイプがあるが,それぞれを右のように自然数に変換したものを番号付正規文のコードと呼ぶ.
if pc=q then vi:=n; pc:=b; endif; code(q, 0, i, n, b)if pc=q then vi:=vj+1; pc:=b; endif; code(q, 1, i, j, b)if pc=q then if vi=vj then pc:=b; else pc:=c; endif; endif; code(q, 2, i, j, b, c)if pc=q then vi:=vj; pc:=b; endif; code(q, 3, i, j, b)
ここで vi は番号 i の変数を表す.
最後に,正規形 while プログラム P の while と endwhile にはさまれて現れる番号付正規文を順次
s0, s1, . . . , sK−1 とすると,code(q0, L, N, I, code(c0, c1, . . . , cK−1))を P のコードと呼び,code(P )で表す.ここで ci は番号付正規文 si のコード,q0 は pc の初期値,I は出力変数の番号である.
異なるプログラムのコードが同じになることもあるが,その場合,それらのプログラムは変数名以外
はまったく同じであるから,それらが計算する関数も等しい.
次のような n + 1 引数の関数 comp(p, x1, x2, . . . , xn) を考える.
n個の入力を持つ正規形 whileプログラム P が存在して p = code(P )のときは comp(p, x1, x2, . . . , xn) =P (x1, x2, . . . , xn) となり,そうでないときは comp(p, x1, x2, . . . , xn) = 0 となる.
この関数を
定理 1.69 (万能 while プログラムの存在, 教科書の定理 1.2). comp(p, x1, x2, . . . , xn) の計算を行うwhile プログラム univ が作れる.このプログラム univ をどんな while プログラムの真似もできるという意味で,万能 while プログラムと呼ぶ.
Proof. univ は,概略,次のように構成すればいい.
21
prog univ(p,x1,x2,...,xn)
var pc, L, N, I, ss, vv, s, output, i, j, c, ...;
if p が n 個の入力を持つ正規形 while プログラムのコードである
then
pc := get(p,0);
L := get(p,1);
N := get(p,2);
I := get(p,3);
ss := get(p,4);
vv := code(x1,...,xn,0,...,0) % 0 が L-N 個
while pc > 0 do % pc が 0 になる (プログラムの終了)まで
while ss > 0 do % 全部の文について
s := top(ss); ss := pop(ss); % 各文 (のコード) s を取り出す
if get(s,0) = pc then % 文番号が pc に一致していれば
文のタイプ get(s,1) を見て各文を実行する;
endif;
endwhile
endwhile;
output := get(vv,I);
else
output := 0;
endif;
endprog(output)
「文のタイプ get(s,1) を見て各文を実行する」という部分は,例えば get(s, 1) が 0 なら変数 vi への
定数 c の代入であり,変数の番号 i は get(s,2),代入する定数 c は get(s,3),次に実行する文の番
号は get(s,4) で得られるので,次のように書けばいい.
if get(s,1) = 0 then
i := get(s,2); c := get(s,3); vv := put(vv,i,c); pc := get(s,4);
endif;
他のタイプの文についても同様に書き,それらを並べておく..
問題 1.70. タイプ 1 から 7 までの文それぞれについて,上のタイプ 0 の場合と同様に univ 内でそれ
を実行する部分を書け.
定理 1.71. n > 0 のとき, n + 1 個の入力を持ち次のような計算を行う while プログラム T は存
在しない.n 個の入力を持つ正規形 while プログラム P が存在して p = code(P ) であり,しかもP (x1, x2, . . . , xn) の計算が止まるときは T (p, x1, x2, . . . , xn) = P (x1, x2, . . . , xn) となり,そうでないときは T (p, x1, x2, . . . , xn) = 0 となる.証明: T が存在するとして,そのプログラムを tcomp と名づけ,次のプログラム h を考える.
22
prog h(p,x2,...,xn)
endprog(1 - tcomp(p,p,x2,...,xn))
h を標準系に変換したプログラムを H,そのコードを cH とする.定義より tcomp はあらゆる入力に
対して停止するので,h の実行も停止し,
h(cH , x2, . . . , xn) = 1 − tcomp(cH , cH , x2, . . . , xn) = 1 − H(cH , x2, . . . , xn) = 1− h(cH , x2, . . . , xn)
となるが,これは矛盾である.
P を真偽どちらかの値をとるような文や式とするとき,記法 [P ] で,P が正しいとき 1,正しくないとき 0 という値を表わす.たとえば,[x ≥ 5] は x が 5 以上なら 1,x が 5 未満なら 0 である. 従って,[x ≥ 5] は,x をパラメタとし,0 か 1 の値をとる関数であるが,このように値が 0 か 1 のどちらかになる関数を述語と呼ぶ.
定理 1.72. プログラムの実行が停止するかどうかを判定するプログラムは存在しない.正確に述べれば,n > 0 のとき, n + 1 個の入力を持つ while プログラムで,述語
[p が n 個の入力を持つ正規形 while プログラム P のコードでしかも P (x1, . . . , xn) の計算が止まる]
を計算するものは存在しない.
証明: そのようなプログラムが存在するとして,それを halt? と名づけ,次のようなプログラム tcomp
を考える.tcomp は,定理 1.71 の T と同じ計算を行うが,これは T が存在しないことに矛盾する.
prog tcomp(p,x1,...,xn)
var output;
if halt?(p,x1,...,xn)
then output := univ(p,x1,...,xn);
else output := 0;
endif;
endprog(output)
while プログラムは,while 文が使えることが重要であるが,これを使うのを禁止し,代わりに次の構文で書かれる for-times 文を使うようにしたものを for-times プログラムと呼ぶ.
文 ::= for 式 times do 文の並び endfor;
for-times プログラムでも,われわれが普通に考える大部分の関数を作ることができる.
例 1.73 (for-times プログラム —素数判定—). 与えられた自然数 x が素数かどうかを判定する述語
prime?(x) = [x は素数] を計算する for-times プログラム.
prog prime?(x)
var i, z, output;
if x > 1 then % x > 1 ならば
output := 1; i := 1; % 仮に x を素数としておき
23
for x - 2 times do % i = 2, 3, ..., x-1 で
i := i + 1; z := x / i; % x を試し割りする.
if z * i = x then output := 0 endif % 割り切れれば素数ではない
endfor
else % x =< 1 ならば
output := 0 % 素数ではない.
endif
endprog(output)
しかし,次の huge のように,while プログラムなら作ることができるが,for-times プログラムは作れないような関数も存在する.
例 1.74 (while プログラム —huge—). 関数 huge は,次のように再帰的に定義される.
huge(0, x) = x + 1, huge(k + 1, x) = huge(k, huge(k, . . . , huge(k︸ ︷︷ ︸x
, x) . . . ))
この hugeを計算する while プログラムは,例えば次のようにスタックの概念を応用すれば,簡単に作成できる.
この例の push,pop,top は,?? 節で述べた関数である.
prog huge(k,x)
var stack;
stack := push(0,k); % k を空のスタックに押し込む
while stack > 0 do % スタックが空になるまで
k := top(stack); stack := pop(stack); % スタックの先頭の要素を取り出す
if k = 0
then x := x + 1;
else
for x times do
stack := push(stack,k-1); % k-1 を x 個
endfor; % スタックに押し込む
endif;
endwhile;
endprog(x)
しかし,huge を計算するような for-times プログラムは存在しない.また,次のように定義されるAckermann 関数 ack(k, y) も for-times プログラムでは計算できないが,やはりスタックの考えを用いて,while プログラムでなら簡単に計算できる.
問題 1.75 (Ackermann 関数). Ackermann 関数 A は,次のように再帰的に定義される.
A(0, x) = x + 1, A(k + 1, 0) = A(k, 1), A(k + 1, x + 1) = A(k, A(k + 1, x),
A を計算する while プログラム ack(k, x) を作れ.
24
問題 1.76 (カタラン数). c0 = 1,cn+1 =∑n
i=0 cicn−i で定義される数 cn をカタラン数という.例え
ば c1 = c0c0 = 1,c2 = c0c1 + c1c0 = 1 + 1 = 2,c3 = c0c2 + c1c1 + c2c0 = 2 + 1 + 2 = 5 である.上のスタックの考え方を使って,n+1 番目のカタラン数 cn を求める while プログラム catalan(n)を作れ.
定理 1.77 (万能関数,教科書の定理 1.6.1). 万能関数 compn は計算可能である.
定理 1.78 (万能関数の全域化,教科書の定理 1.6.2). 万能関数 compn の全域化は計算不能である.
帰納的全域関数であるような述語を帰納的述語または決定可能述語という.チューリング機械の停止
問題は決定不能である.正確には次の定理が成り立つ.
定理 1.79 (チューリング機械の停止問題,教科書の定理 1.6.3). n + 1 引数の述語 haltn を
haltn(z, �x) def= [compn(z, �x) が定義されている]
とすると,n > 0 のとき,haltn は帰納的でない.
定理 1.80 (帰納的関数の枚挙不能性,教科書の定理 1.6.7). 帰納的全域関数は枚挙不能である.
定理 1.81 (部分計算定理,SMN 定理,教科書の補題 1.6.9). m,n を任意の自然数とする.このと
き,任意の �x ∈ Nn,�y ∈ N
m,z ∈ N に対して
compn+m(z, �x, �y) = compn(Sm.n(z, �y), �x)
を満たす原始帰納的関数 Sm,n : Nm+1 → N が存在する.
定理 1.82 (再帰定理,教科書の補題 1.6.10). n を任意の自然数,f を任意の帰納的関数とする.この
とき,ある自然数 c が存在して,任意の �x ∈ Nn に対して compn(c, �x) = compn(f(c), �x) となる.
Proof. 関数 g(�x, y) = compn(y, �x, y) とすると,f は帰納的関数であるからそのコードを a として,部
分計算定理の S1,n を用いると
compn(S1,n(a, y), �x) = compn+1(a, �x, y) = g(�x, y) = compn+1(y, �x, y) (1)
が成り立つ.さらに h(�x, y) = compn(f(S1,n(a, y)), �x) とすると h も帰納的関数であるからそのコード
を b とすると,
compn+1(b, �x, y) = h(�x, y) = compn(f(S1,n(a, y)), �x) (2)
となる.(1) と (2) で y に b を代入すれば,
compn(S1,n(a, b), �x) = compn+1(b, �x, b) = compn(f(S1,n(a, b)), �x)
となるので,c = S1,n(a, b) とおけば,所望の結論が得られる.
帰納的関数に関する意味のある述語は帰納的でない.正確に言えば,次の定理が成り立つ.
定理 1.83 (Rice の定理,教科書の定理 1.6.11). n を任意の自然数とする.述語 p(z) が次の条件を満たすならば p は帰納的述語ではない.
1.任意の自然数 y と z について,ϕny = ϕn
z ならば p(y) = p(z) である.2.自然数 z によって p(z) は真にも偽にもなりうる.
25
1.7 決定可能集合と枚挙可能集合
A ⊂ Nn とする.述語 [�x ∈ A] が決定可能なとき,A を決定可能集合または帰納的集合と呼ぶ.A が
ある帰納的関数 f : Nn → N の定義域に等しいとき,A を枚挙可能集合または帰納的可算集合という.
26
2 λ 計算の基礎
2.1 λ 計算とは何か?
λ 計算で用いる記号は,変数 x, y, . . . と次の 4 種の補助記号のみである。
λ ( ) .
それらの記号をこれから述べる規則に従って並べた列を λ 式と言い,その λ 式に β 簡約と呼ぶ操作を
行なうことで実行される計算の仕組みを λ 計算と呼ぶ.結論を先に述べれば,λ 計算で可能な計算は,
帰納的関数や Turing 機械でも可能であり,逆もまた真である.この意味で,λ 計算が提供する計算に
は,新しいものは全くないが,他の計算モデルにはない λ 計算の重大な特徴に,プログラムとデータが
どちらも全く同じ形をしていることがある.つまり,λ 式は,全て (何の変換もなしで)そのままプログラムにもデータにもなりうる.この λ計算の特徴を生かして設計されたプログラム言語に LISP がある.
定義 2.1 (λ 式,変数の自由出現,束縛). λ 式とその中の変数の自由出現,束縛の概念は次のように帰
納的に定義される.
1.変数 x は λ 式である.λ 式 x 内の変数 x の出現は自由出現である.
2. M が λ式で x が変数のとき (λx.M )は λ式である.M 内の変数 x の自由出現は,この λx によっ
て束縛されたといい,(λx.M )内では自由出現ではなくなる.x 以外の変数の自由出現は (λx.M )内でも自由出現のままである.(λx の x は,束縛を指定するものであり,変数の出現とは言わない.)
3. M と N が λ 式のとき (MN) は λ 式である.M と N 内の変数の自由出現は (MN) 内でも自由出現のままである.
λ 式の全体を L で表す.
定義 2.2 (λ 式の略記法).
(λx1.(λ.x2. · · · (λxn.(· · · ((NM1)M2) . . .Mm)) · · · ))
を λx1x2 · · ·xn.NM1M2 . . .Mm と略記する.さらに,x1x2 · · ·xn や M1M2 . . .Mm の部分をベクトル
表記して λ�x.N �M のように表すこともある.
直観的には,式 (MN) は関数 M をパラメタ N に適用した結果の値を表す.また,式 λx.M は,x
をパラメタとしてもらって M を計算するような関数を表す.通常のプログラムとの対応でいえば,式
(MN) は N を「実引数」として関数 M を呼び出す操作にあたる。また式 λx.M は,x を「仮引数」
とし,関数本体 M を計算させるような関数定義に相当する。
x(λx.xx(λx.x))x などのように,一つの λ 式の中に同じ変数の自由出現とそうでない出現が共存する
ことがあることに注意せよ.ただし,このような場合,自由出現している同じ変数は全て同じものを表
すと考えねばならないが,束縛されている変数とは全く無関係と考えていい.また,束縛されている変
数同士も,それらを束縛する λ の位置が異なるなら,同じ変数であっても別物と考えていい.例えば
x(λx.xx(λx.x))x なら,まず,記号 λ の直後の 2 つの x は変数の出現とは言わない.x の出現のうち,
最初と最後は自由出現なので同じものを表すが,他の x とは別と考えていい.第 2,第 3 の出現は,同
27
じ λ によって束縛されているので同じものを表すが,第 4 の出現は,束縛する λ が異なるのでまた別
物である.
このように自由,束縛の区別は,変数の出現ごとになされるのが厳密には正しいが,混乱のおそれが
なければ,自由出現している変数 (あるいはその出現を)を自由変数と言うことがある.同様に束縛変数という言葉も使うことがあるが,これは束縛している λ の位置も込めての表現である.
定義 2.3. λ 式 M 内に自由出現している変数の全体を FV(M) で表す.
定義 2.4 (結合子). FV(M) = ∅ のとき,すなわち M が変数の自由出現を含まないとき,M を結合子
(combinator)と呼ぶ.頻繁に利用する一部の結合子を次のように定義する.
K def= λxy.x, S def= λxyz.xz(yz), I def= λx.x B def= λxyz.x(yz), C def= λxyz.xzy
自由変数と束縛変数は別のものであり,さらに束縛されている変数は,それを束縛する λ の位置だ
けが問題であって変数名に何が使われているかは大きな意味を持たない.例えば x(λx.xx(λx.x))x とx(λy.yy(λz.z))x は,実質的には同じものを表す式である.λ 式への代入の定義は,これらのことに注
意しながら,次のように与えられる.
定義 2.5 (代入). N, M を λ 式とし,x を変数とする.このとき,N 内の x の自由出現に M を代入
した式 N [x := M ] とは,帰納的に次のように定義される.
1. N = x なら N [x := M ] = M.
2. N が x 以外の変数なら N [x := M ] = N.
3. N = (λy.P ) なら N [x := M ] = (λz.P ′[x := M ]).ここで,z は x 以外で P にも M にも自由出現
しない任意の変数とし,P ′ は P 内の y の自由出現を全て z に変えた式を表す.
4. N = (N1N2) なら N [x := M ] = (N1[x := M ]N2[x := M ]).
代入の定義を厳密に適用すると,(3) で述べているように,代入前に N 内の束縛変数の名前を全て変
更する必要があるが,この操作は,一つには M の自由変数が代入の結果束縛されることがないように
するためであり,もう一つには束縛変数に M が代入されたりしないようにするためである.従って y
が x でも M の自由変数でもないときは (λy.P )[x := M ] = (λy.P [x := M ]) としてもかまわない.また,(λx.P )[x := M ] = (λx.P ) としてよい.一般に,N の束縛変数と M の自由変数に共通のものががないとき,N, M は変数条件を満たすとい
い,そのような場合 N [x := M ] は,N 内の x の自由出現を M で単純に置き換えた式と考えてかまわ
ない.変数条件を満たさないときは,満たすように N の束縛変数の名前を変えたあとで,置き換えを
行えばいい.
定義 2.6 (β 簡約 →β,∗→β,=β). λ 式の β 簡約 →β を帰納的に次のように定義する.
1. (λx.N )M →β N [x := M ].2. M →β N なら λx.M →β λN.
3. M →β N なら MP →β NP かつ PM →β PN.
直観的には P →β Q とは,P が (λx.N )M という形の式を部分式として含み,Q は P においてそ
の部分式を N [x := M ] に変えて得られる式の場合である.(λx.N )M の形の部分式を β 基と呼ぶ.
28
β 簡約を 0 回以上有限回行って P から Q に行けるとき,すなわち,P0, P1, . . . , Pn が存在して
P = P0 →β P1 →β · · · →β Pn = Q のとき,P∗→β Q と書く.互いに β 簡約可能な λ 式を同一視する
ことによって得られる同値関係を =β で表す.
P∗→β Q であり,かつ Q が β 基を持たない,すなわちそれ以上の β 変換が不可能であるとき,式 Q
を式 P の β 正規形 と呼ぶ。あとで示されるように,ある式の β 正規形は,(存在すれば)ただ一つに定まる。
問題 2.7. 次の式の β 正規形を求めよ。
1. (λx.xx)yz
2. (λxz.xz)z3. (λx.x(λab.a))(λx.x)4. (λxy.yx)(λz.zy)5. (λa.a(λb.ab)b)(λc.cb)6. SKK
定義 2.8 (不動点結合子). 任意の λ式 F に対し,不動点MF を与える (すなわち任意の F ∈ Lに対し
て MF =β F (MF ) となる) 結合子 M を不動点結合子と呼ぶ.例えば Y def= λf.(λx.f(xx))(λx.f(xx))は不動点結合子である.
上の Y を Curry の不動点結合子と呼ぶ1.不動点結合子は Y 以外にも無限個存在する.例えば
A = λxy.y(xxy) とすれば,
AAF = λxy.y(xxy)AF →β (λy.y(AAy))F →β F (AAF )
であるから,AA も不動点結合子である.AA を Turing の不動点結合子と呼ぶ.
定理 2.9 (Bohm and van der Mey). G def= λyf.f(yf)(=β SI) とする.結合子 M が不動点結合子
であるための必要十分条件は M が G の不動点であることである.
Proof. (十分性) M =β GM とする.MF =β (GM )F →β (λf.f(Mf))F →β F (MF )(必要性) M を不動点結合子とする.GM →β λf.f(Mf) =β λf.Mf である.(よって後で述べる η
簡約規則があれば,明らかに GM = M であるが,そうでない場合)Mf =β f(Mf) なので合流性から Mf
∗→β T, f(Mf) ∗→β T なる T が存在する.f が変数であるから T は f(. . . ) という形なので,Mf
∗→β T であるためには,M∗→β f または M
∗→β λy.N でなければならない.前者の場合,
ff =β f(ff) となり,合流性に矛盾する.後者の場合,N は y 以外に自由変数を持たないので
GM =β λf.Mf = λf.(λy.N)f →β λf.N [f/y] = λy.N = M
である.
系 2.10. Y0def= Y, Yn+1
def= YnG とすると,Y0, Y1, Y2, . . . はすべて不動点結合子である.
1Curryの Paradoxical Combinator と呼ばれることもある.というのは,否定演算子 ¬に形式的に適用すれば ¬(Y¬) = Y¬だから.
29
Proof. 数学的帰納法による.Y0 については証明済みである.Yn が不動点結合子とすると,YnG はG の不動点である.よって,定理 2.9 により Yn+1 = YnG も不動点結合子である.
実際,先に述べた Turing の不動点結合子 AA は Y1 に他ならない.もっと正確に言うと AA =β Y1
である.
定理 2.11 (再帰呼び出し定理). C が自由変数 f と �x を含む λ 式であるとする.このとき,任意の �X
に対して F �X =β C[f := F, �x := �X ] となるような λ 式 F が存在する.
Proof. M を不動点結合子とし,F = M(λf�x.C) とすればよい.
上記定理は,(Y などの)不動点結合子を使うことにより,あらゆる種類の再帰的計算を λ 計算によっ
てシミュレートできることを述べている。具体的な使い方は,2.4節などの例を見れば,明らかになろう。
問題 2.12 (相互再帰呼び出し). C,D が自由変数 f,g と �x を含む λ 式であるとする.このとき,任
意の �X に対して F �X =β C[f := F, g := G, �x := �X ],G �X =β D[f := F, g := G, �x := �X ] となるようなλ 式 F,G を構成せよ。
30
2.2 Church-Rosser の定理と正規化定理
問題 2.13 (最左簡約と正規形). 次の各 λ-式を繰り返し最左簡約し,正規形を求めよ.
1. (λx.x(λab.a))(λx.x)2. (λxyz.xz(yz))pqr
3. (λxyz.xz(yz))(λuv.u)(λab.a)4. (λxy.yx)(λz.zy)5. (λa.a(λb.ab)b)(λc.cb)
問題 2.14 (正規形). [[2]] = (λfx.f(fx)), [[3]] = (λfx.f(f(fx))) とする.次の各 λ-式の正規形を求めよ.
1. [[2]][[3]]2. [[3]][[2]]
教科書にある標準簡約列の定義 (p.76)には間違いがあるので,ここに正しい定義と例を与える.
定義 2.15. β 簡約 M →β N において簡約の対象になる β 基の左にある λ 記号の数を r(M →β N) で表す.β 簡約列 M0 →β M1 →β · · · →β Mn において
r(Mi−1 →β Mi) ≤ r(Mi →β Mi+1) (i = 1, 2, . . . , n − 1)
が成り立つとき,この β 簡約列を標準簡約列 (standard reduction sequence)という.
たとえば r((λx.x)((λy.z)a) →β (λx.x)z) = 1,r((λx.x)z →β z) = 0 だから,r((λx.x)((λy.z)a) →β
(λx.x)z) →β z は標準簡約列ではない.一方,r((λx.x)((λy.z)a) →β (λy.z)a) →β z は標準簡約列で
ある.
定理 2.16 (標準化定理). M∗→β N ならば M から N への標準簡約列がある.
2.3 不定式と Bohm の木
問題 2.17 (不定式). Curry の不動点結合子 Y = λy.(λx.y(xx))(λx.y(xx)) と Turing の不動点結合子AA = (λxy.y(xxy))(λxy.y(xxy)) を p に適用した λ 式 Yp,AAp それぞれに最左簡約を 100 回適用した結果はどのようになるか?
31
2.4 λ 計算と計算可能性
定義 2.18 (真偽値).
�true� def= λxy.x(= K), �false� = λxy.y(=β KI)
このとき if T then B else C は,単に λ 式 TBC で実現できる.実際,
�true�BC∗→β B, �false�BC
∗→β C
である.また,and, or, not に当たる λ 式は次のように定義できる.
定義 2.19 (論理演算子).
�and� = λxy.xy�false�, �or� = λxy.x�true�y, �not� = λx.x�false��true�
定義 2.20 (対, 組, 合成).
[M, N ] def= λz.zMN, π1def= λp.p�true�, π2
def= λp.p�false�
〈M0, . . . , Mn−1〉 def= λz.zM0M1 . . .Mn−1, πi,ndef= λp.p(λx0x1 . . . xn−1.xi),
M ◦ Ndef= λz.M(Nz)(=β BMN)
このとき,π1[M, N ] =β M, π2[M, N ] =β N, πi,n〈M0, . . . , Mn−1〉 =β Mi である.実際,
π1[M, N ] →β [M, N ]�true� →β �true�MN∗→β M
π2[M, N ] →β [M, N ]�false� →β �false�MN∗→β N
πi,n〈M0, . . . , Mn−1〉 →β 〈M0, . . . , Mn−1〉(λx0x1 . . . xn−1.xi)
→β (λx0x1 . . . xn−1.xi)M0 · · ·Mn−1∗→β Mi
定義 2.21 (標準数).
�0� def= I, �n + 1� def= [�false�, �n�],
�succ� def= λx.[�false�, x], �pred� def= λx.x�false�,
自然数 n を �n� で表現すると,�succ�, �pred� はそれぞれ後者関数 succ,前者関数 pred を表す.
また,�true� に適用することで零判定ができる.例えば,
�succ��n� →β [�false�, �n�] = �n + 1�,
�pred��n + 1� →β �n + 1��false� = [�false�, �n�]�false� →β �false��false��n� →β �n�,
�pred��0� →β �0��false� = I�false� →β �false�,
�n + 1��true� = [�false�, �n�]�true� →β �true��false��n� →β �false�,
�0��true� = I�true� →β �true�
32
となる.従って,加法 �add� は
�add�xy = (x�true�)y(�add�(�pred�x)(�succ�y))
(正確には定理 2.11 より �add� = Y(λfxy.(x�true�)y(f(�pred�x)(�succ�y)))
と書ける.
定義 2.22 (Church 数).
�n1, . . . , nk�cdef= λf1 . . . fk.fn1
1 ◦ . . . ◦ fnk
k , �πi,k�cdef= λpf.pI . . . IfI . . . I,
�succ�cdef= λpf.f ◦ (pf)
ここで,fn は f0 = I, fn+1 = f ◦ fn と定義するものとする.
このとき
�πi,k�c�n1, . . . , nk�c →β λf.�n1, . . . , nk�cI . . . IfI . . . I ∗→β λf.fni = �ni�c
�succ�c�n�c →β λf.f ◦ (�n�cf) →β λf.f ◦ fn = �n + 1�c
となる.また,
�n + 1�c(λy.�false�)�true� →β (λy.�false�)n+1�true�∗→β (λy.�false�)((λy.�false�)n�true�) →β �false�
�0�c(λy.�false�)�true� →β I�true� →β �true�
であるから,λy.�false� と �true� に適用することにより,零判定が可能だ.
問題 2.23. �+�c,�×�c,�pow�c をそれぞれ,
�+�cdef= λxypq.xp(ypq)
�×�cdef= λxyz.x(yz) (= B)
�pow�cdef= λxy.yx
と定義する。このとき,任意の m, n ∈ N に対して
�+�c�m�c�n�c =β �m + n�c
�×�c�m�c�n�c =β �mn�c
�pow�c�m�c�n�c =β �mn�c
となることを示せ。
Church 数と標準数とを互いに対応させる関数 H (H�n� = �n�c, H−1�n�c = �n�) は
Hx = (x�true�)�0�c�succ�c(H(�pred�x))
H−1x = x�succ��0�
33
で与えられる.従って λx.x�succ��0��true�はChurch数の零関数である.また,前者関数はH ◦�pred�◦H−1 と書けるが,対 �n, m�c を利用して定義することもできる.まず S(�n, m�c) = �n + 1, n�c とな
るように関数 S を Sdef= λpfg.f ◦ (pfI) ◦ (pgI) と定義する.このとき前者関数 �pred�c は �pred�c
def=λp.�π1,2�c(pS�0, 0�c) と定義できる.あるいは対 [�n�c, �m�c] を利用して,
Sdef= λp.[�succ�c(p�true�), p�true�], �pred�c
def= λp.(pS[�0�c, �0�c])�false�
としてもよい.
定義 2.24 (教科書の定義 2.4.5 の一般化). 閉 λ 式の列 d0, d1, . . . があり,dn で自然数 n を表すこと
を考える.φ : Np → N を部分関数とする.λ 式 F が任意の n0, . . . , np−1 ∈ N に対して次を満たすと
き,F を φ の λ 表現と呼ぶ.
1. φ(n0, . . . , np−1) = k ∈ N のとき Fdn0 . . . dnp−1
∗→β dk である.
2. φ(n0, . . . , np−1) が未定義のとき Fdn0 . . . dnp−1 は不定式である.
このとき φ を (表現 {di} に関して) λ 定義可能 (λ 表現可能)だといい,このような F の一つを dφ と
表記する.λ 式 Z0, . . . , Zk−1 があり,
d0�Z =β �true�, dn+1
�Z =β �false�
という性質を持つとき,表現 {di} は零判定ができるという.すべての n ∈ N に対して dn が正規形を
しているとき,表現 {di} は 正規であるという.
補題 2.25. すべての帰納的関数が表現 {di} に関して λ 定義可能なとき,次は同値である.
1.ある m, n ∈ N (m �= n) に対して dm = dn である.
2. d1 = d0
3.任意の n, m ∈ N に対して dm = dn である.
Proof. (3) =⇒(2) =⇒(1) は 明らかである.ある m, n ∈ N (m �= n) に対して dm = dn とすると,
d0 = d[m=n] = deq?dmdn = deq?dndn = d[n=n] = d1
であるから (1) =⇒(2) が成り立つ.また d1 = d0 とすると
dm = dmultdmd1 = dmultdmd0 = d0 = dmultdnd0 = dmultdnd1 = dn
であるから (2) =⇒(3) が成り立つ.
定義 2.26. 表現 {di} は,すべての帰納的関数が λ 定義可能であり,d1 �= d0 であるとき,適切である
という.
命題 2.27. 表現 {di} が適切であれば,λ 定義可能な任意の関数 φ と任意の n0, . . . , np−1, m ∈ N に対
して φ(n0, . . . , np−1) = m ⇐⇒ dφdn0, . . . , dnp−1 = dm である.
Proof. 定義と前補題より明らかである.
34
定理 2.28 (教科書の定理 2.4.6 の一般化). 後者関数 succ,前者関数 pred が λ 定義可能であり,零
判定が可能ならば,表現 {di} は適切である.
Proof. 表現 {di} が �Z により零判定可能とする.まず,もし d1 = d0 ならば
�false� = d1�Z = d0
�Z = �true�
であるから矛盾する.次にすべての原始帰納的関数が λ 定義可能であることを関数の構成に関する帰納
法で示す.
1. (初期関数) 後者関数 succは仮定から,また射影 projni とゼロ zero はそれぞれ λx0x1 . . . xn−1.xi
と d0 により λ 定義可能である.
2. (合成) h と g0, . . . , gm−1 が λ 定義可能であるとすると合成 f(�x) = h(g0(�x), . . . , gm−1(�x)) はλ�x.dh(dg0�x) . . . (dgm−1�x) により λ 定義可能である.
3. (原始帰納法) h と g が λ 定義可能で
f(�x, 0) = g(�x), f(�x, y + 1) = h(�x, y, f(�x, y))
であるとする.このとき f は
λ�x.Y(λfy.(y �Z)(dg�x)(dh�x(dpredy)(f(dpredy))))
または Y(λf�xy.(y �Z)(dg�x)(dh�x(dpredy)(f�x(dpredy))))
により λ 定義可能である.
最後にすべての帰納的関数が λ 定義可能であることを示す.系 1.56 Kleene の標準化定理により,帰納的関数は f(�x, µz p(�x, z)) と書ける.p が λ 定義可能だから,µzp(�x, z) は
M = λ�x.Y(λfz.((dp�xz �Z)(f(dsuccz))z)d0
により λ 定義可能である.f も λ 定義可能だから,
G = λ�x.M�x�ZIIdf�x(M�x)
で求める λ 式が得られる.実際,µzp(k0, . . . , kn−1, z) が未定義ならばMdk0 · · ·dkn−1 は不定式だから,
Gdk0 · · ·dkn−1 も不定式である.µzp(k0, . . . , kn−1, z) = n なら n に無関係に
Gdk0 · · ·dkn−1
∗→β Mdk0 · · ·dkn−1�ZIIdfdk0 · · ·dkn−1(Mdk0 · · ·dkn−1)
∗→β dn�ZIIdfdk0 · · ·dkn−1(Mdk0 · · ·dkn−1)
∗→β Idfdk0 · · ·dkn−1(Mdk0 · · ·dkn−1)∗→β dfdk0 · · ·dkn−1(Mdk0 · · ·dkn−1)
上の定理により,標準数 �i� と Church 数 �i�c はともに適切な表現であることが分かる.
φ を λ 式から自然数への適当なコード化とする.
35
定理 2.29 (Rice の定理の λ 計算版,教科書の定理 2.4.8). λ 式の集合 S が =β のもとで閉じている
とする.すなわち,M ∈ S かつ M =β N ならば N ∈ S とする.このとき,S = L でも S = ∅ でもなければ,S は決定可能でない。すなわち,f(φ(M)) = [M ∈ S] を満たす帰納的関数 f は存在しない.
2.5 公理系 β とその拡張
36
3 おまけ
3.1 決定可能性
まず,2つの自然数 m と n の対を一つの自然数 〈m,n〉 で表すという問題を考える.これには二つの要請が伴う.第 1には,異なる対には異なる自然数を対応させること,第 2には,簡単な計算を行うことで (正確には,原始帰納的関数により),対を表現する自然数 〈m,n〉 から元の m, n を復元できること
だ.これには色々なやり方が考えられるが,代表的なものを二つ挙げる.
1. 〈m,n〉 = 2m(2n + 1)2. 〈m,n〉 = (m + n)(m + n + 1)/2 + m + 1
前者の場合,x = 〈m,n〉 = 2m(2n+1)から mを計算する関数は (x)2 で与えられる.また nは pred(x÷2(x)2)÷2で計算できるが,どちらも原始帰納的である.後者の場合,まずm+n が g(x) = pred(µz.2x ≤z(z + 1)) で与えられる.よって m = pred(z−g(z)(g(z) + 1)), n = g(z) − pred(z−g(z)(g(z) + 1)) だが,どちらも原始帰納的である.
• 自然数の列を自然数で表す (その 1)— pi を i 番目の素数として
〈x0, . . . , xn−1〉 def=∏i<n
pxi+1i
a = 〈x0, . . . , xn−1〉,b = 〈y0, . . . , ym−1〉 とするとき
– xi = (µx < a)(px+1i |a ∧ ¬(px+2
i |a)) (i.e. 列の i 番目の要素)
– n = |x0, . . . , xn−1| = (µi < a)¬(pi|a) (i.e. 列の長さ)
– 〈x0, . . . , xn−1, y0, . . . , ym−1〉 = a∏
i<m pyi+1i+n (i.e. 列の連接)
• 自然数の列を自然数で表す (その 2)
〈ε〉 = 0, 〈x, x0, . . . , xn−1〉 = 2x(2〈x0, . . . , xn−1〉 + 1)
• 自然数の列を自然数で表す (その 3)
〈ε〉 = 0, 〈x, x0, . . . , xn−1〉 = 1/2(x + 〈x0, . . . , xn−1〉)(x + 〈x0, . . . , xn−1〉 + 1) + x + 1
• 計算可能な全域関数は帰納的全域関数である.
• 計算可能な部分関数は帰納的部分関数である.
自然数 n を λ-式[[n]] = λfx. f(f(· · · (f︸ ︷︷ ︸
n
x) · · · ))
で定義する.特に [[0]] = λfx.x,[[1]] = λfx.fx である.
37
問題 3.1 (場合分け). 論理値の偽を [[0]] で,真を [[1]] で表現するとき,次の性質を持つ λ-式 [[if]],すなわち if p then a else b endif を [[if]]pab で実現する λ-式 を求めよ.
[[if]][[0]]ab =β b, [[if]][[1]]ab =β a
問題 3.2 (対). λ-式 M, N の対を λu.uMN で表し,これを [[M, N ]]と記述する.与えられた M, N に
対し [[M, N ]] を計算する λ-式を求めよ.また,
Π1[[M, N ]] =β M, Π2[[M, N ]] =β N
なる λ-式 Π1, Π2 を求めよ.
問題 3.3 (原始帰納法). 関数 g(x1, x2, . . . , xn), h(x1, x2, . . . , xn, y, z) が,それぞれ λ-式 [[g]], [[h]]で計算できるとする.このとき,原始帰納法で
f(�x, 0) = g(�x), f(�x, y + 1) = h(�x, y, f(�x, y))
と定義される関数 f を計算する λ-式 [[f ]] を求めよ.
3.2 for-times プログラム
問題 3.4 (for-times プログラム —素数判定—). 与えられた 2 進数が素数かどうかを判定する述語prime?(x) = [x は素数] を計算する for-times プログラムを作れ.ただし,自然数の加減乗除を行う関数 add, mult, sub, div を基本関数として用いてよい.
解答例
prog prime?(x)
if x > 1 then % x > 1 ならば
output := 1; i := 1; % 仮に x を素数としておき
for sub(x,2) times do % i = 2, 3, ..., x-1 で
i := i+1; z := div(x,i); % x を試し割りする.
if mult(z,i) = x then output := 0 endif % 割り切れれば素数ではない
endfor
else % x =< 1 ならば
output := 0 % 素数ではない.
endif
endprog
3.3 帰納的 vs. S プログラム計算可能性
帰納的関数はすべて Sプログラム計算可能である.なぜなら,明らかに初期関数 zero, succ,projni は S
プログラム計算可能であるし,関数 g, g1, . . . , gm を実現する Sプログラムがあるなら f = g(g1, . . . , gm)を実現する S プログラムも簡単に作れる.また,原始帰納法,µ-演算も,次のように S プログラムで実現できる.
38
例 3.5 (原始帰納法の S プログラムによる実現). 関数 g : Nn → N,h : N
n+2 → N に対して f を
f(�x, 0) = g(�x), f(�x, y + 1) = h(�x, y, f(�x, y))
とする.g, h がそれぞれ g, h を計算する S プログラムなら f を計算する S プログラムは,次のように書ける.
prog f(x1, x2, ..., xn, y)
output := g(x1, x2, ..., xn); i := 0;
for y times do
output := h(x1, x2, ..., xn, i, output); i := i + 1;
endfor;
endprog
例 3.6 (µ-演算の S プログラムによる実現). 述語 p を計算する S プログラムを p とすると f =µzp(x1, . . . , xn, z) を計算する S プログラムは,次のように書ける.
prog f(x1, x2, ..., xn);
output := 0;
while p(x1, x2, ..., xn, output)=0 do
z := z + 1;
endwhile;
endprog
例 3.7 (正規形 S プログラムの帰納的関数への変換). 例 ?? の標準系 S プログラム rev4 を帰納的関
数 rev に変換する (ただし rev のパラメタ x は,rev4 への入力文字列そのものではなくてそのゲーデ
ル数であり,rev が返す値も rev4 の出力文字列のゲーデル数である).rev4 の実行途中の状態は,変
数 pc, v1,v2,v3 の値だけで決まるので,それらをこの順に並べた自然数列のゲーデル数 z で状態を
表現すると,z から 1 ステップ実行が進んだあとの状態は,
case(get(z,0),
1: put(put(z,2,get(z,1)),0,2),
2: put(put(z,3,’’),0,3),
3: if(eq?(get(z,1),get(z,3)),put(z,0,4),put(z,0,5)),
4: put(put(z,1,’’),0,0),
5: put(put(z,1,’’),0,6),
6: put(put(z,3,’’),0,7),
7: if(eq?(get(z,2),get(z,3)),put(z,0,0),put(z,0,8)),
8: put(put(z,3,tail(get(z,2))),0,9),
9: put(put(z,1,append(get(z,1),get(z,3))),0,10)
10: put(put(z,2,left(get(z,2))),0,5)
)
39
で表される (ただし,各行の番号は,rev4 との対応を見やすくするためのものであり,関数の一部分で
はない.また,’’ は空列そのものではなく,そのゲーデル数である).これを z をパラメタとする関数
と考え,one-step と名づける.one-step は明らかに原始帰納的である.さらに a を 〈’’, ’’, ’’, 1〉 のゲーデル数とし,
config(x, 0) = put(a, 1, x), config(x, n + 1) = one-step(config(x, n))
によって config(x) を定義すると,これは原始帰納的関数であり,入力 x に対する n ステップ目の
rev4 の状態を表す.従って
rev(x) = get(config(x, µneq?(get(config(x, n), 0), 0)), 1)
は,帰納的関数であり,rev4 をシミュレートする.
3.4 帰納的 vs. チューリング機械計算可能性
省略
3.5 原始帰納的関数と帰納的関数
ここでは,話を簡単にするために,すべての for-times プログラムで halt 文は使われていないもの
とし,計算結果は output という変数に格納されるものとしよう.一般の for-times プログラムをそのような形のものに変換することは機械的にできる.
例 3.8 (for-times プログラムの原始帰納的関数への変換 —除算—). 次の割り算の for-times プログラム div2 を原始帰納的関数に変換しよう.
prog div2(x,y);
output := 0;
if y > 0 then
for x times do
if x >= y then x := x - y; output := output + 1 endif
endfor
endif
endprog
このプログラムの実行途中の状態は,変数 output,x,y の値だけで決まるので,それらを並べた自然数
列のゲーデル数で状態を表現し,各文の実行前の状態から実行後の状態への変化を原始帰納的関数として
表現する.たとえば,x := x - y は f(z) = put(z, 1, sub(get(z, 1), get(z, 2))), output := output +
1 は g(z) = put(z, 0, succ(get(z, 0))) で表現される.従って if x >= y then x := x - y; output
:= output + 1 endif は,h(z) = if(leq?(get(z, 2), get(z, 1)), g(f(z)), z) で表現される.また,
for x times do
40
if x >= y then x := x - y; output := output + 1 endif
endfor
は,k(z, 0) = z, k(z, n + 1) = h(k(z, n)) なる関数 k を用いて l(z) = k(z, get(z, 1)) で表現される.一般には,文 Piが関数 pi(z)で表現されているなら,文の並び P1; P2; . . . ; Pnは,関数 pn(. . . (p2(p1(z)) . . . )
で表現される.また,文の並び F とGがそれぞれ関数 f(z)と g(z)で表現されているなら,if P then F else G endif
は if(p(z), f(z), g(z)) で,for N times do F endfor は k(z, 0) = z, k(z, n + 1) = f(k(z, n)) なる関数k を用いて k(z, n(z)) で表現される.ここで p(z), n(z) は,P, N を実現する適当な原始帰納的関数で
ある.
このようにして,プログラムの本体
output := 0;
if y > 0 then
for x times do
if x >= y then x := x - y; output := output + 1 endif
endfor
endif
を表現する関数 m(z) が得られたとすると,div2 をシミュレートする関数は,〈’’, ’’, ’’〉 のゲーデル数 a を用いて,get(m(a), 0) と書ける.
問題 3.9 (原始帰納法 —次の素数—). n より大きい最初の素数を求める関数 next-prime(n) の原始帰納的定義を与えよ.
41