计算的本质-程序的意义读书笔记

《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: 表示该表达式是否可以规约,像NumberBoolean 这样的值类型不能规约了。
  • reduce:表达式的规范方法,即表达式求值的逻辑,规约的方法返回还是表达式 ExprMixin
  • evaluate:大步语义中表达式求值的方法,求值返回的也是表达式 ExprMixin
  • to_python: 指称语义中表达式转换成python语义的方法,返回的是 python 代码字串

当前小步语义中,我们主要关注 reduciblereduce方法。下面实现两个基本的值类型

值表达式 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

NumberBoolean分表表示数字和布尔值。它们都不能规约了。在命令行中可以看到

>>> 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)。其规约方法如下:

  1. left 可以规约,则对left进行规约,并返回一个新的表达式
  2. right 可以规约,则再对right进行规约,同样返回一个新的表达式
  3. 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 接口, 它的规约方法很简单:

  1. 右值表达式如果可以规约,则进行规约,返回新的赋值表达式
  2. 右值不能规约,把当前的 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

规约的方法比较中规中矩:

  1. cond 表达式可以规约,则对其规约,并返回新的 if 表达式和环境。
  2. 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有两部分,其规约如下:

  1. first 可以规约,则对其进行规约,并返回新的 Sequence 语句
  2. 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

循环语句的规约使用了点小技巧

  1. 可以把循环看成是 If 语句中 else 为 DoNothing 进行规约
  2. 当执行 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 系统实现
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容