正本:FormalLab/Foundation/Functions.lean
第2章:関数・適用・合成#
前章では閉じた式を定義して計算しました。しかし数学の多くは、具体的な値一つではなく、 任意の入力から出力を作る同じ手続きを扱います。二倍、後者関数、恒等写像は、入力を与える まで値が決まらないという点で共通しています。その共通の形が関数です。
本章では関数をグラフや対応表として仮定せず、入力を束縛するラムダ抽象、入力を渡す適用、
出力を次の入力へ渡す合成から組み立てます。型 A → B を読んだだけで必要な入力と得られる
出力を追跡できることが、後の論理、ラムダ計算、関手を学ぶ基礎になります。
入力から出力への依存を対象にする#
集合論的な記法では関数を と書き、各 にただ一つの を 対応させます。型理論では対応のグラフを先に構成するのではなく、入力 から出力 を作る項として関数を扱います。
- 作る:
λx : A. tは入力xを束縛するラムダ抽象。 - 使う:
f xは関数fの入力xへの適用。 - つなぐ:
f : A → Bとg : B → Cからg ∘ f : A → Cを作る。
ラムダ計算の関数は一引数です。A → B → C は右結合して A → (B → C) と読み、最初の
入力を与えると、次の入力を待つ関数が返ります。
関数型・ラムダ抽象・適用の三項を読む#
関数を「入力と出力の対応表」と考えることは有限集合では便利ですが、Leanで関数を 使うための基本像としては狭すぎます。関数も一つの項であり、次の二規則で特徴づけます。
左は関数の導入、右は関数の除去です。導入では仮の入力に対する出力を作り、 除去では実際の入力を与えます。 は の自由な を で置き換えて 計算します。この計算を「非型付きラムダ計算」でβ簡約として対象言語の側から分析します。
置換 は、 に既にある束縛変数を誤って捕獲しないように行います。型付け規則は項を作れる条件、 β規則は作った項が一段でどう計算するかを述べており、役割が異なります。
束縛とスコープ#
fun x => body の x は、新しい値を計算する命令ではなく、body の中だけで使える
局所名を導入します。これを束縛と呼び、その有効範囲がスコープです。
fun x => x + 1
└───────┘ この範囲の x は入力を指す
外側に同じ名前があっても、内側の束縛が優先されます。名前自体ではなく、どの束縛子を 参照するかが意味を決めます。
恒等関数・定数関数・合成を定義する#
受け取った項をそのまま返す恒等関数です。
def identity {α : Type} (x : α) : α :=
x最初の値を保存し、次の入力を無視して返す定数関数です。
def constant {α : Type} {β : Type} (x : α) : β → α :=
fun _ => xf の出力を g の入力へ渡す関数合成です。
def compose {α : Type} {β : Type} {γ : Type}
(g : β → γ) (f : α → β) : α → γ :=
fun x => g (f x)合成を入力へ適用すると、中間結果を順に渡す計算へ戻ります。
theorem compose_apply {α β γ : Type} (g : β → γ) (f : α → β) (x : α) :
compose g f x = g (f x) :=
rfl三関数の合成は、各入力でどちらに括っても同じ計算になります。
theorem compose_assoc_pointwise {α β γ δ : Type}
(h : γ → δ) (g : β → γ) (f : α → β) (x : α) :
compose h (compose g f) x = compose (compose h g) f x :=
rfl関数 f を同じ入力へ二回適用します。
def applyTwice {α : Type} (f : α → α) (x : α) : α :=
f (f x)型から合成を導出する#
compose の本体を暗記する必要はありません。求める型を一段ずつ使えば復元できます。
- 結果は
α → γなので、まずfun x => ...としてx : αを仮定する。 f : α → βへxを渡すとf x : βを得る。g : β → γへそれを渡すとg (f x) : γを得る。- 仮定した
xを閉じればfun x => g (f x) : α → γになる。
型は完成品を検査するだけでなく、次に何を作るべきかを案内しています。
複数引数は一引数関数の反復である#
constant x は値ではなく、次の入力を待つ関数です。関数を一引数へ適用した結果が再び
関数になるため、複数引数のように使えます。この表現をカリー化(currying)、一部の
引数だけを与えることを部分適用と呼びます。
二つの自然数を受け取る関数。型は Nat → (Nat → Nat) と読む。
def add : Nat → Nat → Nat :=
fun left => fun right => left + rightadd の第一引数だけを与えて得た、一引数関数。
def addThree : Nat → Nat :=
add 3
#check add
#check add 3
#eval addThree 4波括弧の {α : Type} は暗黙引数です。identity 3 ではLeanが 3 : Nat から
α := Nat を推論します。@identity と書くと暗黙引数も明示された型を観察できます。
暗黙引数は存在しない引数ではありません。通常はelaboratorが文脈から補う引数です。
推論できないときは明示する必要があり、@ は全引数を観察・指定するために省略を解除します。
#check @identity
#check compose
#eval applyTwice (fun n : Nat => n + 1) 3結合の向きを読む#
矢印は右結合し、適用は左結合します。
この二規約が組み合わさるため、f : α → β → γ と x : α から f x : β → γ、さらに
y : β から f x y : γ と機械的に追えます。括弧を推測で補わず、型から復元します。
関数の記法が隠しているもの#
この章では Type に属する型だけを使います。異なる宇宙階層をまたいで同じ定義を
再利用する宇宙多相は「宇宙階層と宇宙多相」で一般化します。合成の結合則のような関数同士の
等しさには、関数外延性が必要です。この原理は「等式、代入、外延性、一意存在」で導入します。
関数の本体が同じ計算をすることと、関数項が定義的に同じことも区別が必要です。 点ごとに同じ出力を返す二関数を等しいとする関数外延性は、単なる評価規則ではありません。
compose_assoc_pointwise は、任意の入力 で二つの合成結果が等しいことを rfl で検査します。これを
関数そのものの等式へ引き上げるには関数外延性が要ります。計算規則だけで証明できた点ごとの等式と、外延性原理を
使う関数等式を分けることで、後の圏論で必要な合成則の証明責任が見えます。
要点#
関数型は入力型と出力型を指定し、ラムダ抽象が関数を作り、適用が関数を使います。β規則は適用された ラムダ抽象を捕獲回避置換へ簡約します。 多引数関数は一引数関数の反復で、合成は中間型を一致させて導出できます。暗黙引数は elaboratorが補う実在の引数です。
研究史と文献案内#
関数概念はラムダ計算以前から解析学・集合論で発展し、19世紀には関数とその解析的表示を 区別する方向へ一般化されました。Churchの1940年の単純型理論 [CHU40] は型付きラムダ抽象を 論理体系へ組み込みます。現代の型付き関数の構文論は [TAPL02]、判断中心の提示は [PFPL16]、 proof theoryとの関係は [GLT89] を参照してください。
問題#
合成の向きを型だけから復元する#
f : A → B と g : B → C だけを与え、compose g f の本体を見ずにラムダ項を導いてください。
始域、中間型、終域を図に書き、入力 x : A がどの順で移るかを各適用の型判断とともに示します。
次に compose f g を同じ方法で診断し、どの二型が一致すれば例外的に型検査できるかも述べます。
数学的記法 A \xrightarrow{f} B \xrightarrow{g} C、ラムダ式 λx. g (f x)、Leanの compose g f を三行に並べ、各部分の対応を示してください。
解答は記法の暗記ではなく、中間型 B が合成の順序を一意に強制することを説明できれば完了です。
カリー化と部分適用を実装と判断で区別する#
multiply : Nat → Nat → Nat を定義し、multiply 2 の型を予想してから
doubleByMultiplication : Nat → Nat として名前を与えます。二引数関数を一引数関数の反復で表すことと、
その関数に実際に一引数を与えることを、定義時と使用時の異なる操作として説明してください。
さらに暗黙引数を一つ持つ関数を作ります。カリー化、部分適用、暗黙引数の三者を比較表にします。 表には、構文上の引数、省略される引数、新しい関数値が生じる時点を記入してください。
合成の単位則を等式による証明とLean証明で対照する#
任意の f : A → B について、id ∘ f = f と f ∘ id = f をまず等式列で証明します。関数の等しさを
点ごとの等しさへ帰着させ、任意の入力で両辺を計算してください。次に同じ証明をLeanで書き、
funext が担う段階と rfl で閉じる計算段階を分けて注釈します。
検証として、関数外延性を使わずに各点の等式だけを証明した場合、なぜ元の関数等式がまだ得られていないかを述べてください。