SICP学习笔记(3.1)
周银辉
可以明显地感觉出来,从3.1节开始,作者将我们从面向过程的编程逐渐带入到面向对象的世界中来,这比C++直接用一个class关键字来得更为精彩。
我们先看一段纯粹的过程式的编程,假设我们要存储一个人的基本信息,比如Name和Age,那么我们的代码应该如何写呢?
Scheme版本
(define age 0)
(define (getName) name)
(define (setName newName)
(set! name newName))
(define (getAge) age)
(define (setAge newAge)
(set! age newAge))
(setName "ZhouYinhui")
(setAge 25)
(display (getName))
(display "'s Age is ")
(display (getAge))
哦~ 有一点似乎不太好,我们在使用name和age这样的全局变量哦,其他任何的地方也可以完全不通过上面的GetXXX SetXXX方法,也可以访问到它们。而我们希望仅能通过Get和Set方法来访问。
那么,将name和age拿到方法内部去吧!
在命令式语言中,准确地说是在不支持闭包的语言中,上面那句话可能将成为笑话:那是不可能的,每次方法调用时方法内的局部变量都是重新生成的。
为了简化代码,下面的代码都省略对name的操作,只用age进行示例。
恩,C++,Java不行,我们就看看JavaScript是如何办到的吧:
{
var age = 25;
var get = function getAge()
{
return age;
}
var set = function setAge(newAge)
{
age = newAge;
}
var d = function dispatch(flag)
{
if (flag == "get")
return get;
else
return set;
}
return d;
}
//这里不能成功
(F("set")(100));
alert( F("get")() );
//这里能成功
var a = F();
(a("set")(100));
alert( a ("get")() );
函数F是一个高阶函数,其返回的是另外一个函数d,d又是另外一个高阶函数,我们看到d将根据flag指示,返回函数get或函数set。在函数F中有一个局部变量age,由于age将被get和set使用,当执行离开F时,age并不会被立即销毁,age将和d,get,set一起形成一个闭包,被函数F返回。还记得闭包吗,可以到SICP2.2.1复习一下。这是关键点。为此,我们必须转换命令式编程语言的观点了:这里的F有Object性质,而不仅仅是一个Action,这个Object我们可以理解成返回的那个闭包。
再看看下面这段代码为啥不能达到我们的期望值(弹出的消息框显示的是25,我们希望是100)
(F("set")(100));alert( F("get")() );
既然我们说了F具有Object性质,那么我们假想F是Java中的一个class类型,那么上面的代码就形同:
(new F("set")(100));
alert( new F("get")() );
两句代码完全就是new的两个不同的对象,在前一个对象上 设置年龄,又重新new一个新对象来获取年龄,自然设置不会成功了。所以下面的代码就意识到了这一点:
var a = F();
(a("set")(100));
alert( a ("get")() );
再干一件有趣的事情:C#号称支持了Lambda,高阶函数等等,那么要将上面的代码改写成C#却并不容易:
{
var age = 25;
Func<int> get = () => age;
Action<int> set = (newAge) => age = newAge;
YYY d = (string flag) =>
{
if ("get".Equals(flag))
return get;
else
return set;
};
return d;
}
上面的XXX与YYY处应该如何编写呢,也就是说F的返回值类型和d的类型应该是什么 ?
回到Scheme吧!
(define (getAge)
age)
(define (setAge newAge)
(set! age newAge))
(define (getName)
name)
(define (dispatch method)
(cond ((eq? method 'getAge) (getAge))
((eq? method 'setAge) setAge)
((eq? method 'getName) (getName))
(else "invalid method")))
dispatch)
(define p1 (People 25 "ZhouYinhui"))
(p1 'getName)
((p1 'setAge) 100)
(p1 'getAge)
天啊,此时,我完全搞不清楚我应该将People看做是一个高阶函数还是应该看做一个面向对象的中类型。似乎我们已经踏入了面向对象的殿堂。age和name是类的私有字段,get set 则是类的私有函数,而那个dispatch完全就是一个函数分派器嘛。还记得“分派"吗,可以到这里复习一下。
搞清楚了上面的,练习题就是小case了:
练习3.1
(let ((acc ini))
(lambda (n)
(set! acc (+ acc n))
acc)))
(define F (makeAcc 5))
(F 10)
(F 10)
练习3.2
(let ((count 0))
(define (reset)
(set! count 0))
;notes that, must add () to times to make it as a method
;do NOT : (define times count)
(define (times)
count)
(define (dispatch arg)
(cond ((eq? arg 'reset) (reset))
((eq? arg 'times) (times))
(else
(begin
(f arg)
(set! count (+ 1 count))))))
dispatch))
(define m (makeMonitored sqrt))
(m 100)
(m 10000)
(m 'times)
练习3.3
(lambda (arg)
(begin (display msg)
(display ":")
(display arg)
(display "\r"))))
(define (makeAccount balance)
(define (withDraw amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds"))
(define (deposit amount)
(set! balance (+ balance amount)))
(define (dispatch pwd m)
(cond ((and (eq? pwd 'password) (eq? m 'withDraw)) withDraw)
((and (eq? pwd 'password) (eq? m 'deposit)) deposit)
(else (error "invalid method"))))
dispatch)
(define acc (makeAccount 1000))
((acc 'password 'withDraw) 123)
((acc 'password 'deposit) 1000)
((acc 'invalid-password 'withDraw) 245)
练习3.4略掉(与3.3类似)