SICP学习笔记(1.1.6)

旧巷老猫 提交于 2020-01-29 04:16:40

                                    SICP学习笔记(1.1.6)
                                            周银辉

1, 语法糖衣

WIKIpedia上对于语法糖衣的解释是这样的:
In computer science, syntactic sugar in a language is syntax designed to make things easier to read or to express, while alternative ways of expressing them exist. It makes the language "sweeter" for humans to use: things can be expressed more clearly, or more concisely, or in an alternative style that some may prefer.

最早接触到“糖衣”一词是在MaoZedong(写汉字居然不能发布,说有违禁内容,难道园子也安装上LvBa了?)同志的某篇讲话上,所以听到这词总觉得有些贬义夹杂在其中并暗示自己要小心提防。而在计算机科学上这似乎要理解成一个中性词甚至褒义词了,因为我们的确每天都在“享用”着这些“糖衣”为我们带来的甜头,比如C#中的“属性”,甚至C#3.0中的lambda表达式。注意,这里将C#的某些高级语法和“语法糖衣”一词联系了起来,请不要试图反驳什么,我没有贬义,我也不是学院派(我不是总希望事情变得复杂,而仅仅想了解一些本质),正如Anders说的下面这段话:

”我认为,在很大程度上,我希望我们在C#或VB上做的工作至少是——在很大程度上对“函数型”编程进行“去神秘化”。学院式的人总是倾向 于让事情听起来比实际的要复杂,因为这样一来就显得他们自己比较聪明。“呃,我并不是针对任何人,但是,这里有不少符号代数和其他东西呢!(译者注:音频 提高了,似乎在学某些人的语气)”。当我向人们解释lambda表示式时,大家是另一种感觉:“你的意思是他们就是函数?这有什么新鲜的?我们早就有函数 了,lambda表达式只是些语法糖衣外壳吧?”是的,lambda表达式确实只是语法糖衣外壳,但这是种强有力的语法糖衣外壳,它使你能按照一种新的方 式去思考问题,以前总是存在的一些语法噪音——必须声明函数,进行委托,等等——就会逐渐减少,最终,一个全新层次的表达式列表会产生出来。这才是关键所 在!围绕着“函数型”编程还有非常多神秘,我希望我们能够(在打破神秘上)有所突破……“  (http://www.enet.com.cn/article/2007/0904/A20070904809454.shtml)

不仅仅是这些”高级玩意“,其实我们平时使用的普通语法也在玩一些语法糖衣,比如”while“,甚至是”and“,看看Scheme是如何定义”and“谓词的:
(define-syntax and
    (syntax-rules ()
        ((and) # t)
        ((and test) test)
        ((and test1 test2 ... )
         (if test1 (and test2 ... ) #f ))))
这在Scheme中称为“宏”,这个宏定义使用if,#t 和 #f  完成了对and运算的定义(#t和#f在Scheme中是表示True或False的常量)。在Scheme中像if,set!, lambda,调用,常量,变量等等这些表达式被称之为“基本表达式”,而通过这些基本表达式所定义出来的语法元素,比如case, and , or, let, do 等等称之为“扩展表达式”,我们可以理解成语言设计者们为我们提供的“语法糖衣”。同理,如果我们会使用这种编程方式以及了解关键字背后的工作原理的话,我们就可以定义自己的关键字了。事实上语言设计者们也是这么做的。
关于“宏”,可以参考这里”元编程的艺术“以及”Low- and high-level macro programming in Scheme“。


2, 练习1.1

答案略,标准答案可以通过上机测试得到(IDE推荐使用DrScheme: http://download.plt-scheme.org/ )

3, 练习1.2

将中缀表达式转换为前缀表达式,利用"SICP学习笔记(1.1.1~1.1.3)"中介绍的转换方法来得非常容易:
( / (+ 5 4 ( - 2 ( - 3 ( + 6 ( / 4 5 ) ) ) ) ) (* 3 ( - 6 2 ) ( - 2 7 ) )

4, 练习1.3

请定义一个过程,它将以三个数为参数,返回其中较大的两个数只和。
方法很多,仅供参考,在不考虑使用子过程和内置表达式的情况下,假设三个数分别为x,y,z:
方法一,x y z 三者之和减去最小数
(define (func x y z ) ( - ( + x y z ) ( if ( < x y ) x ( if ( < y z ) y z ))))
方法二,x 比 y z 都小吗? 如果是,那么返回 y 与 z 之和, 否则,返回 x 加上 y 与 z 中的较大者:
 ( define (func x y z ) ( if ( and ( < x y ) ( < x z ) ) ( + y z ) ( + x ( if  ( > y z ) y z ))))

5, 练习 1.4

根据对求值模型的认识,理解下面过程的行为:
(define ( a-plus-abs-b ) ( ( if ( > b 0 ) + - ) a b ) )

这种类型的题没有标准答案,以下是我的个人理解,仅供参考:
记得在"SICP学习笔记(1.1.4~1.1.5)"的 "求值与代换模型"中我在讲如何对"+"进行求值的时候,,曾提到"实际上它也是与某个过程关联起来的, 它是个过程名", 如果这句话的理解是正确的话, 那么对于 ( ( if ( > b 0 ) + - ) 这个"匿名过程"的返回值就是一个名为"+"或"-"的一个过程,我们知道"以过程作为参数或返回值的函数"我们称之为"高阶函数", 所以我感觉SICP的作者在这里似乎在暗示高阶函数的思想。

6, 练习 1.5

还是关于"应用序"和"正则序"求值的。
首先,我们看一下 ( define (p) (p) ) 这条语句, 很明显,这是一个无穷递归,其会导致"栈溢出" (暂且这么认为)
如果我们采用正则序的话,表达式会依次计算其子表达式,然后将子表达式的返回值作为新的操作符或操作新重新计算,直到计算出最后结果,所以:
      ( test 0 ( p ) )
=> (lambda ( x y ) ( if ( = x 0 ) 0 y ) 0 ( p ) ) ;  计算test的值,将test替换成对应的指令集
=> ( if ( = 0 0 ) 0 (p) ) ;; 将0 和 (p) 应用于lambda表达式
=> ( if #t 0 (p) ) ; 计算(= 0 0 )并将计算结果 #t 作为新的操作数
=> 0 ;; 计算 if 语句并返回结果0
如果我们采用应用序的话, 表达式会依次展开各个子表达式,待展开完毕后重新归约求值,所以:
      ( test 0 ( p ) )
=> (test 0  ( p ) ) ; 0不需要展开, 所以展开(p ), 但仍然得到(p )
=> (test 0  ( p ) ) ; 展开(p ), 但仍然得到(p )
=> (test 0  ( p ) ) ; 展开(p ), 但仍然得到(p )
=> .... ; 最后导致栈溢出 (暂且这么认为)

所以通过这种方式,我们可以判断一个解释器是按照正则序还是按照应用序工作的。

在Drscheme中,如果按照"begining student"的模式运行程序的话,会得到一个语法错误”define: expected at least one argument name after the function name, but found none“, 按照”advanced student“模式运行的话,会陷入死循环
为什么不是"栈溢出"呢?这是因为Scheme的解释器是"严格尾递归"的(tail recursion ), 尾递归调用时不会占用栈空间的,所以其不会导致栈溢出。关于"尾递归"留到1.2.1再讲吧。

注:这是一篇读书笔记,所以其中的内容仅属个人理解而不代表SICP的观点,并随着理解的深入其中的内容可能会被修改
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!