需要lazy evaluation的一个例子

why functional programming matters里看来的例子,改成了Scheme版本。

占个位子。等SICP做到第四章,搞出LE,这个就可以解决了。

(define (within eps lst)
  (let ((a (car lst))
        (b (cadr lst)))
    (if (<= (abs (- a b)) eps)
        b
        (within eps (cdr lst)))))

(define (repeat f a)
  (cons a (repeat f (f a))))

(define (square-root guess eps n)
  (within esp (repeat
               (lambda (x) (/ (+ x (/ n x)) 2))
               guess)))

data-directed与message passing,及OO

SICP第二章提到了data-directed programming和message passing,及它们的关系。正好最近客户那边重构代码天天提data driven,而且message passing这个词一下让我想到了Smalltalk和OO,所以就把头绪理一下。

考虑一个非常常见的场景:你有两个不同的数据结构,它们其上各自有一组类似但不相同的操作。显然这些操作是针对于各自的具体数据类型的。现在的要求是:

  1. 提供一组通用操作作为这两个数据结构的外部接口。
  2. 要考虑将来加入新的具体数据类型对系统的影响。
  3. 要考虑将来加入新的操作对系统的影响。(感觉OO老是故意回避这个问题。事实上接口变动是很正常很频繁的。当然我设计模式学艺不精,请指正。)

 

按照OO和设计模式的思路,我们应该使用adapter设计模式。Adapter要实现某抽象数据类型,而这个抽象数据类型就是外部接口。client code仅使用此抽象数据类型,这样第一个要求就满足了。至于新加入的具体数据类型,大不了再加adapter就是,对现有代码无需做改动。不过加入新的操作就很麻烦了,每个具体数据类型都要改,抽象数据类型也要改。当然如果新加入的操作对每个具体类型都一样,可以放进抽象数据类型里。

在语言的实现方面,这需要是一个dynamic dispatch/binding的机制。Java和C++都采用了虚函数表,将对于抽象数据类型的操作在运行时映射到具体数据类型。Smalltalk和Python则采用了完全的动态也就是运行时查找,基本就是大查哈希表。

好吧现在我们剥掉OO那个外壳,回到Scheme的朴素思路。还是得有个抽象数据放在这儿当接口,在Scheme中没有object这个把数据和操作绑一起的东西,这个抽象数据其实就是一组通用操作。但现在语言不再自觉地替我们做这些dispatch,或者说将抽象映射到具体的工作了,我们得自己动手了。所以首先最弱智的我们可以用那一手generic operations with explicit dispatch:我们定义通用操作,然后在在其中显式的判断具体数据类型,再调用具体的操作。这一手确实把client code隔离开了,也就是满足了第一条,但显然每加一个具体数据类型都得在每个通用操作里加一个判断分支,没有满足第二条。这就是书中说的not additive。不过如果加一个操作的话还是挺方便的哈,老代码基本不用改。

第二手是用data-directed。其实质就是把那些判断也就是dispatch拿出来放一表或者说registry里。此表以同一具体数据类型的不同操作为列,以不同具体类型的相同操作为行(看SICP 181页或者自己画一个)。表中放的是某一具体数据类型的具体操作。通用操作不再充斥着写死的判断语句,而是接受操作名称作为参数,并从具体数据中获得类型信息,然后查表取得针对具体数据类型的操作并执行之。这样dispatch就成隐式的了,就是一个查表的过程。

所谓的data driven跟这个差不多就一个意思:把代码和数据严格分开,用数据驱动程序,新具体数据类型加进来的时候代码就不用改了。这样的代码就是additive的。详见ESR的Unix编程艺术和Wikipedia。

最后说message passing。它与generic operations with explicit dispatch是相对的。GOED是在operations里包含dispatch,其实质是,每一个operation其实就是data-directed里面的那个表里的一行,根据传入的数据类型自行解决dispatch问题。MP则是,把这个表分割成列,每个data type根据传入的operation自行解决dispatch问题。

在Scheme里表现出来就是,将数据表示为过程(带状态的过程,也就是闭包),其参数是operation。过程内部显式判断operation名称,再做或者调用具体操作。这其实已经是典型的OO特征了。显式判断operation名称就相当于调用实例方法啊!!!相比data-directed,显然是写死了。像Java这种OO语言呢里那些方法名不就是写死的么?当然动态语言除外。Python有个dict大家都知道。所以Scheme这里(object 'method)就相当于Java里object.method()啊!只不过这里dispatch是我们自己写的!而且这个dispatch不是动态的。

哈,object果然就是闭包!

然后SICP里为了提供Scheme风格的数据抽象,对MP也写了个apply-generic函数。据此我们可以再把那些通用操作写出来。比如

(define (real-part z) (apply-generic 'real-part z))

当新的具体数据类型加入时,我们需要写新的过程即可。但加入新操作就费劲了。注意MP和上面OO的局限多么的一致!其实它们的内在就是一样的!

基本思想是,如果我们把数据(具体数据类型)和代码(操作)视作正交,那么GOED和MP各自按住了一个,或者说基于其中一个做模块化针对另一个做dispatch所以遇到它们dispatch的那个东西要增加或者减少时,它们划分好的模块就被切了,怎么整都不爽。(想想那个表在按列切的时候多了一行,或按行切的时候多了一列,等于跨了已划分好的模块。)data-directed则是按照这两个维度做模块化,切得更细,需要两个坐标(具体数据类型,操作)才能找到目标。用这个思想看2.76那道题,一目了然。

私以为经验的重要性大致等同于知识,但二者皆低于思想。

关于JavaScript,肆 (vs Python and Scheme)

JS的对象模型无疑是简单而高效的。从某种意义上来看,简直就是以函数为核心。从面向对象的角度来讲,JS是prototype-based,Python是class-based,Scheme这方面我还未学到啦~我就知道Lisp有个CLOS,还没玩过。

1. 值有类型,变量没有(dynamic typing)。JS中的变量只是名字,这和Scheme一样。Python应该也是,我不是百分百确定。

class C():
pass

D = C
c = C()
C = 1
print C # 1
del C
print D # __main__.C, though C was already deleted from context.
print c # <__main__.C instance at 0x00B44940>
print c.__class__ # __main__.C

2. JS数据类型有numbers, strings, booleans, null, undefined, objects(arrays), functions。new运算符后面可以随便跟个函数,这会创建一个新对象。在此函数内部随便干什么都行,只是这时候this引用的是刚创建的这个对象。

function f() { this.a = 2 }
f.a = 1              // f的property
alert(f)
alert(f.a)           // 1
f = new f()          // 创建对象,覆盖名字f
alert(f)             // 新对象
alert(f.a)           // 执行f()时加上的property a, 值为2

被创建的对象(objects/arrays, functions)可以随意在运行时添加properties(其内部实现应该是hashtable)。如果一个property类型为函数,并且从该对象被调用,函数中的this就是此对象:

function f() { alert(this.a) }
o = Object()
o.a = 'a in the new object'
o.method = f
o.method()             // 'a in the new object'
f2 = o.method          // f2 is equal to f, just a plain function
f2()                   // "this" refers to window object now, so "a" is not defined

如果你在全局scope定义一个函数,这个函数可被视为global object的一个方法。所以在JS中function和method真的是没什么本质区别的。 

Python的数据类型见这里:http://docs.python.org/ref/types.html

下面这段代码很能说明Python中function和method还是有区别的:

class C():
""" A dummy boring class. """

C.g = lambda self: 'method'

# unbound method
print C.g
print C.g(C())

# bound method
c = C()
print c.g
print c.g()

# changed method, still bound
c.__class__.g = lambda self: 'changed method'
print c.g
print c.g()

# function
c.g = lambda : 'function'
print c.g
print c.g()

self-printing

((lambda (x) (list x (list 'quote x)))
 '(lambda (x) (list x (list 'quote x))))

我还没有百分之百的搞懂这个。需要对symbol的进一步认识和对Scheme解释器的进一步理解。

 

 

关于Scheme,贰

I just realized this morning, if you think 'bout it, the declaration and the invocation of the Scheme procedure actually achieve some kind of unified form, just like C, or any other common languages we've seen.

Declaration: 

(define (f x y z) some-thing-to-do)

Invocation:

(f 1 2 3)

Realized this, it's easier to remember the differences between defining a variable and defining a procedure. It's more than meets the eye!

关于Scheme,壹

(define (f1) f1)
(define (f2) (f2))

> (f1)
#<procedure:f1>

> (f2)
#[生命不息,死循环不止。PLT的小人儿跑亚跑]

呵呵,不急不急,先扎稳马步。

执行f1相当于

> f1
#<procedure:f1>

也就是eval一个variable,一个没啥内涵的procedure。

执行f2相当于执行f2,好吧,如果你懂什么叫递归,I服了you。

分页共1页 1