第十章 What Is the Value of All of This?
entry条目 是由list表组成的 pair 对,pair 对的第一个list表是集合 set。另外,两个list表的长度必须是相同的。举出几个 entry条目 的例子。
例如((appetizer entree beverage) (pate boeuf vin)和((appetizer entree beverage) (beer beer beer))
还有((beverage dessert) ((food is) (number on with us)))
我们如何用一个集合的名字和一个列表的值构建一个 entry条目
(define new-entry build)
试试用这个函数来构建前边的 entry条目 例子。
(lookup-in-entry name entry)的值是多少,其中 name 是entree
taste
那假设 name 是 dessert 呢?
这种情况下, lookup-in-entry 什么也不做。
我们如何把这个完成?
当在 entry条目 中没有找到 name , lookup-in-entry 调用另一个参数
你认为这个额外的函数需要多少个参数?
我们认为需要一个,name。为什么?
这里是我们对 lookup-in-entry 的定义。
(define lookup-in-entry
(lambda (name entry entry-f)
(lookup-in-entry-help name
(first entry)
(second entry)
entry-f)))
补全函数 lookup-in-entry-help
(define lookup-in-entry-help
(lambda (name names values entry-f)
(cond
(_____ _____)
(_____ _____)
(_____ _____))))
(define lookup-in-entry-help
(lambda (name names values entry-f)
(cond
((null? names) (entry-f name))
((eq? (car names) name) (car values))
(else (lookup-in-entry-help name (cdr names) (cdr values) entry-f)))))
笔记:entry-f 就是那个额外的函数来专门处理(second entry)为空表的情况。
table表格(也称作 environment环境)是entry条目为成员的表。空表为()。举出其它的例子。
(((appetizer entree beverage) (pate boeuf vin))
((beverage dessert) ((food is) (number one with us))))
定义函数 extend-table, 该函数输入一个 entry 和一个 table 作为参数,然后把新的 entry 放在旧的 table 表格前建立一个新的 table 表格。
(define extend-table cons)
(lookup-in-table name table table-f)是什么,其中 name 是 entree,table 是
(((entree dessert)
(spaghetti spumoni))
((appetizer entree beverage)
(food tastes good)))
table-f 是(lambda (name) ...)
可能是 spaghetti 或者 tastes, 但是 lookup-in-table 是按照表中的 entry 的顺序来查找的。所以是 spaghetti。
写出函数 lookup-in-table
提示:别忘了用一些帮助。
(define lookup-in-table
(lambda (name table table-f)
(cond
((null? table) (table-f name))
(else (lookup-in-entry name
(car table)
(lambda (name)
(lookup-in-table name (cdr table) table-f)))))))
你能描述一下,下面这个函数的作用吗?
(lambda (name)
(lookup-in-table name (cdr table) table-f))
当 name 在第一个 entry 中没有找到时的执行动作。
前面的preface中我们提到 sans serif 字体表示原子。这一点并不要紧。不用注意原子是否是 sans serif 字体了。
我们为 expression表达式选择了好的表示吗?
是的。它们都是 S-expression,所以它们都可以作为函数的数据。
什么样的函数?
比如 value。
还记得第六章的 value 吗?
对表达式调用 value 返回的是它的自然值。
(car (quote (a b c))) 的值是多少?
不知道。
(cons rep-a
(cons rep-b
(cons rep-c
(quote ()))))
的值是多少?其中 rep-a 是a,rep-b 是b,rep-c 是c
(a b c)
非常棒。那这个值呢?
(cons rep-car
(cons (cons rep-quote
(cons
(cons rep-a
(cons rep-b
(cons rep-c
(quote ()))))
(quote ())))
(quote ())))
其中
rep-car 是 car
rep-quote 是 quote
rep-a 是 a,rep-b 是b,rep-c 是c
它是表达式(car (quote (a b c)))的表示。
(car (quote (a b c))) 的值是多少?
a
(value e)的值是多少?其中e是(car (quote (a b c)))
a
(value e)的值是多少?其中e是(quote (car (quote (a b c))))
(car (quote (a b c)))
(value e)的值是多少?其中e是(add1 6)
7
(value e)的值是多少?其中e是(quote nothing)
nothing
(value e)的值是多少?其中e是 nothing
nothing没有value
(value e) 是什么,其中e是
((lambda (nothing)
(cons nothing (quote ())))
(quote (from nothing comes something)))
((from nothing comes something))
(value e) 是什么,其中e是
((lambda (nothing)
(cond
(nothing (quote something))
(else (quote nothing))))
#t)
something。
e的类型是什么,其中e是6
*const
e的类型是什么,其中e是#f
*const
(value e)的值是什么,其中e是#f
#f
e的类型是什么,其中e是cons
*const
(value e) 的值是什么,其中e是car
(primitive car)
e的类型是什么,其中e是(quote nothing)
*quote
e的类型是什么,其中e是nothing
*dentifier
e的类型是什么,其中e是(lambda (x y) (cons x y))
*lambda
e的类型是什么,其中e是
((lambda (nothing)
(cond
(nothing (quote something))
(else (quote nothing))))
#t)
*application
e的类型是什么,其中e是
(cond
(nothing (quote something))
(else (quote nothing)))
*cond
你认为有多少种类型?
我们找到六种:
*const
*quote
*identifier
*lambda
*cond
*application
你认为我们如何表示类型?
我们选择函数,它被称为“action”执行动作。
如果action都是那些应用到特定类型的表达式时会做该做的事情的函数,那么 value 应该是什么?
你猜到了。它必须找出传递给它的类型然后使用相应的执行动作。
记得第八章的 atom-to-function 吗?
当我们重写 value 时,我们需要 atom-to-function。
下面这个函数能够对每一个 S-expression 执行正确的过程。
(define expression-to-action
(lambda (e)
(cond
((atom? e) (atom-to-action e))
(else (list-to-action e)))))
写出函数 atom-to-action。
注:这里不考虑病态的(ill-formed) S-expression 表达式,例如(quote a b), (), (lambda (#t) #t), (lambda (5) 5), (lambda (car) car), (lambda a a), (cond (3 c) (else b) (6 a)), (1 2)等。它们可以在传递给value之前用一个合适的函数来侦测。
(define atom-to-action
(lambda (e)
(cond
((number? e) *const)
((eq? e #t) *const)
((eq? e (quote cons)) *const)
((eq? e (quote car)) *const)
((eq? e (quote cdr)) *const)
((eq? e (quote null?)) *const)
((eq? e (quote eq?)) *const)
((eq? e (quote atom?)) *const)
((eq? e (quote zero?)) *const)
((eq? e (quote add1)) *const)
((eq? e (quote sub1)) *const)
((eq? e (quote number?)) *const)
(else *identifier))))
现在定义函数 list-to-action
(define list-to-action
(lambda (e)
(cond
((atom? (car e))
(cond
((eq? (car e) (quote quote)) *quote)
((eq? (car e) (quote lambda)) *lambda)
((eq? (car e) (quote cond)) *cond)
(else *application)))
(else *application))))
假设 expression-to-action 执行正常,我们就可以定义 value 和 meaning
(define value
(lambda (e)
(meaning e (quote ()))))
(define meaning
(lambda (e table)
((expression-to-action e) e table)))
value 中的(quote ())是什么?
它是空表格。函数 value 以及它用的函数叫做解释器。
注:数 value 函数是个 Scheme(或者Lisp)中eval的近似。
Actions do speak louder than words.
action需要多少个参数?
两个,表达式 e和一个 table 表格
下面这个是constant的action执行动作。
(define *const
(lambda (e table)
(cond
((number? e) e)
((eq? e #t) #t)
((eq? e #f) #f)
(else (build (quote primitive) e)))))
对吗?
对,对于数,它直接返回表达式,对于#t,它返回true,对于#f它返回false
其它的constant类型的原子则代表 primitive。
下面是 *quote 的 action 执行动作
(define *quote
(lambda (e table)
(text-of e)))
定义辅助函数 text-of
(define text-of second)
我们用过table吗?
还没呢,不过一会就用到 table 了。
我们为甚么要用到 table?
存储 identifier 的 value。
假设 table 存储着 identifier 的 value,写出 *identifier 的执行动作。
(define *identifier
(lambda (e table)
(lookup-in-table e table initial-table)))
下面是 initial-table
(define initial-table
(lambda (name)
(car (quote ()))))
什么时候用它?
我们希望永远都不要用到。为什么?
笔记:lookup-in-table 的第三个参数是用来处理某个name对应是空表的情况,这时table是有问题的,table本来是给出identifier标识符的对应值的。没有给出identifier的值,于是直接抛出一个错误了,(car (quote))是违反primitive 的car操作定义的。
(lambda (x) x) 这样的表达式的 value 是什么?
我们不知道,但是我们知道它是一个 non-primitive 函数。
non-primitive 函数与 primitive有什么不同?
我们知道 primitive 做了什么; non-primitive 是由它们的参数和函数体构成。
所以当我们用一个 non-primitive, 我们需要存储它的参数和函数体。
至少,所幸的是,这只是一个 lambda 表达式的 cdr。
我们还需要存储什么?
我们还需要把 table 表格存储了,方便后边使用。
我们如何表示它?
当然是存储在 list 表中。
下面是 *lambda 的 action 执行动作。
(define *lambda
(lambda (e table)
(build (quote non-primitive)
(cons table (cdr e)))))
(meaning e table)是什么,其中e是(lambda (x) (cons x y)),table 是(((y z) ((8) 9)))。
(non-primitive ( (((y z) ((8) 9))) (x) (cons x y) ))
~~~~~~~~~~~ ```` ^^^^^^
table formals body
定义辅助函数来提取三个元素的list(即,table,formals,body)。写出 table-of formals-of 和 body-of
(define table-of first)
(define formals-of second)
(define body-of third)
请您用自己的话描述(cond ...)
cond行是特殊的格式。它一行行往下查找。如果问题部分为假,它往下查找。否则运行为真的部分。如果遇到了 else行,cond认为它为真,直接执行else部分。
下面这个 evcon 就是做我们刚才说的那些事情。
(define evcon
(lambda (lines table)
(cond
((else? (question-of (car lines)))
(meaning (answer-of (car lines)) table))
((meaning (question-of (car lines)) table)
(meaning (answer-of (car lines)) table))
(else (evcon (cdr lines) table)))))
(define else?
(lambda (x)
(cond
((atom? x) (eq? x (quote else)))
(else #f))))
(define question-of first)
(define answer-of second)
我们违反了第一戒律了吗?
是的,我们么有查询(null? lines), 所以 cond 中的问题得至少有一个为真。
现在用函数 evcon 来写一个 *cond 执行动作。
(define *cond
(lambda (e table)
(evcon (cond-lines-of e) table)))
(define cond-lines-of cdr)
辅助函数有用吗?
有用,它们使可读性提高了。你已经知道了。
你现在理解了 *cond 吗?
可能还没有。
你如何熟悉*cond?
最好的方式是来个例子。一个好例子:(*cond e table), 其中e是(cond (coffee klastsch) (else party)), table 是 (((coffee) (#t)) ((klastsch party) (5 (6))))
见过 table 是怎么用的吗?
见过啊, *lambda 和 *identifier 都用过。
但是 identifier 如何查找 table?
使用为一个我们还没有定义的 action: *application。
application 是如何表示的?
一个 application 就是一个 list,它的 car 是一个值是函数的 expression 表达式。
application 与(and ...) (or ...) (cond...) 等有什么不同之处。
一个 application 必须确定它的所有参数。
在我们执行一个函数之前,我们必须知道所有参数吗?
必须。
写一个函数 evlis ,它的参数是由一个用来表示参数的list和一个table构成,返回每一个参数的含义。
(define evlis
(lambda (args table)
(cond
((null? args) (quote ()))
(else
(cons (meaning (car args) table)
(evlis (cdr args) table))))))
在确定 application 的含义之前我们还需要什么?
我们需要知道它的 function-of 的含义。
下面是 *application
(define *application
(lambda (e table)
(apply
(meaning (function-of e) table)
(evlis (arguments-of e) table))))
对吗?
当然对了。我们只需要正确定义 apply, function-of, arguments-of。
写出 function-of 和 arguments-of
(define function-of car)
(define arguments-of cdr)
有多少个类型的函数?
两种:primitive 和 non-primitive。
这两种函数表示什么?
(primitive primitive-name) 和 (non-primitive (table formals body)), 表(table formals body)称为 closure record。
写出 primitive? 和 non-primitive?
(define primitive?
(lambda (l)
(eq? (first l) (quote primitive))))
(define non-primitive?
(lambda (l)
(eq? (first l) (quote non-primitive))))
现在我们可以写出函数 apply 了。
(define apply
(lambda (fun vals)
(cond
((primitive? fun)
(apply-primitive (second fun) vals))
((non-primitive? fun) (apply-closure (second fun) vals)))))
注:如果 fun 既不计算 primitive 也不计算 non-primitive, 例如表达式((lambda (x) (x 5)) 3), 那就没有答案。函数 apply 是Scheme (Lisp) 中apply的近似。
填空
(define apply-primitive
(lambda (name vals)
(cond
((eq? name _____)
(cons (first vals) (second vals)))
((eq? name (quote car))
(car (first vals)))
((eq? name (quote vals))
(_____ (first vals)))
((eq? name (quote null?))
(null? (first vals)))
((eq? name (quote eq?))
(_____ (first _____)))
((eq? name (quote atom?))
(_____ (first vals)))
((eq? name (quote zero?))
(zero? (first vals)))
((eq? name (quote add1))
(add1 (first vals)))
((eq? name (quote sub1))
(sub1 (first vals)))
((eq? name (quote number?))
(number? (first vals))))))
1.(quote cons)
2.cdr
3.eq?
4.(second vals)
5.:atom?
(define :atom?
(lambda (x)
(cond
((atom? x) #t)
((null? x) #t)
((eq? (car x) (quote primitive)) #t)
(else #f))))
注:函数 apply-primitive 能够查找 application 的 cdr 。The function apply·primitive could check for applications of cdr to the empty list or sub1 to 0, etc.
apply-closure 是唯一没有定义的函数了?
只剩它了, apply-closure 必须展开 table。
我们如何得到(f a b)的值,其中f是(lambda (x y) (cons x y)), a是1,b是(2)
这个很是麻烦。不过我们知道怎样做才能得到(cons x y)的含义。
我们为什么做这个?
这个不需要 apply-closure。
你能概括最后两个步骤吗?
对一个 value 的list表应用一个 non-primitive(一个closure)同下面这个做法是一致的:对closure的body和它的展开的table查找结果。其中table展开条目entry格式为(formals values)。 在条目 entry 中,formals 是 closure 的formals,values是 evlis的的结果。
所有这些你都遵守了吗?
如果没有,下面是 apply-closure 的定义。
(define apply-closure
(lambda (closure vals)
(meaning (body-of closure)
(extend-table
(new-entry (formals-of closure) vals) (table-of closure)))))
这是一个复杂的函数,得要一个例子。
以下内容假设:
closure是((((u v w) (1 2 3)) ((x y z) (4 5 6))) (x y) (cons z x)), vals 是((a b c) (d e f))
meaning 的新参数是什么?
meaning 新的参数e将是(cons z x),table将是
(((x y) ((a b c) (d e f))) ((u v w) (1 2 3) (4 5 6)))
(cons z x)的含义是什么,其中z是6,x是(a b c)
就是(meaning e table), 其中e是(cons z x),table是
(((x y) ((a b c) (d e f))) ((u v w) (1 2 3)) ((x y z) (4 5 6)))
让我们找出所有的参数的含义, (evlis args table), 其中args是
(((x y) ((a b c) (d e f))) ((u v w) (1 2 3)) ((x y z) (4 5 6)))
我们必须先确定(meaning e table), 其中e是z;(meaning e table)其中e是x。
(meaning e table)是什么,其中e是z
6, 用的 *identifier。
(meaning e table)是什么,其中e是x
(a b c), 用的 *identifier。
所以 evlis 的结果是什么?
(6 (a b c)), 因为evlis返回一个list的含义。
(meaning e table)是什么,e是cons
(primitive cons), 用的 *const。
我们现在可以 (apply fun vals)了,其中fun是 (primitive cons), vals是(6 (a b c)), 我们应该用哪个path路径?
apply-primitive path路径。
(apply-primitive name vals) 选择cond的哪个分支?其中name是cons,vals是(6 (a b c))
第三个:
((eq? name (quote cons))
(cons (first vals) (second vals)))。
我们完成了吗?
完成了。
但是(define ...)呢?
没有用到define,因为递归可以用Y combinator算子获得。
不需要定义(define ...)吗?
需要,不过需要看本书的后续 Seasoned Schemer。
意思就是如果我们用Y combinator 转换一下,就可以在解释器上运行解释器?
是的,but don't bother。
value有什么不同之处?
它查找 expression 表达式的表示。
will-stop?能查找 expression 的表示吗?
这个很有帮助。
有用吗?
No, don't bother——we can play the same game again. 我们可以定义一个像 last-try? 函数那样可以展现不能定义will-stop?函数的函数。
else
好了,该是宴会的时候了。
你已经到达了中场休息。你的选择呢?你可以马上接下册 The Seasoned Schemer, 或者你可以都一些我们下面提到的书。所有这些书都是经典,有一些比较老;然而它们都能经受的主时间的检验,值得你们去选择。有一些与数学,逻辑等等都无关,有些必须有数学基础,但仅仅是讲些有趣的故事,另外一些值得去发现。无需怀疑:这些书不是用来当作续集来读,就是给你娱乐休闲的。在 The Seasoned Schemer 的结尾你尅有找着一些 Scheme 和 Common Lisp 的书。不要感到到必须读续集,而是该花点时间下面的这些书。当你放松点了,消化了强加给你的卡路里,你再继续读下册。Enjoy!
Abbott, Edwin A. Flatland . Dover Publications, Inc., New York, 1952. (Original publication: Seeley and Co. , Ltd . , London, 1884.)
Carroll, Lewis. The Annotated Alice: Alice 's Adventures in Wonderland and Through the Looking Glass. Clarkson M. Potter, Inc., New York, 1960. Introduction and notes by Martin Gardner. Original publications under different titles:
, Macmillan and
Company, London 1865 and 1872, respectively.
Halmos, Paul R. Naive Set Theory. Litton Educational Publishers, New York, 1960.
Hein, Piet. Grooks . The MIT Press, Cambridge, Massachusetts, 1960.
Hofstadter, Douglas R. Godel, Escher, Bach: an Eternal Golden Bmid. Basic Books, Inc . , New York, 1979.
Nagel, Ernest and James R. Newman . Godel 's Proof. New York University Press, New York, 1958.
P6lya, Gyorgy. How to Solve It. Doubleday and Co. , New York, 1957.
Smullyan, Raymond. To Mock a Mockingbird And Other Logic Puzzles Including an Amazing Adventure in Combinatory Logic. Alfred A. Knopf, Inc., New York, 1985.
Suppes, Patrick. Introduction to Logic. Van Nostrand Co. , Princeton , New Jersey, 1957.
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 2.5 China Mainland License.