「SF-PLF」5 Smallstep
Recall Big-step Pros & Cons
Big-step
一步到位 : eval to its final value (plus final store)
Pros - natural (so called natural semantics), “all in one big step”
Cons - not catch the essence of how program behave
大步语义只是一个
程序 ↦ 结果
这样的 pair 集合,而「如何一步步处理」才是程序「执行」的本质
not just input state get mapped to output state.
but also intermediate state (which could be observed by concurrent code!)
Cons - not technically expressive enough to express exception / crash / non-termination
比如说,大步语义无法区分「不停机」与「卡住」
two quite different reasons of “fail to map a given state to any ending state”
- 不停机 nontermination - we want to allow this (infinite loop is the price paid for usability)
- 卡住 getting stuck / undefiend behaviour 未定义行为 - we want to prevent (wrong)
WHILE_true_nonterm
仅仅表达了「程序不能再 take step」,无法与「卡住」区分WHILE_true
更是直接让任何「无限循环」的程序都「等价」了…而忽略了中间状态和 effect (作用)
we need a way of presenting semantics that distinguish nontermination from erroneous “stuck states”
Small-step
更精细化 : a finer-grained way of defining and reasoning about program behaviors.
原子步骤 : “atomic steps” of computation are performed.
A Toy Language
Only Constant and Plus
1 | Inductive tm : Type := |
Big-Step
==>
is really ⇓
--------- (E_Const)
C n ==> n
t1 ==> n1
t2 ==> n2
------------------- (E_Plus)
P t1 t2 ==> n1 + n2
Small-Step
single reduction step
find leftmost redex
------------------------------- (ST_PlusConstConst)
P (C n1) (C n2) --> C (n1 + n2)
t1 --> t1'
-------------------- (ST_Plus1)
P t1 t2 --> P t1' t2
t2 --> t2'
---------------------------- (ST_Plus2)
P (C n1) t2 --> P (C n1) t2'
Relations
Check notes of
rel
andtactics
for more details about bi-relation.
Deterministic 确定性
a.k.a Partial Function.
in terms of its right uniqueness under mathematical context, not its emphasise on partial under programming context)
1 | Definition deterministic {X : Type} (R : relation X) := |
deterministic step
can be proved by induction on derivation x --> y1
- use
generalize dependent y2
! - in informal proof, we usually just take
∀ y2
by default.
Ltac solve_by_inverts n
1 | Ltac solve_by_inverts n := |
Values 值
Abstract Machine 抽象机!
think of the
-->
relation as defining an abstract machine:
- term = state of machine 项 = 机器状态
- step = atomic unit of computation (think as assembly opcode / CPU instructrion)
- halting state = no more computation. 停机状态
execute a term
t
:
- starting state =
t
- repeatedly use
-->
- when halt, read out the final state as result of execution
Intutively, we call such (final state) terms values.
Okay so the point is…this language is simple enough (no stuck state).
and in this lang, value can only beC
onst:
在这个语言中,我们「规定」只有
C
onst 是「值」:
1 | Inductive value : tm → Prop := |
and we can write
ST_Plus2
more elegant:
well…in this lang, not really, since only one form of value to write out.
in cases we have multiple form of value, by doing this we don’t have to write out any cases.
value v1
t2 --> t2'
-------------------- (ST_Plus2)
P v1 t2 --> P v1 t2'
Strong Progress and Normal Forms 强可进性和正规式
strong progress: every term either is a value or can “make progress”
1 | Theorem strong_progress : ∀t, |
terms that cannot make progress.
for an arbitrary relationR
over an arbitrary setX
normal form: term that cannot make progress (take a step)
其实我个人比较喜欢理解为「常态」或「无能量稳定态」
1 | Definition normal_form {X : Type} (R : relation X) (t : X) : Prop := |
theorem: in this language, normal forms and values are actually the same thing.
1 | Lemma value_is_nf : v, value v → normal_form step v. |
Value != Normal Form (not always)
value is a syntactic concept : it is defined by looking at the form of a term
normal form is a semantic one : it is defined by looking at how the term steps.
E.g. we can defined term that can take a step as “value”:
添加一个不是 normal form 的 value
1 | Inductive value : tm → Prop := |
或者更改
step
让 value 不是 normal form…
1 | Inductive step : tm -> tm -> Prop := |
Multi-Step Reduction -->*
多步规约
relation
multi R
: multi-step closure of R
same asclos_refl_trans_1n
inRel
chapter.
1 | Inductive multi {X : Type} (R : relation X) : relation X := |
以上是一种方便的定义,而以下则给了我们两个 helper 定理:
1 | Theorem multi_R : ∀(X : Type) (R : relation X) (x y : X), |
Normal Forms Again
1 | Definition step_normal_form := normal_form step. (** 这个是一个「性质」 Property : _ -> Prop , 从 polymorphic 的 [normal_form] 以 [step] 实例化而来 **) |
Normalizing 总是可正规化得 – “Evaluating to completion”
something stronger is true for this language (though not for all languages)
reduction of any termt
will eventually reach a normal form (我们知道 STLC 也有这个特性)
1 | Definition normalizing {X : Type} (R : relation X) := |
To prove this, we need lemma showing some congruence of -->*
:
同余关系,不过这次是定义在 -->*
这个关系上,again,同余指的是「关系对于结构上的操作保持」
1 | Lemma multistep_congr_1 : ∀t1 t1' t2, |
Then we can prove…
1 | Theorem step_normalizing : |
Equivalence of Big-Step and Small-Step
1 | Theorem eval__multistep : ∀t n, |
Additional: Combined Language
What if we combined the lang Arith
and lang Boolean
?
Would step_deterministic
and strong_progress
still holds?
Intuition:
step_deterministic
should still hold- but
strong_progress
would definitely not!!- now we mixed two types so we will have stuck terms e.g.
test 5
ortru + 4
. - we will need type check and then we would be able to prove
progress
(which require well-typeness)
- now we mixed two types so we will have stuck terms e.g.
1 | Theorem strong_progress : |
Small-Step IMP
又到了老朋友 IMP……还好没练习……简单看一下
首先对于定义小步语义,我们需要定义 value
和 -->
(step)
aexp
, bexp
1 | Inductive aval : aexp → Prop := |
bexp
不需要 value
因为在这个语言里 BTrue
和 BFalse
的 step 总是 disjointed 得,所以并没有任何复用 value
predicate 的时候
-->a
, -->b
这里,我们先为 aexp
, bexp
定义了它们各自的小步语义,
但是,其实 from PLT we know, 我们其实也可以直接复用
aexp
,bexp
的大步语义!
- 大步语义要短得多
aexp
,bexp
其实并不会出
- 「不停机」: 没有 jump 等控制流结构
- 「异常」/「卡住」: 我们在 meta-language 的 AST 里就区分了
aexp
和bexp
,相当于主动约束了类型,所以不会出现5 || 3
这样 type error 的 AST
cmd
, -->
我们把
SKIP
当作一个「命令值(command value)」 i.e. 一个已经到达 normal form 的命令。
- 赋值命令归约到
SKIP
(和一个新的 state)。- 顺序命令等待其左侧子命令归约到
SKIP
,然后丢弃它,并继续对右侧子命令归约。
对
WHILE
命令的归约是把WHILE
命令变换为条件语句,其后紧跟同一个WHILE
命令。
这些都与 PLT 是一致的
Concurrent IMP
为了展示 小步语义 的能力,let’s enrich IMP with concurrency.
- unpredictable scheduling (subcommands may be interleaved)
- share same memory
It’s slightly confusing here to use Par
(meaning in parallel)
I mean, concurrency could be in parallel but it doesn’t have to…
1 | Inductive com : Type := |
A Small-Step Stack Machine 小步栈机
啊哈!IMP 章节 Stack Machine,我们之前仅仅定义了 Fixpoint s_execute
和 Fixpoint s_compile
,这里给出其小步语义
对于本身就与「小步语义」在精神上更统一的「抽象机」,我怀疑其语义都应该是足够「小」的(即大小步将是一致的?)
1 | Definition stack := list nat. |
Compiler Correctness
「编译器的正确性」= the notion of semantics preservation (in terms of observable behaviours)
S =e
C =s_compile e
B(S) =aeval st e
B(C) = functionals_execute
| relationalstack_multistep
之前我们证明过 functional/computational Fixpoint
的性质
1 | Theorem s_compile_correct : forall (st : state) (e : aexp), |
现在则是证明 relational Inductive
的性质,同样我们需要一个更一般的定理(然后原命题作为推论)
1 | Theorem stack_step_theorem : forall (st : state) (e : aexp) (stack : list nat) (prog : list sinstr), |
Aside: A normalize
Tactic
Even with eapply
and auto
…manual normalization is tedious:
1 | Example step_example1' : |
We could write custom Tactic Notation
…(i.e. tactic macros)
1 | Tactic Notation "print_goal" := |
instantiate
seems here for intros ∃
?
1 | Example step_example1''' : exists e', |
But what surprise me is that we can eapply ex_intro
, which leave the ∃
as a hole ?ex
(unification variable).