标签:
《编程导论(Java)?第3章功能抽象》按照功能抽象的逻辑发展,介绍在Java语言环境中的三种流程、子程序和结构化分解、接口与实现相分离以及抽象方法——功能抽象的最高形式。而把操作/表达式是Java编程中最原始和起步级别的功能抽象。
SICP中,作为函数式编程语言的Scheme,它以表达式为基本单元,其功能抽象/函数抽象更为直接:按照丘奇的λ演算,完成对函数抽象的基本描述:
W是参数为变量x的λ表达式,则λx . W是λ表达式,如λx.( x+1)。这种表达式λx . W给出了一个函数的定义:W是函数体,参数就是变量x。
现在回顾《SICP?第一章构造过程抽象》。
Every powerful language has three mechanisms for accomplishing (organize our ideas about processes):
· Primitive expressions。基本表达式是语言设计者关心的要素,程序员只是被动地知道它们。如原子/基本类型、特殊块(特殊形式)define、if,前缀表达式,基本操作符等。
· means of combination。表达式定义中,组合表达式是基本要求。函数的组合则是函数式编程语言最典型的特征。数据类型的合成,也是编程语言的一个较重要的部分,只不过程序员自定义类型在C、Java中不那么引人注目
· means of abstraction, 抽象的方式——给组合实体一个名字并作为操作的单元。这里,我们可以给常量、组合表达式、函数命名。值得注意的是,数据和函数之间的界限,在Scheme中被模糊。
复合表达式递归地还原到基本表达式的求值。
l 数的值就是它们自己。如同Java中的文字。
l 内置操作符的值,是完成相应操作的及其指令序列。
l 其他名字的值,是环境中关联到该名字的对象。
l 每一种特殊块(特殊形式)都有其自己的求值规则。
换言之,任何东西都是有其值,函数式编程语言特别关心值的稳定性——避免副作用。
环境(全局环境),保存/确定名字和值的关联,或者说它用于确定表达式中各种符号的含义。有了f(x)=x+1,自然需要计算f(2),即给函数一个(实际)参数进行求值。按照丘奇的λ演算,函数应用:A、B是λ表达式,则 (A B) 也是λ表达式,表示将实参B带入函数A中。
虽然1.1.5介绍了“帮助我们领会函数执行过程的代换,但不是解释器实际的工作方式”。替换模型,不仅涉及解释器的工作细节,也说明“变化的数据”会导致什么问题。
(A B)表示将实参B带入函数A中,那么如何带入呢?
一种是将B求值后带入A,即‘‘evaluate the arguments and then apply‘‘,称为应用顺序求值(applicative-orderevaluation);
一种是将B带入A(有点像内联函数的方式),‘‘fullyexpand and then reduce‘‘,称为normal-order evaluation(不知道为什么翻译成“正则序求值”而不是正交)。
练习1.1 – 1.5
《编程导论(Java)?3.1.2 方法》讨论了功能分解或结构化分解(structured decomposition),《?3.1.3 接口与实现分离》介绍了Parnas原则。可能我写的更清楚或啰嗦?
形参必须是局部于方法体,嗯,平时没有注意其意义——是函数作为黑箱的前提。
约束变量=局部变量,不被约束的变量,自由变量或全局变量;
Scheme没有其他手段隐藏函数——如private,于是,在函数的内部定义函数——子函数局部化。
一个函数的内部,构成一个词法作用域。因此,其多个内部函数原来需要的形参x,可以作为词法作用域中的自由变量——类似Java中的成员变量的含义,而这些形参x被简化掉。这个问题有意思。在C的教学中,有些人画流程图作为编写代码的“前期”工作。这一节,SICP说明“能够看清楚函数产生的后果的能力,对于成为程序设计专家是至关重要的”。
什么意思呢?我虽然不喜欢也不推荐学生画流程图,但是通过伪代码或源代码,我们能够知道过程/函数的执行流程,甚至我认为:能够看清楚函数产生的后果的能力,是一个基本能力。
但是,如果你解释器或编译器进行了优化,我需要知道函数的执行流程吗?如果需要,这就不是了解执行流程的问题,而是“深入Java虚拟机”的问题。
而在Scheme中,这一点(知道函数的执行流程)比较重要:因为递归。
如果你对递归不熟悉,其实,那么任何语言中,你都需要学习它的执行流程;如果你对递归比较熟悉,那么你需要知道Scheme中强调这一点的目的:因为想把递归变成循环以提高效率——尾递归。
除了递归之外,“知道函数的执行流程”是显而易见的。
以阶乘为例。
递归代码如下。
(define (factorial n)
(if (= n 1)
1
(* n(factorial (- n 1)))))
其执行流程, SICP中给出了一个图。
但是其解释,我认为欠考虑。“Thesubstitution model reveals a shape of expansion followed by contraction,indicated by the arrow in figure x”,“递归计算过程”本质上就是“轻率地”认为自己的较简单的情形是已知的,因而通过“展开和收缩”方式计算,在Java中(我们不考虑懒惰计算(lazy evaluation)时),这一解释是自然的。但是Scheme中,这里“展开和收缩”或“延迟计算”是递归带来的吗?不是。正如练习1.5所说明的,是采用正则序的if带来的。
如果将正则序作为讨论的默认条件(下面的迭代也使用if),尾递归技术使得“递归函数”按照迭代/循环的方式执行。因此,Scheme就不需要for、while等语法糖。
(define (factorial n)
(fact-iter 1 1 n))
(define (fact-iter product counter max-count)
(if (> counter max-count)
product
(fact-iter (* counter product)
(+ counter 1)
max-count)))
这里,函数fact-iter是一个递归函数,但是计算过程中由几个状态变量控制。We callthis an iterative process.In general, an iterative process is one whose state can be summarized by afixed number of state variables, together with afixed rule that describes how the state variables should be updated as theprocess moves from state to state and an (optional) end test that specifiesconditions under which the process should terminate.当然,用for将state variables、rule of update和an (optional) end test集中起来,这个语法糖,挺好。
练习1.9 – 1.10
Fibonacci number:
(define (fib n)
(cond ((= n0) 0)
((= n1) 1)
(else(+ (fib (- n 1))
(fib (- n 2))))))
(练习1.9)尾递归的一个特点,被替代的函数,作为递归表达式的第一个符号。
这个代码解释了树形递归,但是从效率的角度,存在大量重复计算。如何设计尾递归代码?
其他,跳过。高阶函数见3.
太多数学题了。版权声明:本文为博主原创文章,未经博主允许不得转载。
标签:
原文地址:http://blog.csdn.net/yqj2065/article/details/46942199