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类似)