桃之夭夭
知天之所为,知人之所为者,至矣。知天之所为者,天而生也;知人之所为者,以其知之所知以养其知之所不知,终其天年而不中道夭者:是知之盛也。
BlogJava | 首页 | 发新随笔 | 发新文章 | 联系 | 聚合 | 管理

ANTLR Lexer Tips
     摘要: 参加ThoughtWorks University的一个来月没啥事情,闲了写写compiler玩。发现Lexer部分比较基础也比较常用,有很多相似的东西,每次都要写一遍也太麻烦了,下面是我按着JSL写的一个common java-like lexer,对于大多数接近java语法的语言估计是够用了。BTW:这个Lexer定义是TDD出来,以通过测试为要务,可能可读性不太强。1.WhiteSpaceC...  阅读全文
posted @ 2006-03-27 14:44 Raimundox 阅读(3068) | 评论 (4) | 编辑 收藏
 
An Issue of Chinese Characters Support in Ruby Watir
Watir doesn't work well with chinese characters. Try the following codes.

ie.text_field(:name, 'some text field).set('某某')

It will highlight the text field but put nothing in it. I read the Watir source codes, and found an interesting code segment:

1 for i in 0 .. value.length-1   
2    sleep @ieController.typingspeed   # typing speed
3    c = value[i,1]
4    #@ieController.log  " adding c.chr " + c  #.chr.to_s
5    @o.value = @o.value.to_s + c   #c.chr
6    fire_key_events
7 end

The above codes show how Watir simulates typing.If it doesn't work well with chinese characters, There must be something wrong with Ruby string. The first order of business is to figure out how Ruby string works for Chinese string.

1 chineseString = '某某'
2 puts chineseString.length
3 for i in 0..chineseString.length-1
4  puts chineseString[i, 1]
5 end

result will be:

4





Does Ruby, which is now capturing all java programmers' love, use 8bit char instead of unicode? Holy fuck!

I made a simple patch for the issue after I woke up from a short coma.

 1 require 'Watir'
 2 
 3 module Watir
 4   module Cn
 5     class IE <Watir::IE
 6       def text_field(how , what=nil)
 7         return TextField.new(self, how, what)
 8       end
 9     end
10 
11     class TextField < Watir::TextField
12       def doKeyPress( value )
13         begin
14           maxLength = @o.maxLength
15           if value.length > maxLength
16              value = suppliedValue[0 .. maxLength ]
17              @ieController.log " Supplied string is #{suppliedValue.length} chars, which exceeds the max length (#{maxLength}) of the field. Using value: #{value}"
18           end
19         rescue
20           # probably a text area - so it doesnt have a max Length
21           maxLength = -1
22         end
23                         
24         Cn.characters_in(value) {|c| 
25            sleep @ieController.typingspeed
26            @o.value = @o.value.to_s + c
27            fire_key_events}
28        end
29      end
30  
31      def Cn.characters_in(value) 
32        index = 0
33        while index < value.length 
34          len = value[index] > 128 ? 2 : 1
35          yield value[index, len]
36          index += len
37        end 
38      end
39    end
40 end

I submitted this patch to Watir tracing systsem,and zipped codes could be found here:
http://rubyforge.org/tracker/index.php?func=detail&aid=3232&group_id=104&atid=489
posted @ 2006-01-12 10:26 Raimundox 阅读(4047) | 评论 (2) | 编辑 收藏
 
A Patch for Ruby Watir Test Framework
昨天发现了Ruby Watir里的一个小问题,没有办法在Text Field里输入中文。虽然已经hack了,但是还是不太爽,G.H.Hardy说:

Beauty is the first test: there is no permanent place in this world for ugly mathematics.

感动了我好久。现在换个说法:

Beauty is the first test: there is no permanent place in this world for ugly hack code.

这个问题也不太难出理,ruby作为C的front interface在字符串处理上有很深的C的痕迹,嗯,10年前我还是个C程序员嘛,按照从前的做法区分ASCII码。

1 def characters_in(value) 
2   index = 0
3   while index < value.length 
4     len = value[index] > 128 ? 2 : 1
5     yield value[index, len]
6     index += len
7   end 
8 end

把TextField里的doKeyPress改一下:

1 characters_in(value) {|c| 
2   sleep @ieController.typingspeed
3   @o.value = @o.value.to_s + c
4   fire_key_events}

搞定!但是还是很丑,直接把别人的code改了,contributing to eclipse里说要contribute不要随便change别人的代码。好吧,好在ruby扩展起来也不难:

 1 require 'Watir'
 2 
 3 module Watir
 4   module Cn
 5     class IE <Watir::IE
 6       def text_field(how , what=nil)
 7         return TextField.new(self, how, what)
 8       end
 9     end
10     
11     class TextField < Watir::TextField
12       def doKeyPress( value )
13         begin
14           maxLength = @o.maxLength
15           if value.length > maxLength
16             value = suppliedValue[0 .. maxLength ]
17             @ieController.log " Supplied string is #{suppliedValue.length} chars, which exceeds the max length (#{maxLength}) of the field. Using value: #{value}"
18           end
19         rescue
20           # probably a text area - so it doesnt have a max Length
21           maxLength = -1
22          end
23                        
24         Cn.characters_in(value) {|c| 
25           sleep @ieController.typingspeed
26           @o.value = @o.value.to_s + c
27           fire_key_events}
28       end
29     end
30     
31     def Cn.characters_in(value) 
32       index = 0
33       while index < value.length 
34         len = value[index] > 128 ? 2 : 1
35         yield value[index, len]
36         index += len
37       end 
38     end
39   end
40 end

测试一下:

require 'watir-cn'

ie 
= Watir::Cn::IE.start('http://www.google.com')
ie.text_field(:name, 
'q').set('Ruby Watir 功能测试'

成功。最后一步是贡献社区,直接登到rubyforge,找到Watir然后submit了两个patch:一个直接修改watir库的一个是独立的watir-cn的。推荐大家使用第二个的patch。地址在:http://rubyforge.org/tracker/index.php?func=detail&aid=3232&group_id=104&atid=489
posted @ 2006-01-11 11:30 Raimundox 阅读(2920) | 评论 (4) | 编辑 收藏
 
A Day of Two Pragmatic Programmers' Life
今天我和WPC被迫派给了一个很nonsense的活,客户给了我们两份名单,让我们对照我们SSO中的用户数据做一下数据维护,如果有的用户在SSO中没有就加进来;要是SSO中有,看一下OA里有没有,如果OA里没有写一个列表让OA的同志们去维护数据。本来是一个很枯燥的活,好在WPC和我都是pragmatic programers,于是生活变得有乐趣多了。
解决这个问题最直接的做法,就是login到SSO平台上,然后一个用户一个用户的search,search完了再用OA Admin登陆查OA帐户。我们是pragmatic programmer嘛,这么繁琐的活动自然写程序搞定它。自然浮现两个选择:Ruby Watri,还有就是产自俺们公司的Selenium Script。
上来先用Ruby Watri,这个东西好啊,简单啊。WPC找了一个以前写的example, 我照着改了一个用户的search,然后扩展:
 1 # login in as sso admin
 2 ie = Watir::IE.start(sso_login_url)
 3 ie.text_field(:name,"username").set(sso_admin_user)
 4 ie.text_field(:name,"password").set(sso_admin_pass)
 5 ie.button(:value, "登录").click
 6 
 7 # search user
 8 ie = Watir::IE.start(sso_search_url)
 9 ie.text_field(:name, "userName).set('张三')
10 ie.button(:value, "查找").click

跑到command line run一把,ruby login.rb,然后一个古怪的事情出现了
ie.text_field(:name, "userName).set('张三')
userName输入框highlight了一下,然后没有字...难道是编码问题?换了encoding重新save了一把,结果一样。郁闷...于是我和WPC想法是...Ruby中文有问题,不管了时间紧迫,换Selenium Test,自家的东西嘛。但是Selenium的Script是HTML-based的,写起来太麻烦。我们是pragmatic programmer嘛,这么繁琐的活动自然写程序搞定它。于是我来搞一个ScriptGenerator,WPC同志搞script template。一搞template WPC同志就比较兴奋,大喊:velocity! velocity!哎,我机器上没有velocity的library,于是我决定pragmatic一把,直接writer output。找了一个Selenume Script Demo,在每行前面加上aaaa,每行末尾加上bbb,然后ctrl + f,aaa->writer.write(" bbb->"); 改几个",introduce parameter, extract method, compose method飞快地重构之,一个hard code的generator引擎诞生了。WPC还在调template,我看了一下代码,蛮ugly的,refactory之:
 1     private static String scriptTemplate;
 2 
 3     public static void readScriptTemplate(String templateName) {
 4         BufferedReader reader = null;
 5         try {
 6             reader = new BufferedReader(new FileReader(templateName));
 7             String line = null;
 8             StringBuffer template = new StringBuffer();
 9             while ((line = reader.readLine()) != null)
10                 template.append(line).append("\n");
11             scriptTemplate = template.toString();
12         } catch (IOException e) {
13 
14         } finally {
15             if (reader != null)
16                 try {
17                     reader.close();
18                 } catch (IOException e) {
19                 }
20         }
21     }
22 
23     public static void generatedScriptForUser(String path, String name) {
24         Writer writer = null;
25         try {
26             writer = new BufferedWriter(new FileWriter(path + "/" + name
27                     + ".html"));
28             writer.write(scriptTemplate
29                     .replaceAll("\\$\\$userName\\$\\$", name));
30         } catch (IOException e) {
31             e.printStackTrace();
32         } finally {
33             if (writer != null)
34                 try {
35                     writer.close();
36                 } catch (IOException e) {
37                 }
38         }
39 
40     }

一下子少了无数代码,爽多了。然后wpc也搞好了template,按模版文件一generating,几十个selenium test script就出现了。嗯,write program that write program,有够pragmatic。

写了一个Test Suite,放到改一下Selenium Runner下跑一下又傻眼了。Selenium做的Functional Test,一般假定和被测的应用在一个URL base里,因此这样local“测”remoting就不太好,而且我们又是一个安全平台,URL base做安全基准的。一下就所有测试就crackdown在这里了。郁闷啊...
Selenium文档,发现可以用driver来adpater local和remoting的环境,问题是这个drvier要自己写...郁闷...
WPC在firefox上装了一个Selenium Recorder的plug in可以记录web page actions,然后replay。他发现这个东西能绕过Selenium的限制,于是决定看看他怎么实现的,找到code一看...原来是把selenium runner hack了...用javascript把url base生生的给改了...WPC说好啊,我们写一个Firefox Selenium Recorder Plugin的plug in吧,让他从一个目录里自动load script...然后WPC开始玩firefox plugin.

我决得我还是看看Ruby的中文支持吧,找来找去都没有说Ruby的中文有问题的,后来发现一个老大测了一下Ruby的中文字符串,说没问题。我忘了这个老大的URL了找到再补上。代码上看起来似乎也没什么问题。于是我怀疑是Watri的问题,看Watri的代码,发现Watri的设计思路就是为了模拟人的录入,然后找到这样的代码:
def doKeyPress(value)



for i in 0 .. value.length-1   
   sleep @ieController.typingspeed   # typing speed
   c 
= value[i,1]
   #@ieController.log  
" adding c.chr " + c  #.chr.to_s
   @o.value 
= @o.value.to_s + c   #c.chr
   fire_key_events
end



end
根据设定的延时模拟人敲击键盘。每一个间隔用String slice来输入。于是我试验了一下ruby的中文字符串切片:

1 value = "哈哈"
2 for i in 0..value.length-1 
3   puts value[i,1]
4 end
Ruby果然瞎菜了...value.length是4,每一个切片都是空...啊~~~~天啊,8bit char...C的时代啊。找到了问题就好办了,我权衡了fix watri unicode和直接hack它,最后我选择直接hack它,方法简单:

 1 if @ieController.typingspeed != -1            
 2   for i in 0 .. value.length-1   
 3   sleep @ieController.typingspeed   # typing speed
 4   c = value[i,1]
 5   #@ieController.log  " adding c.chr " + c  #.chr.to_s
 6   @o.value = @o.value.to_s + c   #c.chr
 7   fire_key_events
 8   end
 9 else
10   @o.value = value
11   fire_key_events
12 end

然后测试一下:
1 require 'Watir'
2 
3 ie = Watir::IE.start("http://www.google.com")
4 ie.typingspeed = -1
5 ie.text_field(:name, "q").set("哈哈")

搞定。于是准备改Ruby脚本,这个时候客户下班了,我们决定明天继续,一共用时2小时...

最后说一下需求,一共有多少数据呢?70条...如果pair录入的话,大约40-50分钟吧

结论:

1.Pragmatic Programmer都是很懒的,重复5次的工作都回用代码来写。
2.Pragmatic Programmer都是很有好奇心的,太多的重复性劳动只能分散他们的注意力,不知道会搞出什么了,我估计我要没有hack Watri,WPC已经开始写Firefox plugin了。
3.Pragmatic Programmer都是“古程序员”,写程序操纵计算机是很有乐趣的。
4.比一个Pragmatic Programmer更能折腾的是两个Pragmatic Programmer...


posted @ 2006-01-10 21:30 Raimundox 阅读(2589) | 评论 (9) | 编辑 收藏
 
A Pragmatic Way to Make "Squeak 3D Browser"
There is an article about how to make a "Squeak 3D Browser".And the nice guy who wrote the article also put a flash demo.It's an amazing demo to show how interesting programming in Squeak could be.But I don't like the too-many-GUI-way which the guy used. I thought there might be a more pragmatic way to do the same thing so I spent an hour on making a script, and finally I got it ;-)

Preferences enable: #systemWindowEmbedOK.

myBrowserWindow :
= Browser new openEditString:#Hello. 
myBrowserWindow setLabel:
'System Browser'; bounds: (Rectangle left:20 right:600 top:20 bottom:400).

my3DIDE :
= Wonderland new.
my3DIDE getDefaultCamera getMorph bounds: (Rectangle left:
20 right:800 top: 20 bottom: 600).

my3DBrowser :
= my3DIDE makePlaneNamed: 'System Browser'.
my3DBrowser setY:
50; turn:#right turns:0.38;
                setProperty: #adjustToTexture toValue: 
true;
                setTexturePointer: myBrowserWindow;
                setProperty: #activeTexture toValue: 
true.

Try it and have fun!
posted @ 2005-12-27 16:54 Raimundox 阅读(2029) | 评论 (0) | 编辑 收藏
 
丧钟为谁鸣?(4)

第3. 一切皆对象和面向对象的理论基础

老庄是反对一切皆对象的,而TrustNo1在javaeye的一篇帖子上说:

第一,我可以很负责的说,OO的,70年代成型,80年代在理论基础上就给人毙掉。从这种意义上说不是OO死不死的问题,而是OO还活着么?当然理论基础给人毙掉,不是说没有用。

我先说面向对象的理论基础的问题,至于一切皆对象稍后再表。

所谓面向对象的理论基础其实是没有的,原因很简单,面向对象根本就不是一种计算模型。在第一次软件危机的那个时代,对与计算机的非数值计算应用的讨论以及对于可计算性问题的研究和发展,大抵确立了几种主流的计算模型:递归函数类,图灵机,Lambda演算,Horn子句,Post系统等等。

其中递归函数类是可计算性问题的数学解释;图灵机是图灵解决可计算问题的时候所设计的装置,其后成为计算机的装置模型,与图灵机相关的形式语言和自动机成为了命令式语言的理论基础;lambda演算成为了函数式语言的理论基础;Horn子句是prolog这类逻辑语言的理论基础。但是我们惊讶的发现,面向对象没有计算模型的理论基础,换而言之,面向对象根本就不是从可计算性的研究上发展过来的,那么面向对象的理论基础的价值本身就不大。

所以我很奇怪的一个问题就是TrustNo1所谓的面向对象在80年代理论基础上给人毙掉的说法是从何而来的?既然面向对象本质上不是一种计算模型,那么它大抵上只能归结为一种应用技术,应用技术自然可以从各种不同的领域里得到相似的应用,那么毙掉的理论基础所指的又是什么呢?甚怪之。

既然面向对象不是一个计算模型,那么我们可以从不同的角度推断出OO的各种形态,老庄已经出给了从ADT引出OO的问题以及例子,我就不罗嗦了,我给一个从Functional Programming出来的例子,其实就是SICP里的Data as Procedure。

(define (make-user name age sex)
  (define (dispatch message)
     (cond ((eq
? message 'getName) name)
           ((eq? message 'getAge) age)
           ((eq? message 'getSex) sex))
           (else (error 'messageNotUnderstand))))
  dispatch)

然后我们就可以

(define vincent (make-user 'Vincent 24 'Male))
(vincent 
'getName)

自然的,如果我调用

(vincent 'sayHi)

会得到一个messageNotUnderstand的runtime错误,这就是一个很自然dyanmic type的对象封装,最早的面向对象系统Smalltalk和CLOS基本上都是这个路子,于是有一个问题,为什么最早的面向对象系统都是dyanmic type?这里就跟lambda演算有关了。

lambda演算这个计算模型根本的一个假设就是,对于任何一个定义良好的数学函数,我都可以使用lambda抽象来表述他的求值,因此无论是什么东西你能够构造lambda抽象的话,我就能计算。这个地方东西很多,大家可以找找lambda演算相关的资料,这里我说三件事(由于lambda太难输入,我用scheme程序代替,然后由于alpha变化,beta规约和eta规约我也用scheme伪码来模拟。)

第一个是数值的表述,其实这个很简单,不考虑丘奇代数的系统的话,我们可以把数值表示成单值函数:

(define one (lambda (x) 1))

这个东西无论给什么x都返回1,然后根据lambda演算里的alpha变换,这个lambda抽象等价于数值1。因此,对于所有的数值,我们可以按lambda演算处理。

第二个是bool的表达,也就是如何逻辑进行lambda抽象,下面我直接给出了,缺省认为大家都看了SICP,对Scheme也颇有心得。

(define true-new (lambda (x y) x)) ;;;这个函数也叫select-first
(define 
false-new (lambda (x y) x));;;这个函数也叫select-second

(define 
if-new (lambda (conditon if-true if-false) (condition if-true if-false)))

然后我就可以做一个测试

(if-new true-new 3 4)
3

(
if-new false-new 3 4)
4

因此,对于所有bool我们可以按lambda演算来处理

第三个是自定义类型,这里我们还是先看一个Lisp里的例子,序对。

(define (cons a b) (lambda (dispath) (dispatch a b)))
(define (car list) (list select
-first))
(define (cdr list) (list select
-second))

这里依旧是high-order,我们来测试

(define list1 (cons 1 2))
(car list1)
1
(cdr list1)
2

这里大家自己用beta规约算一下,就发现的确是这样的。这里我们又可以看到,在lambda演算里,根本没有数据或者类型。有的永远各种各样的lambda抽象而已(目前已经有了带类型的lambda演算)。这里说一句题外话,SICP里的Data as Procedure其实就是在说这个问题,不过他没明说,而是用了一种特殊的用法而引出了消息传递风格,我觉得这里SICP有些顾此失彼,对于data as procedure我总结的一般形式是

(define (construct-function value1 value2 value3 value4valuen) (lambda (dispatch) (dispatch value1 value2 value3 value4valuen)))
(define (select
-function1 data) (data select-first))
(define (select
-function2 data) (data select-second))
.
(define (select
-functionn data) (data select-last))

综上所述,我们看到在lambda演算里,一切都是lambda抽象,然后对于不同的lambda抽象使用alpha变换,beta规约和eta规约,表述各种不同计算。看,在面向对象之前就已经有了一切皆某某的完美的计算理论存在了。而且回顾一下:

(define (make-user name age sex)
  (define (dispatch message)
     (cond ((eq
? message 'getName) name)
           ((eq? message 'getAge) age)
           ((eq? message 'getSex) sex))
           (else (error 'messageNotUnderstand))))
  dispatch)

(define vincent (make
-user 'Vincent 24 'Male))
(vincent 
'getName)

我们有理由说,对象其实就是一个lambda抽象,所以一切皆对象不过是一切皆lambda抽象的演化,这也是为什么SICP里把面向对象称作一种“方便的界面”而不是一种抽象的方法的原因了。那么对象和lambda抽象又什么区别呢?

嘿嘿,熟悉FP的人都应该知道了,就是Side-Effect,副作用。对象允许对其内部状态进行修改,那么这个东西就破化了eta规约的前提条件,也就是说允许修改内部状态的东西,已经不是一个可以进行lambda计算的lambda抽象了。因此暂且给一个别的名字吧,就叫对象吧.....因此我认为,对象很大程度上是一种带有副作用的lambda抽象。

我在有一篇blog上写了Say sorry to object-oriented,里面给了一只用对象作分支的例子,这里就不重复了,有兴趣大家可以去看一下(刚才好像在JavaEye上看到一个说法,说Everything is Object是Smalltalk的广告语,唉,Smalltalk里的的的确确everything is object啊。)

这里我们来总结一下,面向对象作为一种“方便的界面”主要解决了一个局部化和模块化的问题,这是从lambda演算和函数编程的角度来看面向对象技术。(taowen在他的一篇blog上说,面向对象局部化了Side-Effect,我深以为然),这个东西我个人认为更加接近面向对象本来的意思,而不是由ADT里发展出来的带类型的对象系统的那个意思。因此老庄不以为然的面向对象类型系统,我也不以为然。但是面向对象作为lambda抽象的界面,我觉得还是很不错的。这一点在Smalltalk和Ruby里都有不错的体现。

posted @ 2005-12-21 15:51 Raimundox 阅读(4309) | 评论 (3) | 编辑 收藏
 
丧钟为谁鸣?(3)

第2 继承

前面已经说过了,继承至少有2个语义: 实现继承和类型继承,在说明这两个东西之前,我们继续来看上面的例子

Person>>playSoloOnInstrument:instrument
  instrument playNote: Note C;
             playNote: Note D;
             playNote: Note E.

Person
>>playBackgroundOnInstrument:instrument
  instrument playChord: Chord C1;
      playChord: Chord C1;
             playChord: Chord C1;

Person
>>playBWV996OnInstrument:instrument
   instrument playNote: Note C;
              playChord: Chord C;
              playNote: Note D.

现在我们看playBWV996这个消息,BWV996是Bach所写一个鲁特琴组曲,鲁特琴是弹拨乐器,同时也是和声乐器(所谓和声乐器就是可以演奏和声)。在很多乐器上都有改编的版本,比如鲁特琴的近亲吉他等等,这个时候,我们可以实现这样几个类

Lute>>playNote:note
  
Lute
>>playChord:note
  

Guitar
>>playNote:note
  
Guitar
>>playChord:note
  

Bass
>>playNote:note
  

然后我们可以尝试以此调用

vincent.playBWV996OnInstrument: Guitar new.
vincent.playBWV996OnInstrument: Lute 
new.
vincent.playBWV996OnInstrument: Bass 
new.

最后一个会得到一个messageNotUnderstand的错误。也就是说,对于Bass而言由于不能演奏和声从而不能演奏BMV996(不过这个世界上能人太多了...哎),我们换到静态类型面向对象系统来看。
对于第一个方法,playSolo的时候我们要求的类型是能够演奏单音的。我们可以写出来

1interface SoloInstrument {
2  SoloInstrument playNote(Note note);   
3}

对于第二个方法,playChord的时候我们要求的类型是能够演奏和弦的,我们可以写出来

1interface ChordInstrument {
2  ChordInstrument playChord(Chord note);   
3}

而对于第三个方法,playBWV996的时候我们要求既能演奏和弦也能演奏单音,这个时候出现一个问题,我们怎么处理Instrument的继承关系?一个能演奏和弦的乐器是否可以演奏单音(答案是一般而言是的,但是也不排除有一些不是这样的)?还是我们简单的写:

1interface SoloAndChordInstrument extends SoloInstrument, ChordInstrument{
2}

或者

1interface BWV996Playable {
2
3  BWV996Playable playNote(Note note); 
4  BWV996Playable playChord(Chord note);  
5}

对于动态类型简单的隐性类型约定,显示的类型系统带来的一个副作用就是我们必须处理类型之间的关系。注意这里是类型之间的关系,而不是对象之间的关系。老庄同志批了很多篇的面向对象的抽象,面向对象的类型系统以及面向对象的本体论,其实都在是在类型关系上折腾,而不是在对象关系上折腾。而事实上面向对象的类型系统并非必然就是静态类型系统,而我们的类之间的关系不一定就和类型的关系相一致。就像上例所示,在Smalltalk里,Lute,Guitar和Bass之间没有任何的继承关系,但是对于person的3个消息而言,它们却是有类型的。

因此老庄所批的,是对象类型系统的抽象能力,而非面向对象的抽象能力。正如他在类型系统里所给的例子,那张他认为很失败的面向对象的图,其实可以完全不依赖继承来实现,而对这个类型系统的消费者而言,他们能够以一定的类型的观点,来处理这个系统的对象。

而老庄最后一个结论:

我的结论是:“一个类型,是由其本质决定了所能表现出的可操作性,而不是有其所能接受的操作决定了其本质。然而,OO正好把这个问题搞反了!”

我的看法是,这句话根本就是诡辩,前面半句的主语是“一个类型”,后面半句的主语是"OO"...

虽然前半句是对的,但是换一样说法可能更好:"所能接受的操作反映了其本质",面向对象本身就没有说我要做一个本质抽象,这一点在Smalltalk的类型判断操作上的可能是一个佐证,Smalltalk用isKindOf来判断继承关系,我们来玩一个文字游戏,改成俚语就是kinda,也就是"有一点,有几分"的意思,而不是说,“就是”,或者“从分类学上可证明之类的含义”。我再举一个龌龊的例子。

vincent ballon: AirBallon new.
vincent ballon: Condom 
new.

气球和保险套,对于ballon这个方法而言是一个类型,都是"有几分"可以吹起来。但是我怎么定义一个精确的本质?Ballonable?还是MakeFromLatexAndVeryThin?或者简单说FlexableAndThin?

在继承这一点上,我想老庄引文中:Elminster的话是从事物的特征与属性归纳出它的“类型”。恰恰对于静态类型面向对象系统是可行的。如我前文所述,我把一个object和所有sender的约定(也就是interface),继承在一起,恰恰就是一个颇为恰当的类型定义。

而对于动态类型系统里的面向对象语言,继承的也有类型继承的含义,但是并不是唯一的途径。用一句我们常说的话,在静态类型系统里,类型和类是紧耦合的,动态类型系统中他们的耦合比较松。

从此而观,所有对于面向对象的哲学考虑以及本体的思考,对于动态面向对象系统已经不是那么迫切了。而把对象类型系统的不足归咎于面向对象的不足,也似乎论据不足。

posted @ 2005-12-21 00:44 Raimundox 阅读(2936) | 评论 (1) | 编辑 收藏
 
丧钟为谁鸣?(2)

第1 关于"接口"

关于接口的问题老庄说对了,这个东西并不属于面向对象的概念,而且在动态类型面向对象语言(比如Ruby, Smalltalk里),根本没有这个东西。这是一个纯粹的静态类型面向对象语言的特性,或者直接说,接口就是一个纯类型(Type)。还是上次的例子:

 1interface Instrument {
 2   void playNote(Note note);
 3   void playChord(Chord chord);
 4}

 5
 6in Person Class
 7
 8void playSolo(Instrument instrument) {
 9 
10}

在我接触Smalltalk的最开始的一段时间里,这种地方是让我最难受,我已经习惯了用类型辅助我的思维,但是我发现我在Smalltalk里做不到,虽然我可以写出

Instrument>>playNote:note
  
^self subclassResponsibility.

Instrument
>>playChord:chord
  
^self subclassResponsibility.

但是他却不是一个接口,我可以很用

instrument = Instrument new.
instrument playNote: Note C.

来构造一个Instrument之后在它之上调用playNote方法,然而我会得到一个messageNotUnderstand的错误,Smalltalk和Ruby里没有Abstract的概念。也就是说abstract method,abstract class以及interface,都不是面向对象的概念(或者严格一些说,都不是面向对象的必须的概念),而是面向对象类型系统的概念。那么在Smalltalk里我们会怎么做呢?

Person>>playSoloOnInstrument:instrument
  instrument playNote: Note C;
             playNote: Note D;
             playNote: Note E.

Person
>>playBackgroundOnInstrument:instrument
  instrument playChord: Chord C1;
                      playChord: Chord C1;
                      playChord: Chord C1;

对于playSoloOnInstrument:instrument,我们对于instrument的类型是有要求的,就是它必须能够接受playNote这个消息。当然这个类型需要是隐性,我也可以对等的写出静态面向对象的代码把这个隐性的类型显示的表示出来:

1interface Instrument {
2   Instrument playNote(Note note);  
3}

同样对于第二个方法我们也可以写出来:

1interface Instrument {
2   Instrument playChord(Note note);  
3}

如果我们需要多于一个的消息也是一样的,比如

Person>>playBWV996OnInstrument:instrument
   instrument playNote: Note C;
              playChord: Chord C;
              playNote: Note D.

同样这个时候隐性的类型需要就是

1interface Instrument {
2   Instrument playNote(Note note);  
3   Instrument playChord(Note note);  
4}

那么接口是什么呢?我给出一个不确切的说法,interface是一个消息的发送者(sender)和一个消息的接受者(reciver)间的一种类型的约定,也就是说在我看来interface的用处主要在细粒度的显式类型约定。我有一个同事,每次写代码都为一个Test Case所要测试的对象定义一个interface,每个interface都只有2-3个方法(lx同学夸你呢:D),这是很得interface之三味的用法。这种的做法对于在静态的面向对象系统的好处我们在继承里再论述。

至于老庄所说的接口是多继承的一种代替品,这只不过是世俗的看法,在静态类型的面向对象里,继承至少有2个语义:

1.实现继承,这个在Smalltalk,Ruby,C++里有,而在java里没有,C++里是通过private extends来实现的

1class SubClassA: private ImplementationParent {
2}

这也是C++里为数不多的subclass不是subtype的例子。

2.类型继承,这个在C++和java里有,而在smalltalk,ruby有却不明显。

类型继承的极致就是C++里的纯虚父类

1abstract class Parent {
2
3  public asbtract void method1() = 0;
4  public asbtract void method2() = 0;
5}

也就是java里的interface

1interface Parent {
2   void method1();
3   void method2();
4}

因此,也就明了了,所谓“面向接口编程”是以类型作为约定的编程。我觉得这点大家一定不陌生,面向接口的编程里interface都很小而且约定明确。但是要说明一点的是,这个东西和"面向抽象而不要面向具体编程"其实还不一样,所以这个东西也就仅仅能算是静态类型面向对象的一个惯用法,还到不了原则这么高。

posted @ 2005-12-21 00:22 Raimundox 阅读(3170) | 评论 (2) | 编辑 收藏
 
I'm a RSSer
RSS may be one of the most delusive terms, it could stand for 'RDF Site Summary', 'Rich Site Summary', 'Really Simple Syndication' or other things.Today I introduce a brand-new meaning of RSS: Ruby.Smalltalk.Scheme, which are my beloved top 3 programming languages in ascending order.The more I write in Ruby, the more I think in Smalltalk;And the more I think in Smalltalk, the more I love Scheme.Abstract concepts in Scheme, think objects in Smalltalk and write codes in Ruby, cool!
posted @ 2005-12-20 21:48 Raimundox 阅读(2668) | 评论 (0) | 编辑 收藏
 
丧钟为谁鸣?(1)

开篇之前先说明一下,我和老庄有着不错的私交,他最初写丧钟系列的时候,我是忠实的拥趸之一,庄兄见我尚有寸尺所长,还在丧钟系列里引用了我的几个观点。然而最近一段时间里,我作了这样几件事情:

1.重新学习了lambda演算。我的数学基础不及taowen等,费了一些时日仍不完全了然,目前大抵能对着R5RS后面的语义定义对一些简单的scheme程序进行lambda演算。
2.反复研究了几遍SICP。同时看了录像和书(感谢曹老师下的Video),自信对前二章半有些许认识书中未及,后二章半粗知大览尚不能发前人所未发之言。
3.学习了Smalltalk和Ruby,Duck Typing以及其他一些有关类型系统的东西。
4.回顾了一下面向对象语言的一些发展史,以史为鉴粗知一些兴替。
5.经日和taowen,老庄讨论语言、设计等等之类,每为一辨必穷我所知争发一言,所以知识可以迅速杂糅:D

经过种种之后,发现当初所谓鸣OO之丧钟,实在是言过其实。
--------------------------------------------------------------------------------------------------------------------------------------------------------------
第0 关于"面向对象"

既然要为人家敲丧钟,先要找对了苦主,不然岂不是白忙活了一场。什么是面向对象?这个问题太大,我们举一个很常见的例子:

interface Instrument {
   
void playNote(Note note);
   
void playChord(Chord chord);
}


abstrat 
class Keyboard implement Instrument {
  ..
}


class ClassicGuitar implements Instrument {
   .
}


class Piano extends Keyboard {
  ..
}


in person 
class

void playSolo(Instrument instrument) {
   instrument.playNote(AbsoluteNote.C1).playNote(AbsoluteNote.C2);
   
}


void playBackground(Instrument instrument) {
   instrument.playChord(Chord.C5).playChord(Chord.C9);
   
}



in some 
case

ClassicGuitar perez711
= .
Piano pianoHere 
= .

vincent.playSolo(stenzel);
may.playBackground(pianoHere);

etc.

这个例子自然很不充分,不过继承啊,接口啊,多态啊,略举大概,尚可以作为一个讨论的例子。从这个例子里,我们发现有这样一个事实,就是Person类有两个方法

void playSolo(Instrument instrument);
void playBackground(Instrument instrument);

分别表示,需要一个类型为Instrument的对象instrument,然后对他进行相应的操作,而且,从概念上来讲,第一个方法,使演奏旋律的,也就是单音,而第二个方法是演奏和声的。那么,如果我有一个类

class GlassBottle {

   
void playNote(Note note) {
      ..
   }

}

我们知道,玻璃瓶装了水也是可以发出声音,Mozart可是为玻璃瓶写过曲子的,而这里我们却不能

GlassBottle beerBottle = .
vincent.playSolo(beerBottle);

因为我们在构造Person类的时候,我们的play方法是依赖一个类型为Instrument的Object,换而言之,我们这里所谓的Object-Oriented其实称作Object-with-Type-Oriented的更合适,其实这类语言有一个专有名词,叫做static type object oriented。那Object-Oriented是不是就一定是Static Type Object Oriented的呢?不然,比如在Ruby里,我们可以写成:

class Person 
  def playSolo(thing)
     thing.playNote(Note.C1).playNot(Note.c2)
  end
  def playBackground(thing)
     thing.playChord(Chord.C5).playChord(Chord.C9)
  end
end

class Guitar
  def playNote(note)
    
  end
  def playChord(chord)
    
  end
end

class GlassBottle
  def playNote(note)
    
  end
end

然后就可以

perez711 = Guitar.new
vincent 
= Person.new
vincent.playSolo(perez711)

同样也可以

vincent.playSolo(GlassBottle.new)

在这里,类型可以推演也可以留到runtime检查,如果是推演的话,在playNote里,由于thing调用了playNote方法,所以传进来的对象必须要刻意接受playNote这个消息,于是Guitar和GlassBottle都是可以通过,因此我即可以演奏我的Perez 711,也可以敲玻璃瓶。这种方式叫做dynamic type。

那么static type和dynamic type区别在那呢?static type认为,class就是type,type就是class; subclass就是subtyping,subtyping就是subclass(其实这句不严谨,在c++里可以使得subclass不是subtyping,但是java里就没办法了);而dynamic type则认为类型和class没有关系,类型取决于一个对象能够接受的消息。

那么哪个才是面向对象之真貌呢?遗憾的说,static type并不是最初的面向对象。以对象作为模块化的单位,始自Simula 67。但是Simula 67并不是一个面向对象系统,以目前的观点来看,充其量是Object-based,第一个面向对象语言当推Smalltalk,Smalltalk是dynamic type的。不过Smalltalk不太流行,第一个大面积流行的面向对象语言是C++,C++是static type的,正如Lisp是第一个函数式编程语言,很多Lisp的特性被当作函数式语言的共有特性(比如表是最重要的数据结构,轻语法)一样,所以很多人以为面向对象就必然是static type(比如老庄和我)。对于面向对象的误解,80%来自C++。也就是说,80%来自于static type的面向对象系统。将static type面向对象归咎于整个面向对象,我辈实在是大而无当言过其实。

后面我再一一将老庄所言之OO不当之处加以说明

posted @ 2005-12-20 19:58 Raimundox 阅读(6946) | 评论 (11) | 编辑 收藏
 
仅列出标题
共4页: 上一页 1 2 3 4 下一页 
随笔:36 文章:0 评论:93 引用:0
<2025年8月>
日一二三四五六
272829303112
3456789
10111213141516
17181920212223
24252627282930
31123456

常用链接

  • 我的随笔
  • 我的评论
  • 我的参与
  • 最新评论

留言簿(21)

  • 给我留言
  • 查看公开留言
  • 查看私人留言

随笔分类

  • ThoughtBlog(21) (rss)
  • 学而篇第一(15) (rss)

随笔档案

  • 2009年3月 (1)
  • 2007年11月 (1)
  • 2007年9月 (2)
  • 2007年8月 (1)
  • 2007年7月 (1)
  • 2007年6月 (1)
  • 2007年5月 (2)
  • 2007年3月 (8)
  • 2007年1月 (1)
  • 2006年10月 (1)
  • 2006年8月 (1)
  • 2006年3月 (1)
  • 2006年1月 (3)
  • 2005年12月 (12)

搜索

  •  

最新评论

  • 1. re: 丧钟为谁鸣?(4)
  • 通讯模型!
  • --ic
  • 2. re: A Patch for Ruby Watir Test Framework
  • 忘记附上我的邮箱(如果能得到你的答复,万分感谢)
    mac_cf@163.com
  • --mac_cf
  • 3. re: A Patch for Ruby Watir Test Framework
  • 评论内容较长,点击标题查看
  • --mac_cf
  • 4. re: 丧钟为谁鸣?(1)
  • 反复研究了几遍SICP。同时看了录像和书(感谢曹老师下的Video),是什么?求~
  • --qixinkui@qq.com
  • 5. re: Apologize to Object-Oriented Methodology
  • 拜读!开阔了视野!
  • --qixinkui@qq.com

阅读排行榜

  • 1. Selenium Better Pratice(8148)
  • 2. Feng Shui for Standard ML Programmers(7121)
  • 3. 丧钟为谁鸣?(1)(6946)
  • 4. Erlang Ring Benchmark(6716)
  • 5. The Keyword 'end' Drives Me Crazy(6688)

评论排行榜

  • 1. Selenium Better Pratice(11)
  • 2. 丧钟为谁鸣?(1)(11)
  • 3. A Day of Two Pragmatic Programmers' Life(9)
  • 4. ANTLR Lexer Tips(4)
  • 5. A Patch for Ruby Watir Test Framework(4)

Powered by: 博客园
模板提供:沪江博客
Copyright ©2025 Raimundox