数学は可能な限り一般的であるべきか
ある論文を読んでいて,「集合 X の部分集合族 K が与えられたとする。このとき〜」と書いてあって,その後には K を用いた概念が定義されているのだけど,その部分を最後まで読んでも何を意図しているかよくわからなかった。もっと先を読んでみると,どうやら定義された概念が論文中で使われるのは X が位相空間で K は X の開基になっている場合だけのようだった。そして,K は開基なのだと思ってもう一度定義を読めば,「何だ,そういうことか」という内容だった。
ということがあって,一般的にできることは常に一般的な形で書くことがよいことなのだろうか,ということを考えた。不要な条件はなんとなく書きたくない,という気持ちになることはよくある。だから,そういう書き方をしたくなるのもわかる。でも,可読性を考えれば不必要な一般性は排除したほうがいいように思う。上の場合だったら,最初から X は位相空間で K はその開基とする,と書いてしまったほうがいい。論理的には必要でなくても,適当な条件が文脈として提供されていれば理解の助けになるというケースはよくある。それだったら,はじめからその条件のもとで議論したほうが,読み手にとってはわかりやすい。
Implicit argument が絡んで型検査を通らない例
型検査(推論?)がうまくいってもよさそうだけどうまくいかない話です.挙動は Coq 8.5pl2 で確認しました.他のバージョンでどうかは調べていません.
Set Implicit Arguments してから,なんか依存型を定義します.
Coq < Set Implicit Arguments. Coq < Inductive Fin : nat -> Set := | F0 n : Fin (S n) | FS n : Fin n -> Fin (S n). Coq < Coq < Fin is defined Fin_rect is defined Fin_ind is defined Fin_rec is defined Coq < Fixpoint Fin' (n : nat) : Set := match n with 0 => Empty_set | S n' => sum unit (Fin' n') end. Coq < Fin' is defined Fin' is recursively defined (decreasing on 1st argument)
自然数で添字付けられた集合族です.返ってくる物の中身はわりと何でもよくて,それぞれ Inductive と Fixpoint で定義されているところが重要な気がします.
それで,これらを引数に取る関数を何か作ります.ここでは面倒なので変数を宣言してしまいます.戻り値は Prop にしておきましたが,別に何でもよさそうです.
Coq < Parameter P : forall n (i : Fin' n), Fin n -> Prop. P is assumed Coq < Print P. *** [ P : forall n : nat, Fin' n -> Fin n -> Prop ] Argument n is implicit Argument scopes are [nat_scope _ _]
P の第一引数の n は implict になります.第三引数の型から推論できそうなのでまあそうでしょうという気がします.ちょっと使ってみます.
Coq < Check (P (inl tt) (F0 0)). Toplevel input, characters 10-16: > Check (P (inl tt) (F0 0)). > ^^^^^^ Error: The term "inl tt" has type "(unit + ?B)%type" while it is expected to have type "Fin' ?n".
うまくいかないようです.(inl tt) の型は (unit + ?B) だけど (Fin ?n) が要求されていると言われています.実際には,第三引数の型が (Fin 1) でこいつは inductive type なので第一引数が 1 であることが(たぶん)わかり,これを第二引数の型の n に代入すれば (Fin' 1) となりますが,これは (unit + unit) に簡約できます.以上のように推論すると型が合っていることがわかる気がしますが,そんなことはしてくれないようです.
明示的に型を書くか引数を与えてやるとチェックは通ります.
Coq < Check (P (inl tt : Fin' 1) (F0 0)). P (inl tt : Fin' 1) (F0 0) : Prop Coq < Check (P (n := 1) (inl tt) (F0 0)). P (inl tt) (F0 0) : Prop
でもちょっと面倒だし,読みにくくなりがち.
試しに P の引数の順番を変えてみると,
Coq < Parameter P' : forall n (i : Fin n), Fin' n -> Prop. P' is assumed Coq < Print P'. *** [ P' : forall n : nat, Fin n -> Fin' n -> Prop ] Argument n is implicit Argument scopes are [nat_scope _ _] Coq < Check (P' (F0 0) (inl tt)). P' (F0 0) (inl tt) : Prop
こっちは問題ないようです.ということは型検査アルゴリズムが項を処理する順番の問題とかだろうか.もし引数を左からチェックするのだとすると,最初の例では n がわからない状態で (inl tt) の型検査をすることになるので,型が合わないように見える.今の例だと先に (F0 0) を検査するので,n = 1 とわかり,その後 (inl tt) を見るので型が合うと判断できる.本当にそういう実装になってるかどうか知りませんが.
ついでに両方とも inductive でない方の型にしてみると,
Coq < Parameter P'' : forall n (i : Fin' n), Fin' n -> Prop. P'' is assumed Coq < Print P''. *** [ P'' : forall n : nat, Fin' n -> Fin' n -> Prop ] Argument scopes are [nat_scope _ _]
これは n が implicit でなくなります.Fin' n のような実引数が与えられると簡約されるものは,n が何だったか後からわからなくなる可能性があるから,まあそれが自然かという気がします(オプションでいろいろ制御できる).
かたい電車
15分間隔で電車が走っている路線でA駅からB駅へ向かい,その後30分間隔で電車が走っている路線に乗り換えてC駅に行くことを考える.
A駅でいつもより一本(つまり15分)早い電車に乗るとどうなるか.おそらくB駅における乗り換えでいつもより15分長く待って,いつもと同じ時間の電車に乗り換え,C駅に着くのはいつもと同じ時間になるだろう.あたりまえなんだけどあたりまえじゃない,という感触があって,おもしろい.
定期的に電車に乗っていた時期が昔からほとんどない.いつも歩いて移動している.だからだろうか,5分早く出れば5分,15分早く出れば15分早く着くのが当然のように思っている.しかし電車はそうではない.そのことに気付いたとき,そうか,電車はかたいのだ,と思った.
正則関数はかたい,と言われることがある.局所的な情報だけで全体が決まってしまう.それに対して連続関数はやわらかい.ウリゾーンの補題がそのやわらかさを示している.
それになぞらえて言うと,電車はかたく,徒歩はやわらかいのだ.飛行機は電車よりももっとかたい.同じ電車でも,都会では田舎よりもやわらかいだろう.
ホーア論理の代入の規則
わりとわかりにくくて混乱しやすい気がするホーア論理の代入の規則ですが,見方を少し変えるとわかりやすくなります。
普通の規則。
同値変形してこうする。
要するに「代入後の x の値を x' とすれば A[x'/x] である」が事前条件です。Hoare triple の意味を考えればそれが事前条件になるのは当たり前ですね。
こっちのほうがわかりやすいし,この形で認識しておくと x' の条件を変えることでいろいろできます。例えば乱数。
上限を指定したり。
配列。
配列への同時代入(構文は適当)。
数学の本の話
数学の本はアウトラインがわかればあとは自分でよく考えるだけで本を閉じたまま詳細を構成することができる。しかし数学以外の本はそうとは限らない。例えば歴史の本は歴史の大まかな流れがわかっていても細部を自分の頭で考えても埋めることはできない,気がする。仮にそれらしく埋めたとしても,正しいかどうかを自分で確認することは難しい。
数学の本の詳細(普通は証明が大部分を占める)が自分で構成できるとは,本の中身を見ることなく本に書いてある通りのことが自分で書けるということではない。本に書いてあるのと同じ結論に至る,論理的に正しい詳細が書けるということだ。自分で考えて書いてみた結果が本に書いてある内容と全く同じになるとは限らない。長い証明などは同じになることのほうが珍しい。しかし,自分で書いたことの詳細が本に書いてあるものと一致するかどうかは,ほとんどの場合には問題ではない。この場合に問題なのは,結論が論理的に正しく導かれているかどうかだからだ(あんまり本文の方針から外れてしまうと,後で理解に不都合が生じる可能性はあるが)。
Hilbert algebra の性質の証明
ちょっと気になってやってみたのをどこかにメモしたくなったので書いた。関数解析じゃなくて論理の方の Hilbert algebra です。
Hilbert algebra の公理
が Hilbert algebra とは
- if then
性質
- で順序が入り,1 が最大元
などが成り立つ。証明は下に書くけど自分で考えると楽しいかも。
resolution の完全性についての疑問
一階述語論理の resolution の規則って, として を の MGU としたときに
と書けて(resolution に を使うのはあまり普通ではないかもしれないけど),これだけで反駁完全なのだと思っていました。しかしどうもそうではないようだという気がしてきて,どうやったら本当に完全になるんだろうかということを最近考えました。
まず完全でないと思う理由ですが,例えば (p は述語記号,x, y, u, v は変数)という節集合を考えます。これが充足不能なのは,論理式で書くと だから明らかでしょう。一方,この節集合からは上の規則を使っても しか(変数の名前の違いを除いて)新しい節は出てきません。だから空節を導くことはできません。
いくつか見てみた本の中で「論理と計算のしくみ」では違う規則を採用していて,この本では一つずつではなく複数のリテラルをまとめて取り出して unify してよいことになっています。具体的には を の MGU として
のようにしています。これだとさっきの例は をまとめて unify してやれば空節が出てくるので OK,ということになります。この形の規則だと確かに反駁完全性が成り立つようです。
でもそういう形の規則になっている本は他に見かけないので,これはどういうことだろうかと疑問に思い調べてみました。以下の記述は "Handbook of Automated Reasoning" の Chapter 2 の冒頭(を読んだ記憶)に基づいています。どうやら resolution の手法を最初に考えた Robinson は,確かに最初の形を resolution として定式化していたようですが,それに加えて別にもう一つ規則を用意していたらしいです。その規則は factoring といって, を D の unifier として
という形をしています(たぶん)。これがあると,
なので,最初の形の resolution と合わせて空節が導けます。厳密には確かめていませんが,こちらも反駁完全性が成り立ちそうです。
ということは,もともとは resolution と factoring を合わせて反駁完全であるという話だったのに,factoring の方は何らかの理由であまり教科書に書かれなくなってしまったのでしょうか(実際,handbook を読むまで factoring という名前を知らなかった)。https://en.wikipedia.org/wiki/Resolution_(logic) には "The resolution rule, as defined by Robinson, also incorporated factoring, .... The resulting inference rule is refutation-complete, ...." と書いてあるので,そんなに知られていない事実でもなくて,たまたま僕がそのあたりのことが書いてある教科書を読まなかっただけかもしれませんが。