《Understanding Computation: From Simple Machines to Impossible Programs》(计算的本质) 是一本由浅入深逐步讲解计算机程序的一本书。书中的代码例子使用 Ruby 描述,因此也有人称之为 Ruby版本的SCIP。
书中的第二章探讨了程序的含义。通过自定义语言规则,构建抽象语法树展示了编程语言和程序的基本意义。本文即对该内容的学习而做的学习笔记。
因原文代码是Ruby,Ruby我不是很熟。就使用了熟悉的 Python3, Golang 和 Rust分别实现了三种版本。
三个版本的Git源码仓库
原书由了小步语义(small step semantic)开始,介绍什么是小步语义,并且设计一个虚拟机进行规约求值。然后再介绍大步语义(big step sanmactic),最后再介绍指称语义(denatation)。三者逐步递进,易于理解。然而本文是对三个整体概念的笔记,会从最后宏观的角度解释理论和代码。
语义
语言学中,语义研究的是单词和他们之间的关系。单词和单词组成短语和句子。计算机的编程语言也有很多种,计算机可选重视的是形式语义学,后者从定义新的语言和进化编译优化具体应用,到构造程序正确性的数学证明领域都应用广泛。
计算机里的语义值的是程序的含义。通过程序语义的语法描述程序是怎么样的。现实中描述变成语言有两种。第一种就是官方规范(C++,Java),即委员会制定的各种语言规范,另外一种就是具体实现(Python,Ruby),如 CPython或Ruby的解释器实现。
不管是哪种,程序员使用的编程语言都有一套语法约束,然后通过编译器或解释器将语法规则转换成预发解析器,或是构建抽象语法树。最后是进行表达式求值。
我们关注的是如何定义一种语法规则,然后构建抽象语法树,进而对这个树求值以得到结果。
小步语义
我们可以设计一种机器,用这个机器按照某个语法规则进行一小步一小步的反复规约,从而进行求值。例如下面表达式
1 + 3 — 2
可以从左到右依次求值
1 + 3 - 2
4 - 2
2
通常这种语法规则可以用数学化公式描述。但是作为开发者,我们理解其中含义更好的方式是通过代码。
假设我们需要设计一个叫Simple
的编程语言。先通过小步语义规约进行实现。首先我们的编程语言关注有三个要素:
- 环境(env):程序运行的环境,类型是Python的字典Dict,其环境存储了值
- 表达式(expr):运算表达式或操作表达式
- 语句(stmt):表达式构成的语句,程序体的构成。
设计一个表达类
class BaseMixin:
@property
def value(self) -> Any:
if hasattr(self, "val"):
return self.val
raise AttributeError
@property
def reducible(self) -> bool:
return False
class VarExprMixin(BaseMixin):
@property
def reducible(self) -> bool:
return False
class ExprMixin(BaseMixin):
@property
def reducible(self) -> bool:
return True
def reduce(self, env: Dict) -> ExprMixin:
raise NotImplementedError
def evaluate(self, env: Dict) -> ExprMixin:
raise NotImplementedError
@property
def to_python(self) -> str:
raise NotImplementedError
ExprMixin
是表达式类,即定义表达式行为的接口,其中有个四个方法或属性:
-
reducibel
: 表示该表达式是否可以规约,像Number
,Boolean
这样的值类型不能规约了。 -
reduce
:表达式的规范方法,即表达式求值的逻辑,规约的方法返回还是表达式 ExprMixin -
evaluate
:大步语义中表达式求值的方法,求值返回的也是表达式 ExprMixin -
to_python
: 指称语义中表达式转换成python语义的方法,返回的是 python 代码字串
当前小步语义中,我们主要关注 reducible
和 reduce
方法。下面实现两个基本的值类型
值表达式 Number & Boolean
值表达式只有一个值。无操作符和运算符
class Number(VarExprMixin, ExprMixin):
def __init__(self, val: int):
self.val = val
def __repr__(self):
return f"{self.val}"
class Boolean(VarExprMixin, ExprMixin):
def __init__(self, val: bool):
self.val = val
def __repr__(self):
return f"{self.val}"
def __eq__(self, other):
return self.val == other.val
Number
和 Boolean
分表表示数字和布尔值。它们都不能规约了。在命令行中可以看到
>>> expr = Number(val=1)
>>> expr
1
>>> Boolean(True)
True
>>>
二元表达式 Add & Mul & LessThan
二元表达式有两个操作数,左值和右值。如运算表达式(1 + 2)和比较表达式 (3 < 2),布尔表达式(true ** 1 + 2)
class Add(ExprMixin):
def __init__(self, left: ExprMixin, right: ExprMixin):
self.left = left
self.right = right
def __repr__(self):
return f"{self.left} + {self.right}"
def reduce(self, env: Dict) -> ExprMixin:
if self.left.reducible:
return Add(self.left.reduce(env), self.right)
elif self.right.reducible:
return Add(self.left, self.right.reduce(env))
else:
return Number(self.left.value + self.right.value)
class Mul(ExprMixin):
def __init__(self, left: ExprMixin, right: ExprMixin):
self.left = left
self.right = right
def __repr__(self):
return f"{self.left} * {self.right}"
def reduce(self, env: Dict) -> ExprMixin:
if self.left.reducible:
return Mul(self.left.reduce(env), self.right)
elif self.right.reducible:
return Mul(self.left, self.right.reduce(env))
else:
return Number(self.left.value * self.right.value)
class LessThan(ExprMixin):
def __init__(self, left: ExprMixin, right: ExprMixin):
self.left = left
self.right = right
def __repr__(self):
return f"{self.left} < {self.right}"
def reduce(self, env: Dict) -> ExprMixin:
if self.left.reducible:
return LessThan(self.left.reduce(env), self.right)
elif self.right.reducible:
return LessThan(self.left, self.right.reduce(env))
else:
return Boolean(self.left.value < self.right.value)
从上面的代码可以看到,对于运算表达式,都有左值(left)和右值(right)。其规约方法如下:
- left 可以规约,则对left进行规约,并返回一个新的表达式
- right 可以规约,则再对right进行规约,同样返回一个新的表达式
- left和right都无法规约,则证明表达式为 Number或Boolean类型,依次返回对应类型即可
注意,上面的规约方法忽略了运算的优先级,优先级会影响学习小步语义,可以暂时忽略。
下面验证一下上面的代码:
Add 表达式
>>> expr = Add(Number(1), Number(2))
>>> expr
1 + 2
>>> expr.reducible # Add 表达式可以规约
True
>>> expr = expr.reduce({}) # 规约返回新的表达式
>>> expr
3
>>> expr.reducible # left 和 right 无法规约,上一步返回的是 Number 表达式
False
Mul 表达式
>>> expr = Add(
... left=Mul(Number(1), Number(2)),
... right=Mul(Number(3), Number(4))
... )
>>> expr
1 * 2 + 3 * 4
>>> expr.reducible
True
>>> expr = expr.reduce({}) # Add 的left 规约 left 1 + 2 规约为 3
>>> expr
2 + 3 * 4
>>> expr.reducible
True
>>> expr = expr.reduce({}) # Add 的 right 规约 right 3 * 4 规约为 12
>>> expr
2 + 12
>>> expr.reducible
True
>>> expr = expr.reduce({}) # 最后规约 Add 本身
>>> expr
14
>>> expr.reducible
False
LessThan 表达式
>>> expr = LessThan(Number(1), Number(2))
>>> expr
1 < 2
>>> expr.reducible
True
>>> expr = expr.reduce({})
>>> expr
True
>>> expr.reducible
False
Add, Mul, LessThan的特点都是二元表达式。类似的 减法 Sub,除法 Div,逻辑与或 And,Or可以参考github的repo。
变量 Variable
变量是编程语言的重要元素。定义变量的值是表达式,变量存储在环境中。上面的值表达式和运行表达式都没有使用环境,变量需要读写环境。
Variable
class Variable(ExprMixin):
def __init__(self, name: str):
self.name = name
def __repr__(self):
return f"{self.name}"
def reduce(self, env: Dict) -> ExprMixin:
return env[self.name]
变量的规约方法即返回环境中的表达式。
>>> env = {"x": Number(1)} # 初始化环境
>>> env
{'x': 1}
>>> expr = Variable("x")
>>> expr
x
>>> expr.reducible
True
>>> expr = expr.reduce(env)
>>> expr
1
>>> isinstance(expr, Number)
True
语句
ExprMixin 是表达式接口定义。此外,三个要素中还有语句。语句与表达式有点区别,语句的规约返回的是语句和环境。
语句接口
class StmtMixin:
@property
def reducible(self) -> bool:
return True
def reduce(self, env: Dict) -> (StmtMixin, Dict):
raise NotImplementedError
def evaluate(self, env: Dict) -> Dict:
raise NotImplementedError
@property
def to_python(self) -> str:
raise NotImplementedError
与表达式接口 ExprMixin 类似,语句接口 StmtMixin 也有四个方法。其中 reduce 和 表达式不一样,它返回的语句本身,同时还返回了一个环境。因为语句在规约的时候,会对表达式求值,并且将求值结果更新到环境中。
空语句
class DoNothing(VarExprMixin, StmtMixin):
def __repr__(self):
return "do-nothing"
def __eq__(self, other):
return isinstance(other, DoNothing)
DoNohing 语句表示空语句,它与 Number,Boolean一样,不能再规约,表示语句规约的终点。但是它本身是一个语句,而不是表达式。
Assign 赋值语句
对于开发者而言,赋值语句最平常了。通常是 变量 = 值(表达式)
的形式。下面是其实现
class Assign(StmtMixin):
def __init__(self, name: str, expr: ExprMixin):
self.name = name
self.expr = expr
def __repr__(self):
return f"{self.name} = {self.expr}"
def reduce(self, env: Dict) -> (StmtMixin, Dict):
if self.expr.reducible:
return Assign(self.name, self.expr.reduce(env)), env
env[self.name] = self.expr
return DoNothing(), env
Assign 实现了 StmtMixin 接口, 它的规约方法很简单:
- 右值表达式如果可以规约,则进行规约,返回新的赋值表达式
- 右值不能规约,把当前的 name 更新到环境中,返回 DoNothing 语句,表示规约完毕
测试:
>>> env = {"x": Number(2)} # 初始化环境
>>> env
{'x': 2}
>>> stmt = Assign("x", Add(Variable("x"), Number(1))) # 赋值语句
>>> stmt
x = x + 1
>>> stmt.reducible
True
>>> stmt, env = stmt.reduce(env) # 规约,即右值表达式进行规约 第一步是变量进行规约
>>> stmt
x = 2 + 1
>>> env
{'x': 2}
>>> stmt.reducible
True
>>> stmt, env = stmt.reduce(env) # 第二步是 Add 表达式规约
>>> stmt
x = 3
>>> env
{'x': 2}
>>> stmt.reducible
True
>>> stmt, env = stmt.reduce(env) # 最后是赋值语句本身规约
>>> stmt
do-nothing
>>> env # 赋值表达式会更新环境
{'x': 3}
>>> stmt.reducible
False
虚拟机
从上面的赋值语句的规约可以看到,语句的规约是反复的调用自身的reduce方法。同时将结果输出到环境中。因此可以设计一个虚拟机,用于一步一步执行规约。这也是本节titile的含义,小步语义。
class Machine:
def __init__(self, stmt: StmtMixin, env: Dict):
self.stmt = stmt
self.env = env
def step(self):
self.stmt, self.env = self.stmt.reduce(self.env) # 规约
def run(self, debug=True):
while self.stmt.reducible: # 在循环中小步规约
print(self.stmt, self.env)
self.step()
print(self.stmt, self.env)
有了虚拟机,赋值语句的小步规约就简单了
>>> stmt = Assign("x", Add(Variable("x"), Number(1)))
>>> stmt
x = x + 1
>>> env = {"x": Number(2)}
>>> env
{'x': 2}
>>> machine = Machine(stmt, env)
>>> machine.run()
x = x + 1 {'x': 2}
x = 2 + 1 {'x': 2}
x = 3 {'x': 2}
do-nothing {'x': 3}
>>> machine.stmt
do-nothing
>>> machine.env
{'x': 3}
If 条件语句
下一步是实现 If 条件语句。if 语句有三个部分,条件 cond 表达式,if主体 consequence 语句,和 else 主体 alternative 语句。
class If(StmtMixin):
def __init__(self, cond: ExprMixin, consequence: StmtMixin, alternative: StmtMixin):
self.cond = cond
self.consequence = consequence
self.alternative = alternative
def __repr__(self):
return f"if ({self.cond}) {{{self.consequence}}} else {{{self.alternative}}}"
def reduce(self, env: Dict) -> (StmtMixin, Dict):
if self.cond.reducible:
return If(self.cond.reduce(env), self.consequence, self.alternative), env
else:
if self.cond == Boolean(True):
return self.consequence, env
else: # self.cond == Boolean(False)
return self.alternative, env
规约的方法比较中规中矩:
- cond 表达式可以规约,则对其规约,并返回新的 if 表达式和环境。
- cond表达式不能规约,即为 Boolean值的时候,如果为真,则返回 consequence 语句和环境,否则返回 alternative 语句。注意 if 的主体是语句。
>>> stmt = If(
... cond=Variable("x"),
... consequence=Assign("y", Number(1)),
... alternative=Assign("y", Number(2)),
... )
>>> stmt
if (x) {y = 1} else {y = 2}
>>> env = {"x": Boolean(True)}
>>> env
{'x': True}
>>> machine = Machine(stmt, env)
>>> machine.run()
if (x) {y = 1} else {y = 2} {'x': True}
if (True) {y = 1} else {y = 2} {'x': True}
y = 1 {'x': True}
do-nothing {'x': True, 'y': 1}
>>> env
{'x': True, 'y': 1}
if 语句最终规约返回的 stmt也是 donothing,因此如果只是 if 语句而忽略 else,那么 else 可以直接初始化为 DoNothing 语句。
Sequence 序列组合语句
从 If 语句可以看出,它必然返回 consequence 或者 alternative。这两者都是语句,通常程序不会只有一句话。因此下面实现的是序列组合语句
class Sequence(StmtMixin):
def __init__(self, first: StmtMixin, second: StmtMixin):
self.first = first
self.second = second
def __repr__(self):
return f"{self.first};{self.second}"
def reduce(self, env: Dict) -> (StmtMixin, Dict):
if self.first.reducible:
reduced_first, reduced_env = self.first.reduce(env)
return Sequence(reduced_first, self.second), reduced_env
else:
return self.second.reduce(env)
Sequence有两部分,其规约如下:
- first 可以规约,则对其进行规约,并返回新的 Sequence 语句
- first 不能规约(donothing),则 scond 进行规约
>>> stmt = Sequence(
... first=Assign("x", Add(Number(1), Number(1))),
... second=Assign("y", Add(Variable("x"), Number(1))),
... )
>>> stmt
x = 1 + 1;y = x + 1
>>> env = {}
>>> machine = Machine(stmt, env)
>>> machine.run()
x = 1 + 1;y = x + 1 {}
x = 2;y = x + 1 {}
do-nothing;y = x + 1 {'x': 2}
y = 2 + 1 {'x': 2}
y = 3 {'x': 2}
do-nothing {'x': 2, 'y': 3}
>>> env
{'x': 2, 'y': 3}
second初始化的时候是可以规约,当他不能规约的时候,会不满足 machine而直接终止。并且若有 3或3+的语句,也用担心,可以一次规约,这将在 while 循环语句中体现。
While 循环语句
class While(StmtMixin):
def __init__(self, cond: ExprMixin, body: StmtMixin):
self.cond = cond
self.body = body
def __repr__(self):
return f"while ({self.cond}) {{{self.body}}}"
def reduce(self, env: Dict) -> (StmtMixin, Dict):
return If(self.cond, Sequence(self.body, self), DoNothing()), env
循环语句的规约使用了点小技巧
- 可以把循环看成是 If 语句中 else 为 DoNothing 进行规约
- 当执行 If 语句的 consequence的时候,使用 Sequence语句将 body 和 while 进行组合,这样执行完 body(first)之后,Sequence语句将将执行 second,即 下一次 while
>>> env = {"x": Number(1)}
>>> env
>>> stmt = While(
cond=LessThan(Variable("x"), Number(5)),
body=Assign("x", Mul(Variable("x"), Number(3)))
... )
>>> stmt
while (x < 5) {x = x * 3}
>>> machine = Machine(stmt, env)
>>> machine.run()
while (x < 5) {x = x * 3} {'x': 1}
if (x < 5) {x = x * 3;while (x < 5) {x = x * 3}} else {do-nothing} {'x': 1}
if (1 < 5) {x = x * 3;while (x < 5) {x = x * 3}} else {do-nothing} {'x': 1}
if (True) {x = x * 3;while (x < 5) {x = x * 3}} else {do-nothing} {'x': 1}
x = x * 3;while (x < 5) {x = x * 3} {'x': 1}
x = 1 * 3;while (x < 5) {x = x * 3} {'x': 1}
x = 3;while (x < 5) {x = x * 3} {'x': 1}
do-nothing;while (x < 5) {x = x * 3} {'x': 3}
if (x < 5) {x = x * 3;while (x < 5) {x = x * 3}} else {do-nothing} {'x': 3}
if (3 < 5) {x = x * 3;while (x < 5) {x = x * 3}} else {do-nothing} {'x': 3}
if (True) {x = x * 3;while (x < 5) {x = x * 3}} else {do-nothing} {'x': 3}
x = x * 3;while (x < 5) {x = x * 3} {'x': 3}
x = 3 * 3;while (x < 5) {x = x * 3} {'x': 3}
x = 9;while (x < 5) {x = x * 3} {'x': 3}
do-nothing;while (x < 5) {x = x * 3} {'x': 9}
if (x < 5) {x = x * 3;while (x < 5) {x = x * 3}} else {do-nothing} {'x': 9}
if (9 < 5) {x = x * 3;while (x < 5) {x = x * 3}} else {do-nothing} {'x': 9}
if (False) {x = x * 3;while (x < 5) {x = x * 3}} else {do-nothing} {'x': 9}
do-nothing {'x': 9}
>>> env
{'x': 9}
应用
通过上面介绍的值表达式和语句表达式。Simple语义实现了编程语言的基本骨架。下面就使用 Simple 计算一个问题。
高斯问题: 从 1 到 100个数进行加和。我们可以使用 while 循环从 1-100 进行求值。
>>> env = {
... "sum": Number(100),
... "i": Number(1),
... "n": Number(101),
... }
>>> env
{'sum': 100, 'i': 1, 'n': 101}
>>> stmt.evaluate(env)
>>> stmt = While(
... cond= LessThan(
... left=Variable("i"),
... right=Variable("n"),
... ),
... body=Sequence(
... first=Assign("sum", Add(Variable("sum"), Variable("i"))),
... second=Assign("i", Add(Variable("i"), Number(1))),
... )
... )
>>> stmt
while (i < n) {sum = sum + i;i = i + 1}
>>> machine = Machine(stmt, env)
>>> machine.run()
... # ✌省略输出
>>> machine.env
{'sum': 5050, 'i': 101, 'n': 101}
大步语义
值表达式
小步语义展示了如何对抽象的语法树进行一步一步的规约,从而最终对整个表达式或语句求值。自底向上,这是一个迭代的过程。
大步语义则是自动向下,使用递归的方式进行表达式或语句求值。表达式(ExprMixin)的大步语义求值返回一个新的表达式。语句(StmtMixin)求值返回一个新的环境,毕竟语句会更新环境。
前文介绍了表达式和语句的代码,大步语义不需要 reducible属性和 reduce方法,只需要实现 evaluate 方法即可。下面是其代码,省略了 小步语义相关的代码
class Number(VarExprMixin, ExprMixin):
def __init__(self, val: int):
self.val = val
def evaluate(self, env: Dict) -> ExprMixin:
return self
class Boolean(VarExprMixin, ExprMixin):
def __init__(self, val: bool):
self.val = val
def evaluate(self, env: Dict) -> ExprMixin:
return self
class Add(ExprMixin):
def __init__(self, left: ExprMixin, right: ExprMixin):
self.left = left
self.right = right
def evaluate(self, env: Dict) -> ExprMixin:
return Number(self.left.evaluate(env).value + self.right.evaluate(env).value)
class Mul(ExprMixin):
def __init__(self, left: ExprMixin, right: ExprMixin):
self.left = left
self.right = right
def evaluate(self, env: Dict) -> ExprMixin:
return Number(self.left.evaluate(env).value * self.right.evaluate(env).value)
class LessThan(ExprMixin):
def __init__(self, left: ExprMixin, right: ExprMixin):
self.left = left
self.right = right
def evaluate(self, env: Dict) -> ExprMixin:
return Boolean(self.left.evaluate(env).value < self.right.evaluate(env).value)
class Variable(ExprMixin):
def __init__(self, name: str):
self.name = name
def evaluate(self, env: Dict) -> ExprMixin:
return env[self.name]
上述的表达式都实现了 evaluate 方法。其中值表达式的 evaluate 返回值本身。二元表达式依次是对其左右值 进行求值。小步语义需要通过虚拟机迭代依次规约求值。大步语义则不需要了。
值表达式
>>> expr = Number(1)
>>> expr
1
>>> expr.evaluate({})
1
>>> expr = Boolean(False)
>>> expr
False
>>> expr.evaluate({})
False
二元表达式
>>> expr = Add(
... Mul(Number(1), Number(2)),
... Mul(Number(3), Number(4)),
... )
>>> expr
1 * 2 + 3 * 4
>>> expr.evaluate({})
14
>>> expr = LessThan(Variable("x"), Number(1))
>>> expr
x < 1
>>> expr.evaluate({"x": Number(2)})
False
语句
语句的实现的是 StmtMixin接口的evaluate 方法。该方法不会返回表达式,而是返回一个环境。下面的代码同样省略了小步语义的规约方法。
class DoNothing(VarExprMixin, StmtMixin):
def evaluate(self, env: Dict) -> Dict:
return env
class Assign(StmtMixin):
def __init__(self, name: str, expr: ExprMixin):
self.name = name
self.expr = expr
def evaluate(self, env: Dict) -> Dict:
return env | {self.name: self.expr.evaluate(env)}
class If(StmtMixin):
def __init__(self, cond: ExprMixin, consequence: StmtMixin, alternative: StmtMixin):
self.cond = cond
self.consequence = consequence
self.alternative = alternative
def evaluate(self, env: Dict) -> Dict:
cond = self.cond.evaluate(env)
if cond == Boolean(True):
return self.consequence.evaluate(env)
else: # cond == Boolean(False)
return self.alternative.evaluate(env)
class Sequence(StmtMixin):
def __init__(self, first: StmtMixin, second: StmtMixin):
self.first = first
self.second = second
def evaluate(self, env: Dict) -> Dict:
return self.second.evaluate(self.first.evaluate(env))
class While(StmtMixin):
def __init__(self, cond: ExprMixin, body: StmtMixin):
self.cond = cond
self.body = body
def evaluate(self, env: Dict) -> Dict:
cond = self.cond.evaluate(env)
if cond == Boolean(True):
return self.evaluate(self.body.evaluate(env)) # 递归调用
else: # cond == Boolean(False)
return env
assign 赋值语句
>>> stmt = Assign("x", Add(Variable("x"), Number(1)))
>>> stmt
x = x + 1
>>> stmt.evaluate({"x": Number(2)})
{'x': 3}
if 条件语句
>>> stmt = If(
cond=Variable("x"),
consequence=Assign("y", Number(1)),
alternative=Assign("y", Number(2)),
... )
>>> stmt
if (x) {y = 1} else {y = 2}
>>> stmt.evaluate({"x": Boolean(True)})
{'x': True, 'y': 1}
序列组合语句
>>> stmt = Sequence(
... first=Assign("x", Add(Number(1), Number(1))),
... second=Assign("y", Add(Variable("x"), Number(1))),
... )
>>> stmt
x = 1 + 1;y = x + 1
>>> stmt.evaluate({})
{'x': 2, 'y': 3}
while 循环语句
>>> stmt = While(
cond=LessThan(Variable("x"), Number(5)),
body=Assign("x", Mul(Variable("x"), Number(3)))
... )
>>> stmt
while (x < 5) {x = x * 3}
>>> stmt.evaluate({"x": Number(1)})
{'x': 9}
最后依然是使用大步语义进行高斯问题求解
>>> env = {
... "sum": Number(100),
... "i": Number(1),
... "n": Number(101),
... }
>>> env
{'sum': 100, 'i': 1, 'n': 101}
>>> stmt.evaluate(env)
>>> stmt = While(
... cond= LessThan(
... left=Variable("i"),
... right=Variable("n"),
... ),
... body=Sequence(
... first=Assign("sum", Add(Variable("sum"), Variable("i"))),
... second=Assign("i", Add(Variable("i"), Number(1))),
... )
... )
>>> stmt.evaluate(env)
{'sum': 5150, 'i': 101, 'n': 101}
指称语义
指称语义确实是一种比操作语义更抽象的方法,因为它只是用一种语言替换另一种语言,而不是把一种语言转换成真实的行为。指称语义通常用来把程序转成数学化的对象,所以不出意料,可以用数学工具研究和控制它们
原书使用 ruby 构建的Simple语言,然后通过指称语义转换成 ruby代码执行。这里我们就转换成 Python代码执行。即 ExprMixin 和 StmtMixin 里声明的 to_python属性方法。
类似的Golang和Rust版本也实现了 to_python 方法。具体可以参考github版本。下面的代码演示,省略了 小步语义 和 大步语义 的代码。
class Number(VarExprMixin, ExprMixin):
def __init__(self, val: int):
self.val = val
@property
def to_python(self) -> str:
return f"lambda e: {self.val}"
class Boolean(VarExprMixin, ExprMixin):
def __init__(self, val: bool):
self.val = val
@property
def to_python(self) -> str:
return f"lambda e: {self.val}"
class Add(ExprMixin):
def __init__(self, left: ExprMixin, right: ExprMixin):
self.left = left
self.right = right
@property
def to_python(self) -> str:
return f"lambda e: ({self.left.to_python})(e) + ({self.right.to_python})(e)"
class Sub(ExprMixin):
def __init__(self, left: ExprMixin, right: ExprMixin):
self.left = left
self.right = right
@property
def to_python(self) -> str:
return f"lambda e: ({self.left.to_python})(e) - ({self.right.to_python})(e)"
class Mul(ExprMixin):
def __init__(self, left: ExprMixin, right: ExprMixin):
self.left = left
self.right = right
@property
def to_python(self) -> str:
return f"lambda e: ({self.left.to_python})(e) * ({self.right.to_python})(e)"
class LessThan(ExprMixin):
def __init__(self, left: ExprMixin, right: ExprMixin):
self.left = left
self.right = right
@property
def to_python(self) -> str:
return f"lambda e: ({self.left.to_python})(e) < ({self.right.to_python})(e)"
class Variable(ExprMixin):
def __init__(self, name: str):
self.name = name
@property
def to_python(self) -> str:
return f"lambda e: e['{self.name}']"
class DoNothing(VarExprMixin, StmtMixin):
@property
def to_python(self) -> str:
return f"lambda e: e"
class Assign(StmtMixin):
def __init__(self, name: str, expr: ExprMixin):
self.name = name
self.expr = expr
@property
def to_python(self) -> str:
return f'lambda e: e | {{"{self.name}": ({self.expr.to_python})(e)}}'
class If(StmtMixin):
def __init__(self, cond: ExprMixin, consequence: StmtMixin, alternative: StmtMixin):
self.cond = cond
self.consequence = consequence
self.alternative = alternative
@property
def to_python(self) -> str:
return f"lambda e: ({self.consequence.to_python})(e) if ({self.cond.to_python})(e) else ({self.alternative.to_python})(e)"
class Sequence(StmtMixin):
def __init__(self, first: StmtMixin, second: StmtMixin):
self.first = first
self.second = second
@property
def to_python(self):
return f"lambda e: ({self.second.to_python})(({self.first.to_python})(e))"
class While(StmtMixin):
def __init__(self, cond: ExprMixin, body: StmtMixin):
self.cond = cond
self.body = body
@property
def to_python(self) -> str:
return f"(lambda f: (lambda x: x(x))(lambda x: f(lambda *args: x(x)(*args))))(lambda wh: lambda e: e if ({self.cond.to_python})(e) is False else wh(({self.body.to_python})(e)))"
to_python 将 Simple 构建的抽象表达式,转换成 Python的 lambda 字串,通过 eval 可以转换成 python 代码。进而执行。相当于将 Simple 解释编译成 Python代码。对于 Golang和 Rust 版本也类似。
从代码也可以看出,to_python 代码逻辑与 evaluate 类似,都是自顶向下对局部表达式求值。
其中 python的 lambda 表达式不支持 while 语句,因此需要借助 Y-combinator 方式实现。由于python没有实现尾递归优化,如果Simple 表达式过于复杂,那么可能会触发python的最大递归深度。
下面使用指称语义解决高斯问题,更多的例子可以参考代码库的测试文件。
>>> stmt = While(
... cond= LessThan(
... left=Variable("i"),
... right=Variable("n"),
... ),
... body=Sequence(
... first=Assign("sum", Add(Variable("sum"), Variable("i"))),
... second=Assign("i", Add(Variable("i"), Number(1))),
... )
... )
>>> stmt
while (i < n) {sum = sum + i;i = i + 1}
>>> code = stmt.to_python
>>> code
'(lambda f: (lambda x: x(x))(lambda x: f(lambda *args: x(x)(*args))))(lambda wh: lambda e: e if (lambda e: (lambda e: e[\'i\'])(e) < (lambda e: e[\'n\'])(e))(e) is False else wh((lambda e: (lambda e: e | {"i": (lambda e: (lambda e: e[\'i\'])(e) + (lambda e: 1)(e))(e)})((lambda e: e | {"sum": (lambda e: (lambda e: e[\'sum\'])(e) + (lambda e: e[\'i\'])(e))(e)})(e)))(e)))'
>>> fn = eval(code)
>>> fn
<function <lambda>.<locals>.<lambda> at 0x10a7739d0>
>>> fn({'sum': 100, 'i': 1, 'n': 101})
{'sum': 5150, 'i': 101, 'n': 101}
语法解析
我们实现的 Simple 语义是一种抽象语法表达式。实际开发者更习惯书写字面语句 x = x + 1
而不是 Assign(Variable("x"), Add(Variable("x"), Number(1)))
。我们可以实现一个规则,然后使用程序,将 x = x + 1
转换成 Assign(Variable("x"), Add(Variable("x"), Number(1)))
。
python 生态的 lark 可以实现这一点。具体的代码文件可以参考parse
下面使用parse 解析字面语句,生成 simple 表达式,进而求解高斯问题
>>> source = """
sum = 0
i = 1
n = 101
while (i < n){
sum = sum + i
i = i + 1
}
"""
>>> simple = parse(source)
>>> simple
sum = 0;i = 1;n = 101;while (i < n) {sum = sum + i;i = i + 1}
>>> fn = eval(simple.to_python)({})
{'sum': 5050, 'i': 101, 'n': 101}
通过书写直观的字面量表达式,通过 parse 解析成 Simple 语言的抽象语法树。再通过小步语义,大步语义或者指称语义进行求值。
总结
通过设计一个 Simple 编程语言,对其抽象表达式进行操作语义或指称语义求值。从而计算程序的结果。其中抽象表达式解析有小步语义,大步语义和指称语义。理解这些语义,就能更好的理解程序和计算机的含义。
- 编程语言通过一个规范或者实现定义一些规则。程序就是使用其规则表达式或语句求值。
- 小步语义:自底向上,通过迭代的方式,一步步对规则进行规约
- 大步语义:自顶向下,通过递归方式直接对表达式求值
- 指称语义:转换成别的语义的一种方式。
求值规则:
- 值表达式:返回其自身
- 二元表达式:左值先求值,然后右值求值
- 变量:从环境中返回变量名match的表达式*
- Assign 赋值语句:对右值表达式求值,再把结果更新到环境中
- If 条件语句:对条件cond表达式求值,其结果为true则对 consequence 求值,否则对 alternative 求值,返回环境
- Sequence 序列语句:一次对序列first和second语句求值,返回环境
- While 循环语句:先对条件cond求值,如果返回true则对 body 语句进行递归求值,返回环境
版本说明:
- Python 版本基于Mixin的继承方式实现
- Golang 版本基于interface的组合方式实现
- Rust 充分利用 Enum 类型和 Trait 系统实现