我选择的是Lisp-Box.据说是很易用的傻瓜包.
CL和Scheme在思想上有着一致性,但是在一些实现细节上就不太一样了。同样是操作系统的概念,在实现层面上 之前去刷SICP的套题的时候,都是在Racket的交互式环境下进行编程,用的是默认的lang racket,其和scheme是近亲。两者区别的微妙之处我还不甚了解。除了基本define和defun等基本语法细节让我有点不习惯之外,对待函数的方式也不同。 虽然在lisp标准当中,函数过程可以被作为数据来对待,作为返回值,作为参数,同时你自己定义的函数也与第一类函数处于同等的地位,但是在scheme中,对于数值与函数的声明的绑定是处在同一个命名空间当中的,也就是scheme对于函数与数值的一致性是直接体现在语言实现中的,而在CL中,函数与数值的声明是分开在两个命名空间中的,也就是依据数值与函数在类型上的不同,你可以对数值与函数声明同一个名称。并且在调用的同时,环境会根据其在S-表达式中的地位,进行判断。e.g. v被声明绑定一个变量2,作为一个数值的同时,另外一个以v命名的函数在同时被调用时,(v v)将会被正常执行。而在函数中使用到作为函数的v时,则需要通过#'进行说明。(据说前者在编译器上的实现更为困难) LispBox是一套配好的东西,包括Emacs-23.2版本,SLIME(Lisp的交互式接口),SBCL的搭配。开发以及探索的过程都是在Emacs环境下的。 S-expression是FP里面最基本的也是(operator arg0[, arg1..])的形式。S表达式构建起了FP的核心语法规则。当然Fp还有许多强大的机制,例如宏以及动态编译等等。以后会讲到。 CL里面第二章主要介绍了list以及format两个最基本的操作. list后面可以跟着一堆的参数,默认将ta们组合成一个列表。列表也是Lisp中最基本以及最通用的数据结构,对于列表的处理有着一对通用的操作,也构成最早Lisp表处理的风格。CL中加入的对于结构体等等其他类型的数据结构的原生支持,相信也是建立在表基础上的。 在SICP中谈及的模块化设计(利用了强大的抽象屏蔽手段),就采用了以接受列表以及可变的lambda为参数的函数构造方式。之前在用java等oo编程语言的时候,总是觉得实现真正的模块化着实不容易(是我设计功夫不到家),不同模块之间的接口由于类型的限制或者处理需求的限制总是成为模块达到所谓重用或者通用性的障碍。 有的类的行为在语义以及职责上面不应被分在一起,但是他们却有着类似的过程模式。例如在一个web project中,分了几个模块,其中有两个模块,分别是数据源模块以及业务逻辑处理模块。两个模块在设计时是被分开的,然而对于数据源模块的设计当中,对于基本CRUD操作中retrieve的操作,总有个通用的过程, 初始化操作,数据连接初始化,或者是从相应的context中获取某个服务器在启动时刻为你管理好的数据源连接池,紧接着利用Connection获取statement构造retrieve行为的命令,最终交付数据源执行retrieve,将结果集返回,并且对结果集进行一个遍历,格式化读取,封装成对象,最终返回给逻辑层使用。这是数据源的那块。 而业务逻辑模块,会有从最基本的配置文件当中读取初始化信息,或者在程序运行过程中要update最新的模块信息,这个过程与上述的过程比较类似:初始化连接资源----可能是完成对于文件流的初始化,紧接着从文件中按照一定的规则读取配置信息,从其中retrieve出不同的初始化信息,作用于不同的初始化或者状态更新的对象,这在上面的例子中对应于封装数据在不同对象中----不同对象对于获取的各自的信息负责,并且开始各自的管理。 initialize >> set target source >> get stream; retrieve; enumerate resultset; encapsule them into objs; pass those objs to another process; another process may be another module which invokes the process above; or else manage objs initialized above and start them up; 看似在逻辑以及模块上分开的代码,其实在过程上具有相似性。也就是说,类似的过程可以通过通用的表示来进行管理。我们在发现冗余的过程中,普遍的编程语言通过数据结构的抽象让我们着眼于数据上的冗余的消除,其实在过程上冗余的例子也不少,只是我们没有专门地去关注复用的问题罢了。OO设计中的多态在某种意义上正是要通过类型的泛化(c#中还有委托),使得存在共性的不同对象的行为,得以在相同的流程框架中运行,这在OO中是通过接口的约定,实现一个抽象框架层面上的服用,而忽略具体实现行为,达到过程复用的目的的。 这是利用抽象屏蔽的思想,将程序的复杂性压缩到数据结构中(也就是通过泛化(利用接口编程)或者继承,unix那本圣经中有提到之),从而完成了对于过程的某种程度的复用。 而将注意力放在对于数据的抽象上,我们没法顾及到其他的一些冗余。比如上面介绍到的过程抽象。不少比较年轻的编程语言都加入了类似的特性,lambda表达式作为参数的传递,或者通过外部迭代器与闭包进行强大的松耦的关联(我只用过Ruby)。但是我想谈谈Lisp的。 在设计的过程中,我们总被鼓励从概念上去设计,这样概念可以独立于实现,我们关注于程序结构中变化较为稳定或者迟缓甚至不变的部分进行设计,而这一部分作为约定去知道具体实现,以及不同实现模块之间的交互,从而降低耦合。但是,我们把关注点转回内聚的方面,我们也强调内聚,我们强调类带有单个职责,类负责少部分的数据,利用各种信息隐藏的手段封装核心的数据,而类的行为要为核心的数据负责,尽量不将负责的行为分散到其他类中,也不去直接操作其他类中的核心数据----“管好自己”。 这样的设计出发点与上面的低耦合相结合,我们在寻找不同类类似行为的时候(通常是利用概念的泛化),经常是以类在概念意义上的关联为基础的。上面说到的例子中,两个在概念上不相干的模块,有着类似的过程抽象可以进行复用,在OO设计中我们就不好去复用,因为接口要求抽象在概念上有意义。 Lisp风格支持将过程作为抽象直接完成过程的复用,这给我们带来了去处冗余的更大的自由。很多人说,利用过程抽象会在另外一个层面建立起两个类不必要的耦合。事实上,那是在概念层次上的,我们遇到的变化总是在概念层次上的变化,比如一个系统需要移植到另外一个平台上,我们需要保证上层调用适配层接口的一致性,而忽略适配与具体平台之间的交互,复杂性都被压缩在适配层中了。而针对同样的数据结构,在一个合理的过程框架中去替换过程,是我们比较少去尝试的。而在lisp中提供了通用的表结构, 以及一系列的表的通用处理方式,lisp中的函数可以利用原语一样的这些表通用操作完成表在不同模块之间的专递,从而完成操作的统一化,设计也能真正实现模块化。(记得最基本的单位是cons,cons作为基本单元可以任意组合成我们常见的各种数据结构) 因为语言给了我们限制,语言带来思考的可能的同时,也在限制着我们的思维,自然语言是这样,人工语言更是这样。媒介决定着我们的思考,尼尔博斯曼还有麦克卢汉都谈过这个问题,这里不扯了。随便举个例子吧。 (read-source proc source-name &option);;返回source的列表抽象 (initialize proc source &option);;proc是初始化的过程抽象,返回流的列表抽象 (retrieve read-proc source statement &optional);;read-proc是对于,返回的是查 询结果的列表 encapsule可能是类似(mapcar (lambda (x)(具体的封装行为;这里还有封装行为进行 多态扩展的余地)) result-list) ;;这里是通过对于不同读取结果的封装行为得到的结果对象的列表 所以最终可以这样组织: (encapsule (encap-behavior) (retrieve (read-proc) (initizalize (init-proc) (read-resource (read-type) source-name)))) ;;其中几个以behavior以及proc为后缀的代表着不同的具体过程,比如encap- ;;behavior代表封装行为,encapsule会在遍历读取结果的列表中,对每个列表中的 ;;查询结果的元素进行封装操作,该操作用encap-behavior限定,每层处理一个表, ;;返回一个表,且由于是同等的数据结构,其实有点像过滤器,unix圣经中提到的 ;;理想的模块化在这里见识到了,甚至根据不同需要可以添加新的过滤器或者改变 ;;顺序 Encapsule最终返回封装之后的对象的列表,这个列表可以传递给别的其他的处理函数进行处理,可能是逻辑层的调用,可能是生命周期管理的模块,而这个模块不必在意,达到了一定的解耦的目的。内聚的例子下次再举。过程的通用性是以对于有类似或者相同行为的数据结构的通信为基础的。在这里给出了一个过程的框架,而过程框架中的一些具体的proc或者behavior可以根据需要进行变换,增强了设计上的灵活性,但是要注意,这些在一些其他语言中是被限定了的。 但是我在想,调用层次太深对于大量数据的处理是不是不太好,甚至在函数调用的问题上总是有人诟病lisp的递归,这个问题以后来讨论。 这里说的只是个人见解,我觉得lisp很高深,远不止我想到的这些,lisp世界中可能把我的这些想法都当成家常便饭了,或者觉得我谈的都是很浅的东西。但是我觉得把想法记录下来是为了日后回头的时候更好的去考察和思考。让各位FP高手见笑了。