선택 심화: 치환 모나드와 α-동치
범주론 렌즈에서 짚었듯, §1.4의 치환 p /ₛ δ는
변수 타입을 바꾸지 않는 특수한 경우다. Depth/TermMonad.lean은 변수 잎을 다른 변수
타입의 식으로 바꿔 끼우는 일반형 bind를 둔다.
def IntExp.bind {V W : Type u} (e : IntExp V) (f : V → IntExp W) : IntExp W :=
match e with
| .num n => .num n
| .var v => f v
| .neg e => .neg (e.bind f)
| .bin op e₀ e₁ => .bin op (e₀.bind f) (e₁.bind f)좌단위·우단위 법칙 둘이 등식으로 성립한다. 좌단위는 정의를 펼치면 바로 나온다.
/-- 좌단위. `pure v >>= f = f v`. `IntExp.bind`의 `var` 절이 곧 이 등식이다. -/
theorem IntExp.bind_pure_left {V W : Type u} (v : V) (f : V → IntExp W) :
(IntExp.var v).bind f = f v := rfl
우단위(IntExp.bind_pure_right : e.bind IntExp.var = e)는 Reynolds가 명제 1.2(b)에서
"c_var가 항등 치환으로 작동한다"고 쓴 §1의 결과(subst_pure_right)를 변수 타입을
바꾸는 쪽으로 일반화한 것이다. 증명은 e에 대한 구조적 귀납법으로, bind_pure_left와
같은 모양의 케이스 분석이다.
연습 1.7(a)가 묻는 치환 두 번의 합성은 모나드의 결합법칙이다. δ로 옮기고 다시 δ'로
옮기는 것을 한 번에 하는 합친 치환이 Kleisli 합성(Subst.kleisli, >=>ₑ)이다.
def Subst.kleisli {V W X : Type u} (δ : V → IntExp W) (δ' : W → IntExp X) : V → IntExp X :=
fun v => (δ v).bind δ'
@[inherit_doc Subst.kleisli] scoped infixr:90 " >=>ₑ " => Subst.kleisli
결합자가 없는 정수 식에서는 이 결합법칙이 등식으로 성립한다. "심화 A2.1"(§1, 변수
타입을 고정한 판)을 먼저 풀어 본 사람은 var 케이스가 rfl인 이유가 여기서도
그대로임을 알아챈다 — 심화 A2.2는 그 결과를 쓰지 않고 독립적으로 다시 증명한다.
@[exercise "심화 A2.2" 2]
theorem IntExp.bind_assoc {V W X : Type u} (e : IntExp V) (δ : V → IntExp W) (δ' : W → IntExp X) :
(e.bind δ).bind δ' = e.bind (δ >=>ₑ δ')
∀v가 있는 단언에서는 사정이 다르다. 포획을 피하려고 고르는 새 결합 변수가 치환을
어떻게 나누느냐에 따라 달라질 수 있어서, 결합법칙이 구문의 등식으로는 깨진다.
심화 A2.3은 그 구체적인 반례를 만드는 연습이다.
@[exercise "심화 A2.3" 2]
theorem subst_assoc_assert_not_eq :
∃ (p : Assert String) (δ δ' : Subst String),
(p /ₛ δ) /ₛ δ' ≠ p /ₛ (fun w => (δ w) /ₑ δ')
구문으로는 달라도 뜻은 같다 — 결합 변수 이름의 차이만 있을 뿐이다. 그 이름 차이를
무시하는 관계가 α-동치(=α)다. cmp 절은 정수 식 쪽에 결합자가 없으므로 두 비교식이
그대로 같아야 한다고 요구하고, quant 절만 결합 변수 이름 차이를 허용한다.
inductive Assert.AlphaEq [DecidableEq V] [HasFresh V] : Assert V → Assert V → Prop
| tru : Assert.AlphaEq .tru .tru
| fls : Assert.AlphaEq .fls .fls
| cmp (c : Cmp) (e₀ e₁ : IntExp V) : Assert.AlphaEq (.cmp c e₀ e₁) (.cmp c e₀ e₁)
| not {p p' : Assert V} : Assert.AlphaEq p p' → Assert.AlphaEq (.not p) (.not p')
| bin (op : LogOp) {p p' q q' : Assert V} :
Assert.AlphaEq p p' → Assert.AlphaEq q q' → Assert.AlphaEq (.bin op p q) (.bin op p' q')
| quant (qt : Quant) (v v' : V) (p p' : Assert V) (w : V)
(hw : w ∉ p.fv.erase v ∪ p'.fv.erase v') :
Assert.AlphaEq (p /[v := .var w]) (p' /[v' := .var w]) →
Assert.AlphaEq (.quant qt v p) (.quant qt v' p')
심화 A2.4는 α-동치가 뜻을 보존한다는 것 — 명제 1.5(이름 바꾸기 정리)의 구문적 일반화다.
명제 1.5는 결합 변수 하나를 그 자리에서 새 이름으로 바꾼 결과의 뜻이 같다고 말하고,
=α는 그 바꿔치기를 구문의 어느 깊이에서든 몇 번이든 허용한다. 명제 1.5 자체
(renaming_assert)는 그 증명이 치환 정리(Prop 1.3-assert, 채점)를 직접 쓰므로,
연습 독립성을 지키려고 가설 hrename으로 받는다 — 실제로 쓸 때는 renaming_assert를
그 자리에 넘긴다.
@[exercise "심화 A2.4" 3]
theorem Assert.AlphaEq.sound [HasFresh V]
(hrename : ∀ (qt : Quant) (v₀ vnew : V) (body : Assert V), vnew ∉ body.fv.erase v₀ →
∀ σ : State V,
⟦Assert.quant qt vnew (body /[v₀ := IntExp.var vnew] )⟧ₐ σ ↔ ⟦Assert.quant qt v₀ body⟧ₐ σ)
{p p' : Assert V} (h : p =α p') :
Equivalent p p'
2장의 리프팅 모나드 Σ⊥ = Flat (State V)(Ch02/Semantics.lean의 SigmaBot)도 같은
모양의 구조를 갖는다. pure에 해당하는 것이 Flat.some, Kleisli 확장에 해당하는 것이
liftBot이고, liftBot_eq_bind가 그 liftBot이 정확히 Flat.bind와 같음을 보여 준다.
IntExp의 bind/Subst.kleisli와 Σ⊥의 Flat.bind/liftBot은 "항 모나드"와
"리프팅 모나드"라는 서로 다른 계산 효과가 공유하는 같은 뼈대다.