tpl-in-lean

보충 주석 명세 · §3.4 전체 정확성🔗

책과의 관계: Reynolds §3.2는 증명을 단언과 명세의 나열로 설명하지만(§3.3의 번호 붙은 증명 예가 그 모양이다), 명령 사이사이에 단언을 끼워 넣는 아래 표기와 거기서 검증 조건을 뽑는 vcg는 책에 없다 — 이 절은 그 아이디어를 형식화한 보충이다. 이어지는 "전체 정확성" 절은 책 §3.4의 본문이다.

유도 나무는 금방 커진다. 그 관찰을 명령 사이사이에 단언을 끼워 넣은 주석 명세(annotated specification)로 줄여 적어 보자. 규칙이 스스로 정하지 못하는 것은 두 가지뿐이다. 순차 합성의 이음매와 반복의 불변식이다. 나머지는 규칙이 계산한다.

주석 명령🔗

사람이 적어야 하는 것만 붙인 구문을 둔다. 주석을 지우면 명령이 된다.

/-- 주석 명령. 이음매(`seq` 의 가운데)와 불변식(`wh` 의 첫 인자)만 사람이 적는다. -/ inductive Annot (V : Type u) where | assign : V → IntExp V → Annot V | skip /-- `a₀ ; {r} a₁` — 가운데가 이음매. -/ | seq : Annot V → Assert V → Annot V → Annot V | ite : BoolExp V → Annot V → Annot V → Annot V /-- `while b do {i} a` — `i` 가 불변식. -/ | wh : Assert V → BoolExp V → Annot V → Annot V | newvar : V → IntExp V → Annot V → Annot V /-- 주석을 지우면 명령이다. -/ def Annot.erase : Annot V → Comm V | .assign v e => .assign v e | .skip => .skip | .seq a₀ _ a₁ => .seq a₀.erase a₁.erase | .ite b a₀ a₁ => .ite b a₀.erase a₁.erase | .wh _ b a => .wh b a.erase | .newvar v e a => .newvar v e a.erase

검증 조건 생성기🔗

사후조건에서 뒤로 가며 사전조건을 계산하고, 도중에 확인해야 할 함의들을 모은다. 대입 공리가 거꾸로 가기 때문에 계산도 거꾸로 간다.

/-- 검증 조건 생성기. `(사전조건, 확인할 함의들)`. -/ def Annot.vcg [HasFresh V] : Annot V → Assert V → Assert V × List (Assert V × Assert V) | .assign v e, q => (q /[v := e], []) | .skip, q => (q, []) | .seq a₀ r a₁, q => ((a₀.vcg r).1, (r, (a₁.vcg q).1) :: ((a₀.vcg r).2 ++ (a₁.vcg q).2)) | .ite b a₀ a₁, q => (.bin .and (.bin .imp b.toAssert (a₀.vcg q).1) (.bin .imp (.not b.toAssert) (a₁.vcg q).1), (a₀.vcg q).2 ++ (a₁.vcg q).2) | .wh i b a, q => (i, (i ⋀ b.toAssert, (a.vcg i).1) :: (i ⋀ .not b.toAssert, q) :: (a.vcg i).2) | .newvar v e a, q => (.quant .all v (.bin .imp (.cmp .eq (.var v) e) (a.vcg q).1), (a.vcg q).2)
대입

사후조건에 치환을 건다. 확인할 것은 없다.

순차 합성

이음매 r이 뒤쪽 명령의 사전조건보다 강해야 한다. 이것이 검증 조건 하나다.

조건

(b ⇒ p₀) ∧ (¬b ⇒ p₁). 확인할 것은 없다.

반복

사전조건은 불변식이다. i ∧ b가 본체의 사전조건보다 강한지, i ∧ ¬b가 사후조건보다 강한지를 확인해야 한다.

변수 선언

∀ v. v = e ⇒ p. 결합자를 ∀로 가두면 바깥 단언에 새지 않는다.

검증 조건이 전부 타당하면 Hoare 유도가 있다(Annot.vcg_sound). Dafny나 Why3 같은 검증 도구가 하는 일의 축소판이다. 맞바꾸기에 주석을 달면 남는 일은 치환을 펼치는 것뿐이다.

/-- 주석 붙은 맞바꾸기. 이음매가 이 파일 첫머리의 주석 그대로다. -/ def swapAnnot : Annot String := .seq (.assign "t" ⟪ x ⟫ₑ) (⟪ t = a ∧ y = b ⟫ₐ) (.seq (.assign "x" ⟪ y ⟫ₑ) (⟪ t = a ∧ x = b ⟫ₐ) (.assign "y" ⟪ t ⟫ₑ)) /-- 주석을 지우면 §2.5 의 `swap` 이다. -/ theorem swapAnnot_erase : swapAnnot.erase = swap := rfl /-- 검증 조건 두 개와 맨 앞의 함의 하나. 셋 다 치환을 펼치면 항등이다. -/ theorem swap_hoare' : Hoare (⟪ x = a ∧ y = b ⟫ₐ) swap (⟪ y = a ∧ x = b ⟫ₐ) := ⊢ Hoare (Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "y") (IntExp.var "b"))) swap (Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "y") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.var "b"))) ⊢ Hoare (Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "y") (IntExp.var "b"))) swapAnnot.erase (Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "y") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.var "b"))) ⊢ Stronger (Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "y") (IntExp.var "b"))) (swapAnnot.vcg (Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "y") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.var "b")))).1⊢ ∀ vc ∈ (swapAnnot.vcg (Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "y") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.var "b")))).2, Stronger vc.1 vc.2 ⊢ Stronger (Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "y") (IntExp.var "b"))) (swapAnnot.vcg (Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "y") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.var "b")))).1 σ:State Stringh:⟦Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "y") (IntExp.var "b"))⟧ₐ σ⊢ ⟦(swapAnnot.vcg (Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "y") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.var "b")))).1⟧ₐ σ All goals completed! 🐙 ⊢ ∀ vc ∈ (swapAnnot.vcg (Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "y") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.var "b")))).2, Stronger vc.1 vc.2 vc:Assert String × Assert Stringhvc:vc ∈ (swapAnnot.vcg (Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "y") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.var "b")))).2σ:State Stringh:⟦vc.1⟧ₐ σ⊢ ⟦vc.2⟧ₐ σ vc:Assert String × Assert Stringσ:State Stringh:⟦vc.1⟧ₐ σhvc:vc = (Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "t") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "y") (IntExp.var "b")), Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "t") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.var "b")) /["x":=IntExp.var "y"] ) ∨ vc = (Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "t") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.var "b")), Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "y") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.var "b")) /["y":=IntExp.var "t"] )⊢ ⟦vc.2⟧ₐ σ σ:State Stringh:⟦(Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "t") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "y") (IntExp.var "b")), Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "t") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.var "b")) /["x":=IntExp.var "y"] ).1⟧ₐ σ⊢ ⟦(Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "t") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "y") (IntExp.var "b")), Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "t") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.var "b")) /["x":=IntExp.var "y"] ).2⟧ₐ σσ:State Stringh:⟦(Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "t") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.var "b")), Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "y") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.var "b")) /["y":=IntExp.var "t"] ).1⟧ₐ σ⊢ ⟦(Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "t") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.var "b")), Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "y") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.var "b")) /["y":=IntExp.var "t"] ).2⟧ₐ σ σ:State Stringh:⟦(Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "t") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "y") (IntExp.var "b")), Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "t") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.var "b")) /["x":=IntExp.var "y"] ).1⟧ₐ σ⊢ ⟦(Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "t") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "y") (IntExp.var "b")), Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "t") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.var "b")) /["x":=IntExp.var "y"] ).2⟧ₐ σσ:State Stringh:⟦(Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "t") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.var "b")), Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "y") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.var "b")) /["y":=IntExp.var "t"] ).1⟧ₐ σ⊢ ⟦(Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "t") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.var "b")), Assert.bin LogOp.and (Assert.cmp Cmp.eq (IntExp.var "y") (IntExp.var "a")) (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.var "b")) /["y":=IntExp.var "t"] ).2⟧ₐ σ All goals completed! 🐙

전체 정확성 — 변항🔗

전체 정확성 체계는 반복에 WHT를, 상수 규칙에 종료 전제가 있는 CST를 쓴다. 반복이 끝난다는 것을 말하려면 한 바퀴마다 줄어드는 양, 곧 변항(variant)이 있어야 한다.

/-- 전체 정확성의 추론 체계. 반복에는 WHT를, 상수 규칙에는 종료 전제가 있는 CST를 쓴다. -/ inductive HoareT [HasFresh V] : Assert V → Comm V → Assert V → Prop where | skip (p : Assert V) : HoareT p .skip p | assign (q : Assert V) (v : V) (e : IntExp V) : HoareT (q /[v := e]) (.assign v e) q | seq {p r q : Assert V} {c₀ c₁ : Comm V} : HoareT p c₀ r → HoareT r c₁ q → HoareT p (.seq c₀ c₁) q | ite {p q : Assert V} {b : BoolExp V} {c₀ c₁ : Comm V} : HoareT (p ⋀ b.toAssert) c₀ q → HoareT (p ⋀ .not b.toAssert) c₁ q → HoareT p (.ite b c₀ c₁) q /-- 반복. `i` 가 불변식, `e` 가 변항, `z` 가 한 바퀴 시작의 `e` 값을 붙드는 유령 변수. -/ | wh {i : Assert V} {b : BoolExp V} {c : Comm V} {e : IntExp V} {z : V} (hzi : z ∉ i.fv) (hzb : z ∉ b.fv) (hzc : z ∉ c.fv) (hze : z ∉ e.fv) (hnonneg : Stronger (i ⋀ b.toAssert) (.cmp .le (.num 0) e)) : HoareT (i ⋀ b.toAssert ⋀ .cmp .eq e (.var z)) c (i ⋀ .cmp .lt e (.var z)) → HoareT i (.wh b c) (i ⋀ .not b.toAssert) | dc (s : List (Comm V)) {p q : Assert V} {v : V} {e : IntExp V} {c : Comm V} (hq : v ∉ q.fv) : HoareT p (Comm.seqs s (.seq (.assign v e) c)) q → HoareT p (Comm.seqs s (.newvar v e c)) q /-- RN: 앞부분 뒤의 지역 결합 이름을 어느 방향으로든 바꾼다. -/ | rename {p q : Assert V} {c c' : Comm V} : Comm.PrefixRename c c' → HoareT p c q → HoareT p c' q /-- SP (§3.3 p.59). 사전조건을 강화한다. -/ | strengthen {p p' q : Assert V} {c : Comm V} : Stronger p' p → HoareT p c q → HoareT p' c q /-- WC (§3.3 p.59). 사후조건을 약화한다. -/ | weaken {p q q' : Assert V} {c : Comm V} : HoareT p c q → Stronger q q' → HoareT p c q' /-- CA (§3.5 p.68). 같은 명령의 두 명세를 연언으로 합친다. -/ | conj {p₀ p₁ q₀ q₁ : Assert V} {c : Comm V} : HoareT p₀ c q₀ → HoareT p₁ c q₁ → HoareT (p₀ ⋀ p₁) c (q₀ ⋀ q₁) /-- DA (§3.5 p.68). 서로 다른 사후조건도 선언으로 합친다. -/ | disj {p₀ p₁ q₀ q₁ : Assert V} {c : Comm V} : HoareT p₀ c q₀ → HoareT p₁ c q₁ → HoareT (.bin .or p₀ p₁) c (.bin .or q₀ q₁) /-- CST (§3.5 p.68). 전제의 종료 보장에 변하지 않는 단언을 덧붙인다. -/ | constancy {p q r : Assert V} {c : Comm V} (hr : Disjoint c.fa r.fv) : HoareT p c q → HoareT (p ⋀ r) c (q ⋀ r)

z는 본체 실행 전의 e 값을 기억하는 유령 변수다. 명세에는 나오지만 명령에는 자유롭게 나오지 않는다. z ∉ FV(c)는 본체가 이 값을 보존한다는 사실뿐 아니라, z를 바꾼 입력에서의 실행 결과를 원래 입력으로 옮기는 데도 쓰인다. 값의 보존만 말할 때는 더 약한 z ∉ FA(c)로 충분하다.

§3.1에서 보았듯 전체 정확성은 극한을 통과하지만, ⊥에서 종료를 보일 수 없어 Scott 귀납법의 시작 조건이 막힌다. 그래서 건전성 증명은 측도 위의 정초 귀납을 쓴다. 2장에서 구체적인 루프의 값을 계산할 때 손으로 잡던 측도를 이제는 규칙이 인자로 받는다.

의미 단언에서는 실행 전 변항 값을 n : Int로 기억한다. 연습에서는 변항보다 큰 자연수 상계를 줄이는 귀납법을 구성한다. 비음수 조건은 반복 조건이 참일 때만 필요하므로, 마지막 본체 실행이 변항을 음수로 만들어도 다음 조건이 거짓이면 된다.

/-- WHT (§3.4). 본체가 불변식을 보존하고 정수 변항을 엄격히 줄이면 반복이 종료한다. 변항의 비음수 조건은 불변식과 반복 조건이 참인 상태에만 요구한다. 마지막 실행 뒤 조건이 거짓이면 변항은 음수여도 된다. **책과의 차이**: 책 p64의 유령 변수 대신 본체 실행 전의 값을 `n : Int`로 고정한다. 구문 단언의 유령 변수 규칙은 `Total.lean`에서 이 정리의 따름정리로 얻는다. -/ @[exercise "§3.4 wht-sound" 3] theorem TotalCorrectS.wh {I : State V → Prop} {E : State V → Int} {b : BoolExp V} {c : Comm V} (hbody : ∀ n : Int, TotalCorrectS (fun σ => I σ ∧ ⟦b⟧ᵇ σ = true ∧ E σ = n) c (fun σ => I σ ∧ E σ < n)) (hnonneg : ∀ σ, I σ → ⟦b⟧ᵇ σ = true → 0 ≤ E σ) : TotalCorrectS I (.wh b c) (fun σ => I σ ∧ ⟦b⟧ᵇ σ = false)

구문 판 whT_sound는 책 p64의 유령 변수 규칙을 그대로 유지하는 제공 정리다. 본체를 σ[z := n]에서 실행한 뒤, 명제 2.6으로 z 밖의 결과를 원래 입력의 실행에 옮긴다. 이 과정은 의미 규칙에 줄 전제를 만들며, 반복의 종료는 위 연습이 증명한다.

명제 2.6의 두 반쪽이 모두 쓰인다. (b)는 본체가 z를 건드리지 않음을, (a)는 z만 다른 두 시작 상태에서 본체의 결과가 불변식과 변항이 보는 변수들 위에서 같음을 준다.

§2.8의 백까지 세기를 규칙만으로 유도하면, 이번에는 끝난다는 것까지 얻는다.

/-- `[x ≤ 100] while x < 100 do x := x + 1 [x = 100]`. -/ theorem countTo100_total : HoareT (⟪ x ≤ 100 ⟫ₐ) ⟪ while x < 100 do x := x + 1 ⟫ᶜ (⟪ x = 100 ⟫ₐ) := ⊢ HoareT (Assert.cmp Cmp.le (IntExp.var "x") (IntExp.num 100)) (Comm.wh (BoolExp.cmp Cmp.lt (IntExp.var "x") (IntExp.num 100)) (Comm.assign "x" (IntExp.bin IntOp.add (IntExp.var "x") (IntExp.num 1)))) (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.num 100)) ⊢ "z" ∉ (Assert.cmp Cmp.le (IntExp.var "x") (IntExp.num 100)).fv⊢ "z" ∉ (BoolExp.cmp Cmp.lt (IntExp.var "x") (IntExp.num 100)).fv⊢ "z" ∉ (Comm.assign "x" (IntExp.bin IntOp.add (IntExp.var "x") (IntExp.num 1))).fv⊢ "z" ∉ (IntExp.bin IntOp.sub (IntExp.num 100) (IntExp.var "x")).fv⊢ Stronger (Assert.bin LogOp.and (Assert.cmp Cmp.le (IntExp.var "x") (IntExp.num 100)) (BoolExp.cmp Cmp.lt (IntExp.var "x") (IntExp.num 100)).toAssert) (Assert.cmp Cmp.le (IntExp.num 0) (IntExp.bin IntOp.sub (IntExp.num 100) (IntExp.var "x")))⊢ HoareT (Assert.bin LogOp.and (Assert.bin LogOp.and (Assert.cmp Cmp.le (IntExp.var "x") (IntExp.num 100)) (BoolExp.cmp Cmp.lt (IntExp.var "x") (IntExp.num 100)).toAssert) (Assert.cmp Cmp.eq (IntExp.bin IntOp.sub (IntExp.num 100) (IntExp.var "x")) (IntExp.var "z"))) (Comm.assign "x" (IntExp.bin IntOp.add (IntExp.var "x") (IntExp.num 1))) (Assert.bin LogOp.and (Assert.cmp Cmp.le (IntExp.var "x") (IntExp.num 100)) (Assert.cmp Cmp.lt (IntExp.bin IntOp.sub (IntExp.num 100) (IntExp.var "x")) (IntExp.var "z")))⊢ Stronger (Assert.bin LogOp.and (Assert.cmp Cmp.le (IntExp.var "x") (IntExp.num 100)) (BoolExp.cmp Cmp.lt (IntExp.var "x") (IntExp.num 100)).toAssert.not) (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.num 100)) ⊢ "z" ∉ (Assert.cmp Cmp.le (IntExp.var "x") (IntExp.num 100)).fv All goals completed! 🐙 ⊢ "z" ∉ (BoolExp.cmp Cmp.lt (IntExp.var "x") (IntExp.num 100)).fv All goals completed! 🐙 ⊢ "z" ∉ (Comm.assign "x" (IntExp.bin IntOp.add (IntExp.var "x") (IntExp.num 1))).fv All goals completed! 🐙 ⊢ "z" ∉ (IntExp.bin IntOp.sub (IntExp.num 100) (IntExp.var "x")).fv All goals completed! 🐙 ⊢ Stronger (Assert.bin LogOp.and (Assert.cmp Cmp.le (IntExp.var "x") (IntExp.num 100)) (BoolExp.cmp Cmp.lt (IntExp.var "x") (IntExp.num 100)).toAssert) (Assert.cmp Cmp.le (IntExp.num 0) (IntExp.bin IntOp.sub (IntExp.num 100) (IntExp.var "x"))) σ:State Stringh:⟦Assert.bin LogOp.and (Assert.cmp Cmp.le (IntExp.var "x") (IntExp.num 100)) (BoolExp.cmp Cmp.lt (IntExp.var "x") (IntExp.num 100)).toAssert⟧ₐ σ⊢ ⟦Assert.cmp Cmp.le (IntExp.num 0) (IntExp.bin IntOp.sub (IntExp.num 100) (IntExp.var "x"))⟧ₐ σ σ:State Stringh:σ "x" ≤ 100 ∧ σ "x" < 100⊢ σ "x" ≤ 100 All goals completed! 🐙 ⊢ HoareT (Assert.bin LogOp.and (Assert.bin LogOp.and (Assert.cmp Cmp.le (IntExp.var "x") (IntExp.num 100)) (BoolExp.cmp Cmp.lt (IntExp.var "x") (IntExp.num 100)).toAssert) (Assert.cmp Cmp.eq (IntExp.bin IntOp.sub (IntExp.num 100) (IntExp.var "x")) (IntExp.var "z"))) (Comm.assign "x" (IntExp.bin IntOp.add (IntExp.var "x") (IntExp.num 1))) (Assert.bin LogOp.and (Assert.cmp Cmp.le (IntExp.var "x") (IntExp.num 100)) (Assert.cmp Cmp.lt (IntExp.bin IntOp.sub (IntExp.num 100) (IntExp.var "x")) (IntExp.var "z"))) ⊢ Stronger (Assert.bin LogOp.and (Assert.bin LogOp.and (Assert.cmp Cmp.le (IntExp.var "x") (IntExp.num 100)) (BoolExp.cmp Cmp.lt (IntExp.var "x") (IntExp.num 100)).toAssert) (Assert.cmp Cmp.eq (IntExp.bin IntOp.sub (IntExp.num 100) (IntExp.var "x")) (IntExp.var "z"))) (Assert.bin LogOp.and (Assert.cmp Cmp.le (IntExp.var "x") (IntExp.num 100)) (Assert.cmp Cmp.lt (IntExp.bin IntOp.sub (IntExp.num 100) (IntExp.var "x")) (IntExp.var "z")) /["x":=IntExp.bin IntOp.add (IntExp.var "x") (IntExp.num 1)] ) σ:State Stringh:⟦Assert.bin LogOp.and (Assert.bin LogOp.and (Assert.cmp Cmp.le (IntExp.var "x") (IntExp.num 100)) (BoolExp.cmp Cmp.lt (IntExp.var "x") (IntExp.num 100)).toAssert) (Assert.cmp Cmp.eq (IntExp.bin IntOp.sub (IntExp.num 100) (IntExp.var "x")) (IntExp.var "z"))⟧ₐ σ⊢ ⟦Assert.bin LogOp.and (Assert.cmp Cmp.le (IntExp.var "x") (IntExp.num 100)) (Assert.cmp Cmp.lt (IntExp.bin IntOp.sub (IntExp.num 100) (IntExp.var "x")) (IntExp.var "z")) /["x":=IntExp.bin IntOp.add (IntExp.var "x") (IntExp.num 1)] ⟧ₐ σ σ:State Stringh:(σ "x" ≤ 100 ∧ σ "x" < 100) ∧ 100 - σ "x" = σ "z"⊢ σ "x" + 1 ≤ 100 ∧ 100 - (σ "x" + 1) < σ "z" All goals completed! 🐙 ⊢ Stronger (Assert.bin LogOp.and (Assert.cmp Cmp.le (IntExp.var "x") (IntExp.num 100)) (BoolExp.cmp Cmp.lt (IntExp.var "x") (IntExp.num 100)).toAssert.not) (Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.num 100)) σ:State Stringh:⟦Assert.bin LogOp.and (Assert.cmp Cmp.le (IntExp.var "x") (IntExp.num 100)) (BoolExp.cmp Cmp.lt (IntExp.var "x") (IntExp.num 100)).toAssert.not⟧ₐ σ⊢ ⟦Assert.cmp Cmp.eq (IntExp.var "x") (IntExp.num 100)⟧ₐ σ σ:State Stringh:σ "x" ≤ 100 ∧ 100 ≤ σ "x"⊢ σ "x" = 100 All goals completed! 🐙 /-- 그러니 §2.8 에서 계산으로 얻은 것을 규칙만으로 다시 얻는다 — 그리고 이번에는 끝난다는 것까지. -/ example : [⟪ x ≤ 100 ⟫ₐ]⟪ while x < 100 do x := x + 1 ⟫ᶜ[⟪ x = 100 ⟫ₐ] := countTo100_total.sound