- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
求值器完整实现代码我已经上传到了GitHub仓库: TinySCM ,感兴趣的童鞋可以前往查看。这里顺便强烈推荐UC Berkeley的同名课程 CS 61A .
在这个层次结构的最底层是对象语言。对象语言只涉及特定的域,而不涉及对象语言本身(比如它们的文法规则,或其中的其体句子)。如要涉及它们,则要有一种元语言。对于语言的两个层次这一经验,所有学习外国语的人都是很熟悉的。然后,就要有一种元元语言来讨论元语言,以此类推.
——侯世达《哥德尔、埃舍尔、巴赫:集异璧之大成》 。
到目前为止,我们探讨的都是通过过程抽象、数据抽象以及模块化等手段来控制系统的复杂性。为了阐释这些技术,我们一直使用的是同一种编程语言。然而,随着我们所面对的问题更复杂,我们必须经常转向新的语言,以便能够有效地表述自己的想法。 实际上,建立新语言是工程设计中控制复杂性的一种威力强大的工作策略,我们常常能通过采用一种新语言而处理复杂问题的能力 .
元语言抽象 就是建立新的语言。它在工程设计的所有分支中都扮演着重要的角色,在计算机程序设计领域更是特别重要。因为这个领域中,我们不仅可以设计新的语言,还可以通过构造求值器的方式实现这些语言。对某个程序设计语言的 求值器 (或者 解释器 )也是一个过程,在应用于这个语言的一个表达式时,它能够执行求值这个表达式所要求的动作.
把这一点看做是程序设计语言中最基本的思想一点也不过分:
求值器决定了一个程序设计语言中各种表达式的意义,而它本身也不过就是另一个程序.
事实上,我们几乎可以把任何程序看做是某个语言的求值器。比如在符号代数中,一个多项式系统包含着多项式的算数规则以及底层的实现,一个符号求导系统亦包含着函数求导的规则以及底层的实现(参见我之前的博客 《SICP:符号求导、集合表示和Huffman树(Python实现)》 )。从这样一种观点看, 处理大规模计算机系统的技术,与构造新的程序设计语言的技术有紧密的联系,而计算机科学中一个重要的思想就是如何去构造适当的描述语言 .
接下来我们将要讨论如何关于在一些语言的基础上构造新的语言。在这篇博客里,我们将用Python语言去构造一个Scheme语言的求值器。事实上求值器的实现语言无关紧要,我们也可以用Scheme语言去构造Scheme语言的求值器。用于被求值语言同样的语言写出来的求值器被称为 元循环 (metacircular).
从根本上说,元循环求值器也就是我们在博客 《SICP:赋值和局部状态(Python实现)》 中所描述求值的 环境模型 的一个实现形式。回忆一下,该模型包括两个部分:
这两条规则描述了求值过程的核心部分,也就是它的基本循环。 在这一循环中,表达式在环境中的求值被规约到过程对实参的应用,而这种应用又被规约到新的表达式在新的环境中的求值。如此下去,直至我们下降到符号(其值可以在环境中找到)或基本过程(它们可以直接应用) 。 如下图所示:
这一求值循环实际体现为求值器里的两个关键函数 scheme_eval() 和 scheme_apply() 的相互作用,我们在4.1.1节将描述他们.
求值器的实现依赖于一些定义了被求值表达式的 语法(syntax) (也即被求值表达式是以何种数据结构来表示的)的过程。我们仍将采用自顶向下的数据抽象技术,设法使求值器独立于语言的具体表示。例如,对于赋值表达式 (set! <var> <val>) ,我们用抽象的选取函数 assignment_variable() 和 assignment_value() 去访问赋值中相应的部分(不过这里需要注意,为了方便后面的类型分派操作,我们仍然假设可以用 first_expr() 和 rest_exprs() 分别去访问表达式的首元素和其余元素,算是对抽象性的一个小小的击穿吧)。表达式的具体表示将在4.1.2节里描述。在4.1.3里还会描述一些过程和环境的表示形式,如 Environment 类、 LambdaProcedure 类的定义及其对应的一些方法.
求值过程可以描述为两个函数 scheme_eval() 和 scheme_apply() 之间的相互作用(interplay).
scheme_eval() 的参数是一个表达式和一个环境。 scheme_eval() 对表达式进行分类以此展开后续的求值工作。正如我们上面所说,我们采用选取函数 first_expr() 和 rest_exprs() 分别去访问表达式的首元素和其余元素,并根据表达式的首元素(也即其标签)来完成表达式类型的判定。表达式的类型可做如下分类:
基本表达式:
scheme_eval()
直接返回这个表达式本身。 scheme_eval()
必须在环境中查找变量的值。 特殊形式(special forms):
if
表达式、变量的赋值/定义、引号( '
)表达式、lambda表达式等、我们的处理方式是将其分派给不同的名为 eval_×××()
的求值函数处理。如 if
表达式就分派给 eval_if()
函数来处理。 组合式(combinations):
scheme_eval()
必须先递归地求值组合式的运算符部分和运算对象部分。而后将这样得到的过程对象和参数送给 scheme_apply()
,由它去处理实际的过程应用。注意,如果运算符部分是宏(Macro),则不需要事先对其进行求值就直接将其应用,待应用完毕之后再进行求值。 下面是 scheme_eval() 的定义:
@primitive("eval", use_env=True)
def scheme_eval(expr, env, _=None):
# Evaluate self-evaluating expressions
if is_self_evaluating(expr):
return expr
# Evaluate variables
elif is_scheme_variable(expr):
return env.lookup_variable_value(expr)
# All valid non-atomic expressions are lists (combinations)
if not is_scheme_pair(expr):
raise SchemeError(
"Unknown expression type: {0}".format(repl_str(expr)))
first, rest = first_expr(expr), rest_exprs(expr)
# Evaluate special forms
if is_scheme_symbol(first) and first in SPECIAL_FORMS:
return SPECIAL_FORMS[first](rest, env)
# Evaluate an application
else:
operator = scheme_eval(first, env)
# Check if the operator is a macro
if isinstance(operator, MacroProcedure):
return scheme_eval(complete_apply(operator, rest, env), env)
operands = rest.map(lambda x: scheme_eval(x, env))
return scheme_apply(operator, operands, env)
@primitive("eval", use_env=True) 表示将其加入Scheme的基本过程中,过程名为 eval 。这里的特殊形式的类型分派采用了数据导向的方式(这种做法可以使用户更容易增加 scheme_eval() 能分辨的表达式类型,而又不必修改 scheme_eval() 的定义本身)。所有的 SPECIAL_FORMS 及其所分派的求值函数如下所示:
SPECIAL_FORMS = {
# Conditionals
"if": eval_if,
"cond": eval_cond,
"and": eval_and,
"or": eval_or,
# Sequencing
"begin": eval_begin,
"let": eval_let,
# Assignments
"set!": eval_assignment,
# Definitions
"define": eval_definition,
# Lambda expressions
"lambda": eval_lambda,
# Quoting
"quote": eval_quote,
"unquote": eval_unquote,
"quasiquote": eval_quasiquote,
# Dynamic scoping
"dlambda": eval_dlambda_form,
# Macro
"define-macro": eval_macro_definition,
# Stream
"delay": eval_delay,
"cons-stream": eval_cons_stream
}
注意这里的 dlambda 特殊形式定义的是采用动态作用域的过程,是原Scheme标准中没有的.
scheme_apply() 有两个参数,一个是过程,一个是该过程去应用的实参表。 scheme_apply() 将过程分为两类:基本过程(求值器内置)和复合过程(通过 define 或 lambda / dlambda 特殊形式来定义)。对于基本过程,直接调用该过程对象的 apply() 方法去应用实参,对于复合过程,则在新建环境后(该环境包含了形参绑定到实参的新帧),顺序地求值组成该过程体的那些表达式。下面是 scheme_apply() 的定义:
@primitive("apply", use_env=True)
def scheme_apply(procedure, arguments, env):
validate_procedure(procedure)
if is_primitive_procedure(procedure):
return procedure.apply(arguments, env)
elif is_compound_procedure(procedure):
new_env = procedure.make_call_frame(arguments, env)
# Note that `tail` is set to True when `eval_sequence()` is called here
return eval_sequence(procedure.body, new_env, tail=True)
else:
raise SchemeError(
"Unknown procedure type: {0}".format(repl_str(procedure)))
eval_if() 先在给定的环境中求值 if 表达式的谓词(predicate)部分,如果得到的结果为真, eval_if() 就去求值这个 if 的推论(consequent)部分,否则就求值其替代(alternative)部分。其中需要注意,对于 if 语句是肯定有推论部分的,但是我们后面会将 cond 语句的求值规约到对 if 语句的求值,因 cond 语句推论可能为空,故此处需要做出判断——若推论为空,则返回谓词的求值结果,否则照常对推论求值。此外,如果 if 语句没有替代部分,则返回 False .
def eval_if(expr, env, tail=True):
validate_form(expr, min=2, max=3)
if_pred = if_predicate(expr)
if_predicate_val = scheme_eval(if_pred, env)
# All values in Scheme are true except `false` object,
# that is why we need `is_scheme_true()`
if is_scheme_true(if_predicate_val):
# Note that for `if` caluse , it muse have consequnet,
# so `if_consequent` can not be None (although it can be `nil`).
# But for `cond` clause, `if_consequent` can be None.
if_conseq = if_consequent(expr)
if if_conseq is None:
return if_predicate_val
else:
return scheme_eval(if_conseq, env, tail=tail)
# Turn to alternative
elif len(expr) == 3:
return scheme_eval(if_alternative(expr), env, tail=tail)
# If there is no alternative, return False
else:
return False
从 eval_if() 对 is_scheme_true() 的使用中,我们可以看到被实现语言(Scheme)和实现所用的语言(Python)之间的联系。 if_predicate() 在被实现的语言里求值,产出这一语言里的一个值,而解释器的谓词 is_scheme_true() 实际上会将该值翻译为可由实现所用语言的 if 检测的值。这里因为在Scheme中,除了 False 之外的对象都应被判断为真,故我们实际上需要用Python将 is_scheme_true() 实现如下:
def is_scheme_true(val):
return val is not False
注意这里如果对象为真则返回该对象本身,而不是直接返回 True 。 至于 cond 表达式,可以做为派生表达式(derived expressions)由 if 表达式实现出来,而不必直接去实现.
def eval_cond(expr, env, tail=True):
return scheme_eval(cond_to_if(expr), env, tail)
最后,我们还需要实现 and 和 or 这两个布尔表达式:
def eval_and(exprs, env, tail=True):
# If there is no expression to be evaluated, return True
if exprs is nil:
return True
# If the last expression is reached (indicating that the values of the
# previous expressions are all true), then the evaluation result is
# returned directly
elif rest_exprs(exprs) is nil:
return scheme_eval(first_expr(exprs), env, tail=tail)
value = scheme_eval(first_expr(exprs), env)
# If an expression evaluates to False, return False,
# and the remaining expressions are not evaluated
if is_scheme_false(value):
return False
else:
# If an expression evaluates to True, go on,
return eval_and(rest_exprs(exprs), env)
def eval_or(exprs, env, tail=True):
# If there is no expression to be evaluated, return True
if exprs is nil:
return False
# If the last expression is reached (indicating that the values of the
# previous expressions are all False), then the evaluation result is
# returned directly
elif rest_exprs(exprs) is nil:
return scheme_eval(first_expr(exprs), env, tail=tail)
value = scheme_eval(first_expr(exprs), env)
# If an expression evaluates to True, return value, and the remaining
# expressions are not evaluated
if is_scheme_true(value):
return value
else:
return eval_or(rest_exprs(exprs), env)
如上所示, and 和 or 表达式都是短路(short-circuited)的。对于 and 表达式,如果其子表达式有求值为 False 的,则直接返回 False ;对于 or 表达式,如果其子表达式有求值为 True 的,直接返回 True .
eval_sequence() 用在 scheme_apply() 里,用于求值过程体里的表达式序列。它也用在 scheme_eval() 里,用于求值 begin 和 let 表达式内的表达式序列。这个过程以一个表达式序列和一个环境为参数,按照序列里表达式出现的顺序对它们进行求值,并返回最后一个表达式的值.
def eval_sequence(exprs, env, tail=False):
if not is_scheme_pair(exprs):
return
# If `exprs` is the last expression
if rest_exprs(exprs) is nil:
# The value of the last expression is returned as the value of the
# entire `begin` special form(or the body of a procedure)
return scheme_eval(first_expr(exprs), env, tail)
else:
# Evaluate the expressions <expr 1>, <expr 2>, ..., <expr k> in order
scheme_eval(first_expr(exprs), env)
return eval_sequence(rest_exprs(exprs), env, tail)
eval_begin() 和 eval_let() 亦依据 eval_sequence() 来定义:
def eval_begin(exprs, env, tail=True):
validate_form(exprs, min=1)
return eval_sequence(exprs, env, tail)
def eval_let(exprs, env, tail=True):
validate_form(exprs, min=2)
let_env = make_let_env(first_expr(exprs), env)
return eval_sequence(rest_exprs(exprs), let_env, tail=tail)
下面过程处理变量赋值,它先调用 scheme_eval() 求出需要赋的值,将变量和得到的值传给 env.set_variable_value() 方法,将有关的值安置到环境 env 里.
def eval_assignment(expr, env):
env.set_variable_value(assignment_variable(
expr), scheme_eval(assignment_value(expr), env))
变量定义也用类似方式处理:
def eval_definition(expr, env):
# Check that expressions is a list of length at least 2
validate_form(expr, min=2)
var = definition_varaible(expr)
val = definition_value(expr)
env.define_variable(var,
scheme_eval(val, env))
return var
对lambda表达式进行求值时(注意此时只是对lambda表达式的”定义“进行求值),我们会先分别获取lambda表达式的形参和过程体,并结合lambda表达式定义时的环境来初始化 LambdaProcedure 对象(也即默认采用的基于词法作用域规则).
def eval_lambda(expr, env):
validate_form(expr, min=2)
parameters = lambda_parameters(expr)
validate_parameters(parameters)
body = lambda_body(expr)
return LambdaProcedure(parameters, body, env)
而对于我们额外添加的采用动态作用域的dlambda表达式,在初始化时则不会用到其定义时的环境.
def eval_dlambda_form(expr, env):
validate_form(expr, min=2)
parameters = expr.first
validate_parameters(parameters)
return DLambdaProcedure(parameters, expr.rest)
对 quote 表达式(即 ' )进行求值时,直接返回其所引用的表达式本身即可(无需环境).
def eval_quote(expr, env):
validate_form(expr, min=1, max=1)
return text_of_quotation(expr)
对 quasiquote 表达式(即 ` )进行求值则是一个递归的过程,在获得了 ` 所引用的表达式后,我们需要扫描该表达式查看是否有被unquote(即以 , 开头)的子表达式,如果遇到了则对该子表达式求值,而其余表达式则保持不求值状态。比如 (1 2 ,(+ 3 4)) 的求值结果为 '(1 2 7) 。注意这里 quasiquote 内部可以嵌套多个 quasiquote 和 unquote ,故需要在递归时维护 depth 变量来表示被unquote操作“抵消”后剩余的 quasiquote 深度.
def eval_quasiquote(expr, env):
def quasiquote_item(val, env, depth):
"""Evaluate Scheme expression `val` that is nested at depth `level` in
a quasiquote form in frame `env`."""
if not is_scheme_pair(val):
return val
# When encountering `unquote`, we decrease the depth by 1.
# If the depth is 0, we evaluate the rest expressions.
if is_tagged_list(val, "unquote"):
depth -= 1
if depth == 0:
expr = rest_exprs(val)
validate_form(expr, 1, 1)
return scheme_eval(first_expr(expr), env)
elif val.first == "quasiquote":
# Leave the item unevaluated
depth += 1
# Recursively quasiquote the items of the list
return val.map(lambda elem: quasiquote_item(elem, env, depth))
validate_form(expr, min=1, max=1)
# Note that when call `quasiquote_item`, we have encountered
# the first quasiquote, so depth=1
return quasiquote_item(text_of_quotation(expr), env, depth=1)
注意unquote操作是不能够在 quasiquote 表达式之外进行的,如果在 quasiquote 之外对unquote进行求值,则会报对应的错误:
def eval_unquote(expr, env):
raise SchemeError("Unquote outside of quasiquote")
对宏的定义,也即 define-macro 表达式进行求值时,我们选择将表达式的形参、表达式体及环境都保存到 MacroProcedure 对象中,而不立即进行求值操作求值。此外,我们还在环境中添加宏的名称与 MacroProcedure 对象的绑定.
def eval_macro_definition(expr, env):
validate_form(expr, min=2)
target = expr.first
if is_scheme_pair(target) and is_scheme_symbol(target.first):
func_name = target.first
# `target.rest` is parameters,not `target.rest.first`
parameters = target.rest
body = expr.rest
# Just store the expression, rather than evaluate it
env.define_variable(func_name, MacroProcedure(parameters, body, env))
return func_name
else:
raise SchemeError("Invalid use of macro")
这个求值器很像我们在博客 《SICP:符号求导、集合表示和Huffman树(Python实现) 》 中所讨论的符号微分程序。这两个程序完成的都是一些对符号表达式的操作。在这两个程序中,对一个复合表达式的求值结果,也是由对表达式片段的递归操作来确定的,且对该结果的组合方式也是由表达式的类型来确定。在这两个程序中,我们都采用了数据抽象技术, 将一般性的操作规则与表达式的表示方式进行了解耦(decouple) 。在符号微分程序中,这意味着同一个微分程序可以处理前缀(prefix)形式、中缀(infix)形式或其他形式的代数表达式。 对于求值器,这意味着被求值语言的语法(syntax)仅仅由对表达式进行分类和片段提取的过程来决定 .
这里需要提一下,我们求值器的输入是以序对 Pair 组成的表,也即经过词法分析(lexical analysis)+语法分析(syntactic analysis)后所构建的抽象语法树(abstract syntax tree, AST)。比如对于 Scheme表达式 。
(+ (* 3 5) (- 10 6))
其对应的表为:
Pair('+', Pair(Pair('*', Pair(3, Pair(5, nil))), Pair(Pair('-', Pair(10, Pair(6, nil))), nil)))
事实上由于Scheme程序本身就可视作一棵AST,因此为Scheme程序写用于语法分析的Parser异常简单。我们可以通过 Pair 对象的 .first 属性访问其的第一个元素, .rest 属性访问其的后续元素.
下面是我们所要实现的Scheme语言的 语法规范 :
nil
(也即空表 '()
)、布尔值和 undefined
(在Python中表示为 None
):
def is_self_evaluating(expr):
return is_scheme_boolean(expr) or is_scheme_number(expr) or \
is_scheme_null(expr) or is_scheme_string(expr) or expr is None
def is_scheme_variable(x):
return is_scheme_symbol(x)
(quote <text-of-quotation>)
(求值器看到的表达式是以 quote
开头的表,即使这种表达式在输入时用的是一个引号):
def text_of_quotation(expr):
return expr.first
def is_unquote(expr):
return is_tagged_list(expr, "unquote")
注意这里对 text_of_quotation() 函数而言传入的 expr 是不含标签 quote 的,故直接使用 expr.first 来访问 <text-of-quotation> 即可.
is_unquote() 函数借助函数 is_tagged_list() 来定义,它可用于确定一个表的开始是不是某个给定符号:
def is_tagged_list(expr, tag):
if is_scheme_pair(expr):
return expr.first == tag
else:
return False
注意我们这里没有 is_quote() 函数,这是因为对 quote 的类型判断和其它特殊形式一样,直接在 scheme_eval() 的分派过程中解决了.
(set! <var> <value>)
:
def assignment_variable(expr):
return expr.first
def assignment_value(expr):
return expr.rest.first
同样,注意此处的 expr 不含标签.
(define <var> <value>)
或者 。
(define (<var> <parameter 1>... <parameter n>)
<body>)
后一形式(标准的过程定义)只是下面形式的一种语法包装:
(define <var>
(lambda (<parameter 1> ... <parameter n>)
<body>))
相应的语法函数是 。
def definition_varaible(expr):
target = expr.first
# For the case of (define <var> <value>)
if is_scheme_symbol(target):
# `(define x)` or `(define x 2 y 4)` is invalid
validate_form(expr, min=2, max=2)
return target
# For the case of (define (<var> <param 1>, ..., <param n>) <body>)
elif is_scheme_pair(target) and is_scheme_symbol(target.first):
return target.first
else:
bad_target = target.first if is_scheme_pair(target) else target
raise SchemeError("Non-symbol: {0}".format(bad_target))
def definition_value(expr):
target = expr.first
# For the case of (define <var> <value>)
if is_scheme_symbol(target):
return expr.rest.first
# For the case of (define (<var> <param 1>, ..., <param n>) <body>)
elif is_scheme_pair(target) and is_scheme_symbol(target.first):
# Note: The validation of the lambda special form is turned over
# to `scheme_eval()`
return make_lambda(target.rest, expr.rest)
else:
bad_target = target.first if is_scheme_pair(target) else target
raise SchemeError("Non-symbol: {0}".format(bad_target))
同样,注意此处的 expr 不含标签.
lambda
开始的表:
def lambda_parameters(expr):
return expr.first
def lambda_body(expr):
return expr.rest
同样,注意此处的 expr 不含标签。 我们还为 lambda 表达式提供了一个构造函数,它用在上面的 definition_value() 里.
def make_lambda(parameters, body):
return scheme_cons("lambda", scheme_cons(parameters, body))
if
开始,有一个谓词部分,一个推论部分和一个(可缺的)替代部分。如果这一表达式没有替代部分,我们就用 False
做为其替代。
def if_predicate(expr):
return expr.first
def if_consequent(expr):
return expr.rest.first
def if_alternative(expr):
return expr.rest.rest.first
我们也为 if 表达式提供了一个构造函数,它在 cond_to_if 中使用,用于将 cond 表达式变换为 if 表达式.
def make_if(predicate, consequent, alternative):
return scheme_list("if", predicate, consequent, alternative)
begin
包装起一个表达式序列。这里我们设计选择函数返回序列中的第一个表达式和其余表达式。
def first_expr(seq):
return seq.first
def rest_exprs(seq):
return seq.rest
我们还设计了一个构造函数 sequence_to_expr() (用在 cond_to_if 里),它把一个序列变换为一个表达式,如果需要的话就加上 begin 作为开头:
def sequence_to_expr(seq):
if seq is nil:
return seq
elif rest_exprs(seq) is nil:
return first_expr(seq)
else:
return make_begin(seq)
def make_begin(seq):
return scheme_cons("begin", seq)
let
表达式在规约到用 eval_sequence()
对表达式序列进行求值之前,需要新建环境。该新环境的构造函数下:
def make_let_env(bindings, env):
def bindings_items(bindings, env):
if bindings is nil:
return nil, nil
binding = bindings.first
validate_form(binding, min=2, max=2)
var = binding.first
val = scheme_eval(binding.rest.first, env)
vars, vals = bindings_items(bindings.rest, env)
return scheme_cons(var, vars), scheme_cons(val, vals)
if not is_scheme_list(bindings):
raise SchemeError("Bad bindings list in let form")
vars, vals = bindings_items(bindings, env)
validate_parameters(vars)
return env.extend_environment(vars, vals)
派生表达式 。
在一些语言中,一些特殊形式可以基于其他特殊形式的表达式定义出来,而不必直接去实现,比如 cond 表达式和 let 表达式。这样采用语法变换的方式实现的表达式称为 派生表达式(derived expressions) .
cond
表达式可以实现为一些嵌套的 if
表达式。例如,我们可以将对于下列表达式的求值问题:
(cond ((> x 0) x)
((= x 0) (display 'zero) 0)
(else (- x)))
规约为对下面涉及 if 和 begin 的表达式的求值问题:
(if (> x 0)
x
(if (= x 0)
(begin (display 'zero)
0)
(- x)))
采用这种方式实现对 cond 的求值能简化求值器。 下面展示了提取 cond 表达式中各个部分的语法过程,以及函数 cond_to_if() ,它能将 cond 表达式变换为 if 表达式。一个分情况分析以 cond 开始,并包含一个谓词-动作子句的表。如果一个子句的符号是 else ,那么就是一个 else 子句.
def cond_predicate(clause):
return clause.first
def cond_actions(clause):
return clause.rest
def cond_to_if(exprs):
return expand_clauses(exprs)
def expand_clauses(clauses):
# return None means that interpreter does not print anything
if clauses is nil:
return None
first = clauses.first
rest = clauses.rest
validate_form(first, min=1)
if cond_predicate(first) == "else":
if rest is nil:
return sequence_to_expr(first.rest)
else:
raise SchemeError(
"ELSE clause is not last: {0}".format(
repl_str(clauses)))
else:
if cond_actions(first) is nil: # for example, (cond ((= 1 1)))
# there is no consequent, we denote it as None
# o distinguish it from nil
if_consequent = None
else: # for example, (cond ((= 1 1) 2)) or (cond ((= 1 1) nil))
# there is a consequent, including nil
if_consequent = sequence_to_expr(first.rest)
return make_if(cond_predicate(first), if_consequent, cond_to_if(
rest))
除了需要定义表达式的外部语法之外,求值器的实现还必须定义好在其内部实际操作的数据结构,做为程序执行的一部分。例如序列数据结构,过程和环境的表示形式,真和假的表示方式等等。 序列数据结构 如我们在博客 《SICP: 层次性数据和闭包性质(Python实现)》 中所言,序列数据结构由序对来进行层次化定义,序对的定义如下:
class Pair:
def __init__(self, first, rest):
self.first = first
self.rest = rest
def __len__(self):
n, rest = 1, self.rest
while isinstance(rest, Pair):
n += 1
rest = rest.rest
# The tail of the list must be nil
if rest is not nil:
raise TypeError("length attempted on improper list")
return n
def __eq__(self, p):
if not isinstance(p, Pair):
return False
return self.first == p.first and self.rest == p.rest
def map(self, fn):
mapped = fn(self.first)
if self.rest is nil or isinstance(self.rest, Pair):
return Pair(mapped, self.rest.map(fn))
else:
raise TypeError("ill-formed list (cdr is a promise)")
def flatmap(self, fn):
from primitive_procs import scheme_append
mapped = fn(self.first)
if self.rest is nil or isinstance(self.rest, Pair):
return scheme_append(mapped, self.rest.flatmap(fn))
else:
raise TypeError("ill-formed list (cdr is a promise)")
此外,我们还需要一个空表类及其实例:
class nil:
def __len__(self):
return 0
def map(self, fn):
return self
def flatmap(self, fn):
return self
nil = nil()
注意, nil 实例将与其相同的类名进行了覆盖,且该空表类自始至终只有一个实例,所以我们可以直接使用 is nil 语法来测试某个对象是否为空表.
谓词检测 为了实现条件表达式,我们把除了 False 对象之外的所有东西都接受为真:
def is_scheme_true(val):
return val is not False
def is_scheme_false(val):
return val is False
除了谓词检测之外,还有许多诸如 is_scheme_pair() 、 is_scheme_list() 之类的Scheme内置数据结构检测函数,就不在此处进行一一列举了,大家可直接参考我项目目录下的 primitive_procs.py 文件.
过程的表示 过程分为基本过程(primitive procedures)和复合过程(compound procedures,也即由 define 语句或 lambda / dlambda 语句定义的过程)。我们先定义基本过程如下:
class Procedure:
class PrimitiveProcedure(Procedure):
import primitive_procs as pprocs
def __init__(self, fn, name="primitive", use_env=False):
self.name = name
self.fn = fn
self.use_env = use_env
def apply(self, arguments, env):
if not self.pprocs.is_scheme_list(arguments):
raise self.pprocs.SchemeError(
"Arguments are not in a list: {0}".format(arguments))
# Convert a Scheme list to a Python list
arguments_list = self.flatten(arguments)
try:
if self.use_env:
return self.fn(*arguments_list, env)
return self.fn(*arguments_list)
except TypeError:
raise self.pprocs.SchemeError(
"Incorrect number of arguments: {0}".format(self))
def flatten(self, arguments):
if arguments is nil:
return []
else:
return [arguments.first] + self.flatten(arguments.rest)
下列函数可检查过程是否为基本过程:
def is_primitive_procedure(procedure):
return isinstance(procedure, PrimitiveProcedure)
复合过程则包括形参 parameters 、过程体 body 和环境 body :
class LambdaProcedure(Procedure):
import primitive_procs as pprocs
def __init__(self, parameters, body, env):
assert isinstance(env, Environment), "env must be of type Environment"
self.pprocs.validate_type(parameters, self.pprocs.is_scheme_list,
0, "LambdaProcedure")
self.pprocs.validate_type(
body, self.pprocs.is_scheme_list, 1, "LambdaProcedure")
self.parameters = parameters
self.body = body
self.env = env
def make_call_frame(self, arguments, env):
return self.env.extend_environment(self.parameters, arguments)
复合过程的 make_call_frame() 方法用于将复合过程对象应用于实参时,向过程定义时的环境 self.env 中添加一个新帧(注意不是调用时环境 env ,调用时环境 env 并未使用),从而扩展得到一个新的环境。具体的 env.extend_environment() 方法我们会在环境的表示部分再讲述如何实现.
下列函数可检查过程是否为复合过程:
def is_compound_procedure(procedure):
return isinstance(procedure, Procedure)
使用动态作用域的dlambda过程对象定义如下:
class DLambdaProcedure(Procedure):
def __init__(self, parameters, body):
self.parameters = parameters
self.body = body
def make_call_frame(self, arguments, env):
return env.extend_environment(self.parameters, arguments)
该过程也是复合过程,唯一的区别是在过程应用时,建立的新环境是基于调用时环境 env 来扩展的,而不是定义时环境.
环境的表示 求值器还需要定义对环境的表示。正如我们在博客 《SICP:求值和环境模型(Python实现)》 中所说,一个环境就是一个帧的序列,每个帧都是一个包含绑定的表格,其中绑定关联起一些变量和与之对应的值.
我们首先定义好帧:
class Frame:
def __init__(self):
self.bindings = {}
def add_binding(self, var, val):
self.bindings[var] = val
def set_var(self, var, val):
self.add_binding(var, val)
然后在帧的基础之上定义好环境:
class Environment:
import primitive_procs as pprocs
def __init__(self):
self.frames = self.pprocs.scheme_list(Frame())
def lookup_variable_value(self, var):
...
def extend_environment(self, vars, vals):
...
def define_variable(self, var, val):
...
def set_variable_value(self, var, val):
...
和环境有关的方法 lookup_variable_value() 、 extend_environment() 、 define_variable() 、 set_variable_value() 我们在下面进行分别阐述.
lookup_variable_value()
方法返回符号 var
在环境里的绑定值,如果这一变量没有绑定就发出一个错误信号:
def lookup_variable_value(self, var):
def env_loop(frames):
# If cannot find the variable in the current environment
if self.pprocs.is_scheme_null(frames):
raise self.pprocs.SchemeError(
"Unbound variable: {0}".format(var))
frame = frames.first
if var in frame.bindings.keys():
return frame.bindings[var]
else:
return env_loop(frames.rest)
return env_loop(self.frames)
extend_environment()
方法返回一个新环境,这个环境里包含了一个新帧,其中所有位于表 vars
里的符号绑定到表 vals
里对应的元素,而该帧的外围环境是当前这个对象所对应的环境。
def extend_environment(self, vars, vals):
new_env = Environment()
if len(vars) == len(vals):
new_env.frames = self.pprocs.scheme_cons(
self.make_frame(vars, vals),
self.frames)
elif len(vars) < len(vals):
raise self.pprocs.SchemeError(
"Too many arguemtns supplied")
else:
raise self.pprocs.SchemeError(
"Too few arguemtns supplied")
return new_env
@ staticmethod
def make_frame(vars, vals):
frame = Frame()
while isinstance(vars, Pair):
var = vars.first
val = vals.first
frame.add_binding(var, val)
vars = vars.rest
vals = vals.rest
return frame
define_variable()
方法在当前对象所对应环境的第一个帧里加入一个新绑定,它关联起变量 var
和 val
。
def define_variable(self, var, val):
frame = self.frames.first
frame.add_binding(var, val)
set_variable_value()
方法修改变量 var
在当前对象所对应环境里的绑定,使得该变量现在绑定到值 val
。如果这一变量没有绑定就发出一个错误信号。
def set_variable_value(self, var, val):
def env_loop(frames):
# 空表
if self.pprocs.is_scheme_null(frames):
raise self.pprocs.SchemeError(
"Unbound variable: {0}".format(var))
frame = frames.first
if var in frame.bindings.keys():
frame.set_var(var, val)
return
else:
env_loop(frames.rest)
env_loop(self.frames)
不过需要注意的是,这里所描述的方法,只不过是表示环境的许多方法之一。由于前面采用了数据抽象的技术,而这已经将求值器的其它部分与这些表示细节隔离开,因此如果需要的话我们也完全可以修改环境的表示。在产品质量的Lisp系统里,求值器中环境操作的速度——特别是查找变量的速度——对系统的性能有着重要的影响。这里所描述的表示方式虽然在概念上非常简单,但其工作效率却很低,通常不会用在产品系统里。其低效的原因来自求值器为了找到一个给定变量的绑定,需要搜索许多个帧。这样一种方式称为 深约束 。避免这一低效性的方法是采用一种称为 语法作用域 的策略,可参考原书5.5.6节.
有了一个求值器,我们手头上就有了一个有关Lisp表达式如何求值的程序描述(这是用Python描述的)。接下来我们来看如何运行这个程序.
我们的求值器程序最终把表达式规约到基本过程的应用(基本过程的定义在项目代码中的 primitive_procs.py 文件中)。而每个基本过程名都必须有一个绑定,以便当 scheme_eval() 求值一个应用基本过程的运算符时,可以找到相应的对象,并调用这个对象的 apply 方法。为此我们必须创建起一个初始环境,在其中建立起基本过程的名字与一个唯一对象的关联,在求值表达式时的过程中可能遇到这些名字.
def setup_environment():
initial_env = Environment()
initial_env.define_variable("undefined", None)
add_primitives(initial_env, PRIMITIVE_PROCS)
return initial_env
这里的 PRIMITIVE_PROCS 是一个Python全局变量,具体而言是一个存储了基本过程函数及其名称的表。基本过程对象的具体表示方式我们已经在上文中提到,也就是 PrimitiveProcedure ,因此我们可以用下列函数将基本过程名称及其对象的绑定添加到全局环境中:
def add_primitives(env, funcs_and_names):
for fn, name, use_env in funcs_and_names:
env.define_variable(name, PrimitiveProcedure(
fn, name=name, use_env=use_env))
为了能够很方便地运行这个元循环求值器,我们使用函数 read_eval_print_loop() 来模拟Lisp中的读入-求值-打印循环。这个循环打印出一个 提示符(prompt) ,读入输入表达式,进行词法分析和语法分析转换为AST后,再在全局环境里求值这个表达式,而后打印出得到的结果.
def read_eval_print_loop(env, infile_lines=None, interactive=False,
quiet=False, startup=False, load_files=(),
report_errors=False, print_ast=False):
if startup:
for filename in load_files:
scheme_load(filename, True, env)
# Initialize a tokenizer instance
tokenizer = Tokenizer()
# Initialize a parser instance
parser = Parser()
while True:
try:
lines_stream = read_input(infile_lines, input_prompt="scm> ")
# Tokenize the input lines
lines_stream = (tokenizer.tokenize(line) for line in lines_stream)
# Parse a single expression / multiple expressions util all the
# tokens are consumed
while True:
# Parse a complete expression (single-line or multi-line) at a
# time
ast = parser.parse(lines_stream)
result = scheme_eval(ast, env)
if not quiet:
print(repl_str(result))
# If all the tokens read are consumed, then break
if parser.is_empty():
break
except (SchemeError, SyntaxError, ValueError, RuntimeError) as err:
...
def read_input(infile_lines, input_prompt):
if infile_lines: # If use a file stream as input
while infile_lines:
line = infile_lines.pop(0).strip("\n")
yield line
raise EOFError
else: # if use a keyboard stream as input
while True:
yield input(input_prompt)
# If a multi-line expression input is not
# terminated, use whitespace as the
# the input prompt to read more lines.
input_prompt = " " * len(input_prompt)
为了运行这个求值器,现在我们需要着的全部事情就是初始化这个全局环境,并启动上述的读入-求值-打印循环.
def main():
args = parse_args()
sys.path.insert(0, "")
interactive = True
load_files = []
if args.load:
for filename in args.filenames:
load_files.append(filename)
the_global_env = setup_environment()
read_eval_print_loop(env=the_global_env, startup=True,
interactive=interactive, load_files=load_files,
print_ast=args.ast)
下面是一个交互过程实例:
scm> (define (make-withdraw balance)
(lambda (amount)
(If (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds")))
make-withdraw
scm> (define W1 (make-withdraw 100))
w1
scm> (define W2 (make-withdraw 100))
w2
scm> (W1 50)
50
scm> (W2 70)
30
scm> (W2 40)
"Insufficient funds"
scm> (W1 40)
10
在思考求值Lisp表达式的Python程序时,有一个类比可能很有帮助。关于程序意义的一种操作式观点(operational view),就是将程序看做是一种抽象的(可能无穷大的)机器的一个描述。比如考虑下面的Lisp求阶乘程序:
(define (factorial n)
(if (= n 1)
1
(* (factorial (- n 1)) n)))
我们可以将这一程序看成一部机器的描述。这部机器包含的部分有递减(decrement)、乘法(multiply)以及相等测试(tests for equality),还有一个两位开关(即 if 分支)和另一部阶乘机器(这样,阶乘机器就是无穷的,因为其中包含着另一部阶乘机器)。下图是这部阶乘机器的流程图,说明了有关的部分如何连接在一起.
按照类似的方式,我们也可以把求值器看做是一部非常特殊的机器,它要求以一部机器的描述作为输入。给定了这个输入之后,求值器就能够规划自己的行为(configures itself)来模拟被描述的机器。举例来说,如果我们将 factorial 过程的定义送入求值器,求值器能够计算阶乘,如下图所示:
按照这一观点,我们的求值器可以被看做是是一种 通用机器(universal machine) 。它能够模拟其它的任何机器,只要它们已经被描述为Lisp程序。这是非常惊人的。比如我们为电子电路设想一种类似的求值器,这将会是一种电路,它以另一个电路(例如某个滤波器)的信号编码方案做为输入。在给了它这种输入之后,这一电路求值器能具有与这一描述所对应的滤波器同样的行为。这样的一个通用电子线路将会难以想象的复杂,而程序的求值器却是一个相当简单的程序.
事实上,任一求值器都能模拟其他的求值器。这样,有关“原则上说什么可以计算”(忽略掉有关所需时间和空间的实践性问题)的概念就与语言或计算机无关了,它反映的是一个有关 可计算性(computability) 的基本概念。这一思想第一次是由图灵以清晰的方式阐述,他在1936年的论文《论可计算数及其在判定性问题上的应用》 [2] 为理论计算机科学奠定了基础。在这篇论文里,图灵给出了一种简单的计算模型——现在被称为 图灵机 ——并声称,任何“有效过程”都可以描述为这种机器的一个程序(这一论断就是著名的 邱奇—图灵论题 )。图灵而后实现了一台通用机器,即一台图灵机,其行为就像是所有图灵机程序的求值器.
求值器的另一惊人方面,在于它就像是在我们的程序设计语言所操作的数据对象和这个程序设计语言本身之间的一座桥梁。现在设想这个用Python实现的求值程序正在运行,一个用户正在输入表达式并观察所得到的结果。从用户的观点看,他所输入的形如 (* x x) 的表达式是程序设计语言里的一个表达式,是求值器将要执行的东西。而从求值器的观点看,这一表达式不过是一个表(这里就是三个符号 * 、 x 和 x 的表),它所要做的也就是按照一套定义良好的规则去操作这个表.
这种用户程序即求值器的数据的情况,未必会成为混乱之源。事实上,有时简单地忽略这种差异,为用户提供显式地将数据对象当做Lisp表达式求值的能力,允许他们在程序里直接使用 eval ,甚至可能带来许多方便。在我们实现的这个求值器里,就可以直接使用 eval 过程:
scm> (eval '(* 5 5))
25
scm> (eval (cons '* (list 5 5)))
25
事实上,Python语言中也内置了 eval() 函数:
>>> eval("5 * 5")
25
最后此篇关于SICP:元循环求值器(Python实现)的文章就讲到这里了,如果你想了解更多关于SICP:元循环求值器(Python实现)的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我的问题:非常具体。我正在尝试想出解析以下文本的最简单方法: ^^domain=domain_value^^version=version_value^^account_type=account_ty
好吧,这就是我的困境: 我正在为 Reddit 子版 block 开发常见问题解答机器人。我在 bool 逻辑方面遇到了麻烦,需要一双更有经验的眼睛(这是我在 Python 中的第一次冒险)。现在,该
它首先遍历所有 y 值,然后遍历所有 x 值。我需要 X 和 y 同时改变。 For x = 3 To lr + 1 For y = 2 To lr anyl.Cells(x, 1)
假设我有一个包含 2 列的 Excel 表格:单元格 A1 到 A10 中的日期和 B1 到 B10 中的值。 我想对五月日期的所有值求和。我有3种可能性: {=SUM((MONTH(A1:A10)=
如何转换 Z-score来自 Z-distribution (standard normal distribution, Gaussian distribution)到 p-value ?我还没有找到
我正在重写一些 Javascript 代码以在 Excel VBA 中工作。由于在这个网站上搜索,我已经设法翻译了几乎所有的 Javascript 代码!但是,有些代码我无法准确理解它在做什么。这是一
我遇到过包含日期格式的时间戳日期的情况。然后我想构建一个图表,显示“点击”项目的数量“每天”, //array declaration $array1 = array("Date" => 0); $a
我是scala的新手! 我的问题是,是否有包含成员的案例类 myItem:Option[String] 当我构造类时,我需要将字符串内容包装在: Option("some string") 要么 So
我正在用 PHP 创建一个登录系统。我需要用户使用他或她的用户名或电子邮件或电话号码登录然后使用密码。因为我知道在 Java 中我们会像 email==user^ username == user 这
我在 C++ 项目上使用 sqlite,但是当我在具有文本值的列上使用 WHERE 时出现问题 我创建了一个 sqlite 数据库: CREATE TABLE User( id INTEGER
当构造函数是显式时,它不用于隐式转换。在给定的代码片段中,构造函数被标记为 explicit。那为什么在 foo obj1(10.25); 情况下它可以工作,而在 foo obj2=10.25; 情况
我知道这是一个主观问题,所以如果需要关闭它,我深表歉意,但我觉得它经常出现,让我想知道是否普遍偏爱一种形式而不是另一种形式。 显然,最好的答案是“重构代码,这样你就不需要测试是否存在错误”,但有时没有
这两个 jQuery 选择器有什么区别? 以下是来自 w3schools.com 的定义: [attribute~=value] 选择器选择带有特定属性,其值包含特定字符串。 [attribute*=
为什么我们需要CSS [attribute|=value] Selector根本当 CSS3 [attribute*=value] Selector基本上完成相同的事情,浏览器兼容性几乎相似?是否存在
我正在解决 regx 问题。我已经有一个像这样的 regx [0-9]*([.][0-9]{2})。这是 amont 格式验证。现在,通过此验证,我想包括不应提供 0 金额。比如 10 是有效的,但
我正在研究计算机科学 A 考试的样题,但无法弄清楚为什么以下问题的正确答案是正确的。 考虑以下方法。 public static void mystery(List nums) { for (
好的,我正在编写一个 Perl 程序,它有一个我收集的值的哈希值(完全在一个完全独立的程序中)并提供给这个 Perl 脚本。这个散列是 (string,string) 的散列。 我想通过 3 种方式对
我有一个表数据如下,来自不同的表。仅当第三列具有值“债务”并且第一列(日期)具有最大值时,我才想从第四列中获取最大值。最终值基于 MAX(DATE) 而不是 MAX(PRICE)。所以用简单的语言来说
我有一个奇怪的情况,只有错误状态保存到数据库中。当“状态”应该为 true 时,我的查询仍然执行 false。 我有具有此功能的 Controller public function change_a
我有一个交易表(针对所需列进行了简化): id client_id value 1 1 200 2 2 150 3 1
我是一名优秀的程序员,十分优秀!