2장의 언어에는 정수 식, 불 식, 명령이 있다. 정수 식은 1장의 IntExp를 그대로 쓴다.
불 식은 1장의 단언에서 양화사를 뺀 조각이고, 명령은 상태를 바꾸는 구문이다.
불 식(boolean expression)의 모양은 다음과 같다.
/--
불 식(boolean expression). Reynolds §2.1 의 ⟨boolexp⟩.
1장의 `Assert` 에서 **양화사 절만 뺀 것**이다. 나머지 다섯 절은 글자까지 같다.
비교와 이항 논리 연산은 1장의 `Cmp`, `LogOp` 를 그대로 쓴다. 같은 기호를 두 번 정의하면
`Semantics.lean` 에서 의미도 두 번 정의해야 하고, 그러면 "불 식은 양화사 없는 단언" 이라는
Reynolds 의 말이 코드에서 사라진다.
-/inductiveBoolExp(V:Typeu)where/-- 참 `true`. -/|tru/-- 거짓 `false`. -/|fls/-- 정수 비교 `e₀ ∼ e₁`. -/|cmp:Cmp→IntExpV→IntExpV→BoolExpV/-- 부정 `¬b`. -/|not:BoolExpV→BoolExpV/-- 이항 논리 연산 `b₀ ∘ b₁`. -/|bin:LogOp→BoolExpV→BoolExpV→BoolExpVderivingDecidableEq,Repr
여기서 양화사를 뺀 이유는 구문을 작게 만들기 위해서가 아니다. 정수 전체에 대한 양화가
있는 1장의 단언에는 모든 식의 참과 거짓을 계산하는 공통 절차를 줄 수 없다. 양화사가 없는
BoolExp는 유한한 구문 트리를 따라 각 절을 계산할 수 있다. 이 차이가 §2.2에서 결과 타입
Prop과 Bool의 차이로 나타난다.
명령(command)은 여섯 생성자로 정의한다.
/--
명령(command). Reynolds §2.1 의 ⟨comm⟩ 과 §2.5 의 `newvar`.
`newvar v e c` 가 `newvar v := e in c` 다. **`v` 는 결합 발생(binding occurrence)이고
그 유효 범위는 `c` 뿐이다 — `e` 는 범위 밖이다.** 초기값 `e` 는 새 변수를 만들기 전에
바깥 상태에서 재기 때문이다. 1장의 합 식(연습 1.5)에서 상계가 범위 밖이었던 것과 같다.
이 장에서 결합이 등장하는 자리는 여기 하나뿐이다. §2.5 까지는 쓰지 않는다.
-/inductiveComm(V:Typeu)where/-- 대입 `v := e`. -/|assign:V→IntExpV→CommV/-- 아무것도 하지 않는다. -/|skip/-- 순차 합성 `c₀ ; c₁`. -/|seq:CommV→CommV→CommV/-- 조건 `if b then c₀ else c₁`. -/|ite:BoolExpV→CommV→CommV→CommV/-- 반복 `while b do c`. 의미는 구문에 대한 구조적 재귀만으로 정의되지 않는다. -/|wh:BoolExpV→CommV→CommV/-- 변수 선언 `newvar v := e in c`. `v` 의 유효 범위는 `c` 다. -/|newvar:V→IntExpV→CommV→CommVderivingDecidableEq,Repr
newvar는 책에서 §2.5에 추가된다. Lean 타입을 중간에 다시 정의하면 §2.2~§2.4의 의미와
정리도 새 타입에 맞춰 복제해야 하므로, 이 저장소는 생성자를 처음부터 넣고 §2.5 전에는
사용하지 않는다. 현재 문서 범위에서 newvar는 이후 절을 위한 자리다.
while이 들어가도 Comm 값 자체는 유한한 트리다. 예를 들어
while tt do skip은 생성자 두 개를 자식으로 가진 작은 트리다. 끝나지 않는 것은 그 트리를
실행하는 과정이지 트리의 크기가 아니다.
/--
`⟦b⟧ᵇ σ` — 불 식의 뜻. Reynolds §2.2 의 `⟦-⟧boolexp ∈ ⟨boolexp⟩ → Σ → 𝔹`.
1장의 `Assert.eval` 과 절이 하나씩 대응하고, 결과 타입만 `Prop` 에서 `Bool` 로 바뀌었다.
그 차이가 `#eval` 로 돌릴 수 있느냐를 가른다.
-/defBoolExp.eval{V:Typeu}:BoolExpV→StateV→Bool|.tru,_=>true|.fls,_=>false|.cmpce₀e₁,σ=>c.denoteBool(⟦e₀⟧ₑσ)(⟦e₁⟧ₑσ)|.notb,σ=>!b.evalσ|.binopb₀b₁,σ=>op.denoteBool(b₀.evalσ)(b₁.evalσ)
State.const 3에서 x < 10을 재면 true이고, State.const 42에서는 false다.
boolExp_eval_iff 정리는 같은 불 식을 1장의 단언으로 읽었을 때에도 참·거짓이 일치함을
증명한다. “양화사 없는 단언”이라는 설명을 구문 모양의 유사성에 그치지 않고 의미 수준에서
확인한 것이다.
상태 σ에서 명령을 실행한 결과는 새 상태일 수도 있고, 결과 상태가 없을 수도 있다.
이 두 경우를 전용 귀납 타입 Flat으로 표현한다. 평평한 순서는 Flat에만 등록하므로
일반 Option의 core 순서는 영향을 받지 않는다.
/--
`Σ⊥` — 상태 하나 또는 비종료. `none` 이 `⊥` 다.
이 타입은 결정적 언어의 정상 종료와 비종료만 구분한다. `none`을 실행 오류나 비결정적
결과로도 함께 읽지 않는다. Reynolds가 §2.7에서 산술 오류를 추가하면 의미 공간을 다시
매개변수화해야 하고, 비결정성은 7장에서 멱집합이나 멱영역과 같은 다른 구조를 요구한다.
`Flat`은 종료 결과와 바닥을 갖는 전용 귀납 타입이다. Reynolds 가 §2.2 에서 손으로 도입하는 확장
```
f⊥⊥ x = if x = ⊥ then ⊥ else f x
```
이 정확히 `Flat.bind` 다. 아래 `liftBot_eq_bind` 가 그것을 확인한다.
-/abbrevSigmaBot(V:Typeu):=Flat(StateV)
none의 뜻은 이 장의 범위에서 좁게 잡혀 있다.
some σ'
실행이 끝났고 결과 상태가 σ'이라는 정보다.
none
유한한 결과 상태를 얻지 못했다는 정보다. 연료 해석기에서는 실제 비종료와 연료 부족이
모두 이 값으로 보인다.
이 구분은 §2.7에서 오류를 넣거나 7장에서 비결정성을 넣을 때 다시 확장해야 한다.
Flat 하나가 모든 효과를 표현한다고 일반화하면 안 된다.
/--
명령의 의미 함수가 만족해야 할 조건. Reynolds §2.2 의 의미 방정식 여섯 개를 그대로 적었다.
**정의가 아니라 명세(specification)다.** 이런 `m` 이 있는지, 있다면 하나뿐인지는
이 술어가 답하지 않는다. `while`을 제외한 다섯 방정식은 `m` 을 구문에 대한 재귀로
결정하지만, `wh` 방정식은 양변에 같은 구가 나와서 그렇게 하지 못한다.
§2.4 에서 이 조건을 만족하는 `Comm.eval` 을 만들고 `Comm.eval_isSemantics` 를 증명한다.
-/defIsSemantics{V:Typeu}[DecidableEqV](m:CommV→StateV→SigmaBotV):Prop:=(∀veσ,m(.assignve)σ=Flat.some(σ[v:=⟦e⟧ₑσ]))∧(∀σ,m.skipσ=Flat.someσ)∧(∀c₀c₁σ,m(.seqc₀c₁)σ=Flat.bind(mc₀σ)(mc₁))∧(∀bc₀c₁σ,m(.itebc₀c₁)σ=if⟦b⟧ᵇσthenmc₀σelsemc₁σ)∧(∀bcσ,m(.whbc)σ=if⟦b⟧ᵇσthenFlat.bind(mcσ)(m(.whbc))elseFlat.someσ)∧(∀vecσ,m(.newvarvec)σ=restorevσ(mc(σ[v:=⟦e⟧ₑσ])))
대입, skip, 순차 합성, 조건문, newvar의 우변은 더 작은 명령의 뜻만 참조한다.
while 절만 같은 while 명령을 다시 참조한다. 그래서 IsSemantics는 의미 함수를
만드는 재귀 정의가 아니라, 후보 함수가 지켜야 할 명세다.
비유일성은 while x ≠ 0 do x := x - 2에서 드러난다. x가 0 이상의 짝수면 언젠가
0에 도달한다. 양의 홀수는 1에서 -1로 지나가고, 음수는 계속 작아지므로 0에 도달하지 않는다.
decrTrue는 끝나는 입력에서 x = 0인 상태를 내고 나머지에서는 none을 낸다.
decrFake는 끝나는 입력에서 같은 답을 내지만, 끝나지 않는 입력에서 x = 999인 상태를
낸다. 999라는 수에는 뜻이 없다. 방정식이 그 입력의 결과를 제한하지 않는다는 것을 보이기
위해 고른 임의의 값이다.
두 함수가 같은 풀기 방정식을 만족하고도 서로 다르다는 사실을 Lean에서 증명한다.
/-- 한 걸음 간 상태. 네 갈래 계산에서 계속 쓴다. -/theoremdecr_step(f:StateString→SigmaBotString)(σ:StateString):Flat.bind(decrBodyσ)f=f(σ["x":=σ"x"-2]):=rfl/-- 끝나는 상태에서 한 걸음 가도 여전히 끝나는 상태다. -/theoremdecrHalts_step{σ:StateString}(hh:decrHaltsσ)(h0:σ"x"≠0):decrHalts(σ["x":=σ"x"-2]):=σ:StateStringhh:decrHaltsσh0:σ"x"≠0⊢ decrHaltsσ["x":=σ"x"-2]σ:StateStringhh:0≤σ"x"∧σ"x"%2=0h0:σ"x"≠0⊢ 0≤σ["x":=σ"x"-2]"x"∧σ["x":=σ"x"-2]"x"%2=0σ:StateStringhh:0≤σ"x"∧σ"x"%2=0h0:σ"x"≠0⊢ 0≤σ"x"-2∧(σ"x"-2)%2=0All goals completed! 🐙/-- 끝나지 않는 상태에서 한 걸음 가도 여전히 끝나지 않는다. 홀수는 홀수로, 음수는 음수로. -/theoremdecrHalts_step_not{σ:StateString}(hh:¬decrHaltsσ):¬decrHalts(σ["x":=σ"x"-2]):=σ:StateStringhh:¬decrHaltsσ⊢ ¬decrHaltsσ["x":=σ"x"-2]σ:StateStringhh:¬(0≤σ"x"∧σ"x"%2=0)⊢ ¬(0≤σ["x":=σ"x"-2]"x"∧σ["x":=σ"x"-2]"x"%2=0)σ:StateStringhh:¬(0≤σ"x"∧σ"x"%2=0)⊢ ¬(0≤σ"x"-2∧(σ"x"-2)%2=0)All goals completed! 🐙/-- 의도한 의미 후보 `decrTrue`는 풀기 방정식을 만족한다. -/theoremunwindsDecr_true:UnwindsDecrdecrTrue:=⊢ UnwindsDecrdecrTrueσ:StateString⊢ decrTrueσ=ifσ"x"≠0thenFlat.bind(decrBodyσ)decrTrueelseFlat.someσσ:StateStringh0:σ"x"=0⊢ decrTrueσ=ifσ"x"≠0thenFlat.bind(decrBodyσ)decrTrueelseFlat.someσσ:StateStringh0:¬σ"x"=0⊢ decrTrueσ=ifσ"x"≠0thenFlat.bind(decrBodyσ)decrTrueelseFlat.someσσ:StateStringh0:σ"x"=0⊢ decrTrueσ=ifσ"x"≠0thenFlat.bind(decrBodyσ)decrTrueelseFlat.someσ-- 조건이 거짓이라 한 걸음도 가지 않는다.posσ:StateStringh0:σ"x"=0⊢ decrTrueσ=Flat.someσhavehh:decrHaltsσ:=by⊢ UnwindsDecrdecrTrueunfolddecrHaltsσ:StateStringh0:σ"x"=0⊢ 0≤σ"x"∧σ"x"%2=0;omegaposσ:StateStringh0:σ"x"=0hh:decrHaltsσ⊢ decrTrueσ=Flat.someσsimponly[decrTrue,if_poshh,State.subst_eq_selfσ"x"h0]All goals completed! 🐙·negσ:StateStringh0:¬σ"x"=0⊢ decrTrueσ=ifσ"x"≠0thenFlat.bind(decrBodyσ)decrTrueelseFlat.someσrw[if_posh0,negσ:StateStringh0:¬σ"x"=0⊢ decrTrueσ=Flat.bind(decrBodyσ)decrTruedecr_stepnegσ:StateStringh0:¬σ"x"=0⊢ decrTrueσ=decrTrueσ["x":=σ"x"-2]]negσ:StateStringh0:¬σ"x"=0⊢ decrTrueσ=decrTrueσ["x":=σ"x"-2]by_caseshh:decrHaltsσposσ:StateStringh0:¬σ"x"=0hh:decrHaltsσ⊢ decrTrueσ=decrTrueσ["x":=σ"x"-2]negσ:StateStringh0:¬σ"x"=0hh:¬decrHaltsσ⊢ decrTrueσ=decrTrueσ["x":=σ"x"-2]·posσ:StateStringh0:¬σ"x"=0hh:decrHaltsσ⊢ decrTrueσ=decrTrueσ["x":=σ"x"-2]simponly[decrTrue,if_poshh,if_pos(decrHalts_stephhh0),State.subst_subst]All goals completed! 🐙·negσ:StateStringh0:¬σ"x"=0hh:¬decrHaltsσ⊢ decrTrueσ=decrTrueσ["x":=σ"x"-2]simponly[decrTrue,if_neghh,if_neg(decrHalts_step_nothh)]All goals completed! 🐙/--
대안으로 만든 `decrFake`도 풀기 방정식을 만족한다.
`decrTrue` 와 `decrFake` 는 끝나지 않는 상태에서 다른 답을 내지만 둘 다 같은 방정식을
만족한다. 종료하지 않는 입력에서는 우변이 다시 미지 함수에 돌아가므로, 방정식만으로는
그 입력의 결과를 제한하지 못한다.
-/theoremunwindsDecr_fake:UnwindsDecrdecrFake:=by⊢ UnwindsDecrdecrFakeintroσσ:StateString⊢ decrFakeσ=ifσ"x"≠0thenFlat.bind(decrBodyσ)decrFakeelseFlat.someσby_casesh0:σ"x"=0posσ:StateStringh0:σ"x"=0⊢ decrFakeσ=ifσ"x"≠0thenFlat.bind(decrBodyσ)decrFakeelseFlat.someσnegσ:StateStringh0:¬σ"x"=0⊢ decrFakeσ=ifσ"x"≠0thenFlat.bind(decrBodyσ)decrFakeelseFlat.someσ·posσ:StateStringh0:σ"x"=0⊢ decrFakeσ=ifσ"x"≠0thenFlat.bind(decrBodyσ)decrFakeelseFlat.someσrw[if_neg(byσ:StateStringh0:σ"x"=0⊢ ¬σ"x"≠0simp[h0]All goals completed! 🐙)]posσ:StateStringh0:σ"x"=0⊢ decrFakeσ=Flat.someσhavehh:decrHaltsσ:=by⊢ UnwindsDecrdecrFakeunfolddecrHaltsσ:StateStringh0:σ"x"=0⊢ 0≤σ"x"∧σ"x"%2=0;omegaposσ:StateStringh0:σ"x"=0hh:decrHaltsσ⊢ decrFakeσ=Flat.someσsimponly[decrFake,if_poshh,State.subst_eq_selfσ"x"h0]All goals completed! 🐙·negσ:StateStringh0:¬σ"x"=0⊢ decrFakeσ=ifσ"x"≠0thenFlat.bind(decrBodyσ)decrFakeelseFlat.someσrw[if_posh0,negσ:StateStringh0:¬σ"x"=0⊢ decrFakeσ=Flat.bind(decrBodyσ)decrFakedecr_stepnegσ:StateStringh0:¬σ"x"=0⊢ decrFakeσ=decrFakeσ["x":=σ"x"-2]]negσ:StateStringh0:¬σ"x"=0⊢ decrFakeσ=decrFakeσ["x":=σ"x"-2]by_caseshh:decrHaltsσposσ:StateStringh0:¬σ"x"=0hh:decrHaltsσ⊢ decrFakeσ=decrFakeσ["x":=σ"x"-2]negσ:StateStringh0:¬σ"x"=0hh:¬decrHaltsσ⊢ decrFakeσ=decrFakeσ["x":=σ"x"-2]·posσ:StateStringh0:¬σ"x"=0hh:decrHaltsσ⊢ decrFakeσ=decrFakeσ["x":=σ"x"-2]simponly[decrFake,if_poshh,if_pos(decrHalts_stephhh0),State.subst_subst]All goals completed! 🐙·negσ:StateStringh0:¬σ"x"=0hh:¬decrHaltsσ⊢ decrFakeσ=decrFakeσ["x":=σ"x"-2]simponly[decrFake,if_neghh,if_neg(decrHalts_step_nothh),State.subst_subst]All goals completed! 🐙/--
풀기 방정식은 해를 유일하게 결정하지 않는다.
`decrTrue`와 `decrFake`가 모두 해라는 두 보조정리와 둘이 다르다는 증거를 묶은 정리다.
§2.3~2.4에서는 이 해들을 근사 순서로 비교하고 최소인 해를 선택한다.
증명은 두 해를 제시하고 다른 값을 내는 상태를 하나 짚으면 된다.
`x` 가 1 인 상태에서 하나는 `⊥` 를, 다른 하나는 상태를 낸다.
-/@[exercise"§2.2 unwinding-not-unique"3]theoremunwinding_not_unique:∃fg:StateString→SigmaBotString,UnwindsDecrf∧UnwindsDecrg∧f≠g
풀기 방정식은 반복문의 한 단계 행동을 올바르게 적지만, 끝나지 않는 입력에서 무엇을
내야 하는지는 고르지 못한다. §2.3은 후보 함수들을 “얼마나 많은 종료 정보를 주는가”로
비교할 순서를 만든다.