| |||
| 考虑这样的一个简单的需求: 表示一个二叉树。 在C/C++里也许是这样: 代码:
这是一种最常用的procedural的办法。它的好处是简单,清楚。坏处是处理0指针容易出错,你要时刻担心你手里的指针是不是0. 而且它也不够fancy. (当然,在这种方法在很多场合如嵌入式系统编程,还是必要的合理的) 典型的使用这样的结构的代码会是这样: 代码:
再看OO的方法,即visitor pattern: 先声明一个接口: 代码:
代码:
1。类型安全。有编译器的保护,你想犯错误,把TreeLeaf当TreeCons用都不行。 2。fancy. 你可以跟人吹嘘你用了visitor pattern. 缺点是: 1。繁琐。你要写更多的代码。即使是Java的anonymous class的语法也要求你写好多的多余代码。这对重量级的处理程序还可以。但是要是我所要的只是对某一个case写一两句话,如,“判断这个Tree是否是空”这个visitor就太笨重了。本来在procedural里只要写两行的,你要我写十行? 此时,你会被引诱写这样的代码: 代码:
2。visitor接口是否要返回值?是否要throws Exception? 写visitor接口时,你并不知道会有几十种实现,所以,你怎么决定返回值呢?在Java里,你可以用Object. 但,这样,你又牺牲了类型安全,比如: 代码:
所以,人们一般都不给visitor指定返回类型。对以上的getName的逻辑,使用无返回类型的visitor的代码是这样: 代码:
不过,此时,代码就更加繁琐了。你也不能再使用anonymous class. 明明是函数里面的逻辑,却要移到函数外实现。感觉绕啊绕啊的别扭极了。 其实,这里使用generics, 或者说模板,可以说最合适,代码可以是这样 (这里使用GJ的语法,感觉比C++的简洁): 代码:
不过,即使用模板,我们也还是有一个麻烦:Exception. 因为定义visitor接口的时候,我们根本不知道各个实现会抛出什么异常。 幸好Java里所有的checked exception都是Exception. 可是我们真想让我们的每一个visitor接口都充斥着throws Exception这样的东西吗?那样,所有的visitor的使用者都得被迫try-catch或者throws Exception, 即使使用者知道不可能出现Exception. 代码:
代码:
代码:
基于这些麻烦,现实程序中的visitor往往也是不定义任何exception的。如果实现抛出了任何exception, 它必须被包装在一个RuntimeException (也就是一个不要求使用者try-catch-throws的exception). 但这种方法往往导致一个异常链,你得这样: 代码:
(听说为了这个, jdk1.4不是1.5的还把异常链直接建入了Exception类里,也真够难为可怜的Sun的) 另外一个问题是,RuntimeException不要求使用者try-catch/throws, 所以,程序员的大意可能导致异常没有被及时处理。又是一个牺牲静态类型系统的地方。 好了,最后,让我们来看看functional是怎么处理这种需求的。它大致的语法如下(记不清了) 代码:
接下来,我们的getName()函数可以这样: 代码:
简单吧?加上异常也一样简单: 代码:
没有类型安全的问题。没有代码繁琐的问题。 值得注意的是,这种方法虽然看上去和procedural的方法很象,但却有本质的不同。 类型系统会保证你不会把一个Leaf当成一个TreeCons, 你也不用做强制转换。 可以说,它既有传统procedural的简洁的优点(实际上,我觉得它比procedural的代码还要简洁的多),也具有OO visitor的类型安全的特点。(区别只是比visitor更安全罢了) 我最近就一直在想,能不能把pattern match这个特性并入到OO中去呢?各位有什么想法吗?欢迎讨论。 |
| |||
| exception也很有可能是rtti实现的。但我并没有否定exception呀!底层实现是rtti, if-else还是visitor那是另一回事。我们关心的是代码是否类型安全。 这种case的写法和 代码:
代码:
|
| |||
| 在考虑一个pattern match的最简单情况: 代码:
不过相比于C的enum, pattern match类型更安全。你不能把weekday当int使的。 Java就缺enum这么个东西。有些人说java不需要,甚至举出各种workaround来。 但是,这些workaround都有问题。 比如说,我前面的visitor的解决方法。繁琐的缺点我已经说了。另一个缺点是: enum的逻辑等语义是和值类型一样的。也就是说, 代码:
代码:
flyweight不是那么好做的。) 它在object persistence的时候还是会有问题。你怎么保证你的引用完整性不被persistence破坏呢?Java的serialization只保证在一次serialize的时候保持引用完整性。也就是说:你把一个对象serialize两次,它就会变成两个对象拉! 对简单的enum, 有的人还提出用这样的方法: class Weekday{ public static final Weekday monday= new Weekday(); public static final Weekday tuesday= new Weekday(); public static final Weekday wednsday= new Weekday(); public static final Weekday thursday= new Weekday(); public static final Weekday friday= new Weekday(); public static final Weekday saturday= new Weekday(); public static final Weekday sunday= new Weekday(); } 但是,它也是有persistence语义的问题的。(实际上,我感觉,new在语义上就不应该承诺必然返回不同的地址。对这样的no-side-effect对象,语言实现完全可以考虑做适当的优化。不同于flyweight的是,这些优化可以是在编译时静态解析的,所以没有运行开销)相比之下,C的enum就没有这个问题。 哎,所以有时候当我看到一些板动不动的人自信地宣称:“Java不需要enum, Java不需要generics, java不需要这个,java不需要那个”时,真感觉#*¥)(—#¥ |
| ||||
| 引用:
|
| |||
| 我觉得函数语言里的pattern match的语义就是对象的动态类型匹配。在C++这样的依赖静态类型检查的语言来说,动态类型检查肯定就绕过了编译时期的类型检查,所以,这是不太可能的,也许扩展union的定义能实现这一点。而其他的OO语言主要是靠类型rtti来实现这一点吧?如果使用virtual语义来实现有限动态类型检测,那实际上就是visitor模式。 函数型语言为什么能安全地进行对象的动态类型匹配呢?我觉得有几个原因:1. 无副作用; 2. 强大的类型推导系统; 3. 无类型转换。这三条加在一起,使得一个对象从生成的那一刻起,就不可能改头换面或者成为漏网之鱼。 |
| ||||
| 引用:
引用:
另一方面,我猜测,从设计思想上,这种 pattern match 的想法是不能简单的放进现有的 OO 的框架的,除非象 C++ 那样摆明车马当作一个新的 paradigm 来支持,否则只会破坏整个语言的一致性。 猜测而已,进一步的确证和讨论需要大量时间精力,这是我现在支付不起的。 |
| |||
| 引用:
反之,有副作用和类型cast使得编译器无法断定一个对象的实际类型。比如一个函数,传入的参数定义是基本类,实际可能是个子类,具体是什么子类,编译器在编译时无法判定,除非做全局的数据流分析,但是做全局分析就要求分析所有涉及的子模块,这和静态语言的子模块各自独立,链接不做分析的传统是矛盾的。 所以,要想在OO语言安全地实现动态类型匹配,一个折中的办法就是利用virtual语义。 |
| ||||||
| 在我的概念中,面向对象的特征是多态,而不是rtti. rtti只是一个附加的功能而已。你可以设计一个没有rtti的面向对象语言,但没有多态,那就不是面向对象了。实际上,如果你读了我翻的那篇对象概论,里面有个观点是:从purist的观点看,rtti是损害了OO的纯洁性的。所以选rtti来做OO的代表,就有点讽刺意味了。 所以,我说OO中pattern match的对应物是visitor. 而且,visitor和pattern match的一个共同点就是类型安全。而rtti的不安全类型正是pattern match所防止的。可以说rtti和pattern match只是形似罢了。 要说对应,用union+type flag好像和rtti对应。 下面逐句回答 引用:
动态语义可以选择visitor或者union+type flag.反正对源程序透明啦。 引用:
引用:
引用:
你概念中的叶子是这个:Node(leaf, leaf). 理解有误。 引用:
实际上,我正在构思一个functional的OO语言。无副作用正是它要支持的。当然目标只是一个玩具。 引用:
当然,我不是说它肯定是可行的。我是正在试图看到它不可行的一面。 smille, 能举个例子证明side-effect或downcast会使pattern match不工作吗? 其实fpl里已有多态呀。你也可能拿到一个signature但不知道它的具体类型是什么。好像这并不构成问题。 |
| |||
| 实际上,如果仅仅谈实现的可能性。 给定这样的代码: 代码:
代码:
当然,另一个问题是,如果我们支持rtti, 我们是否要让datatype代表运行时的一个单独类型。 |
| ||||
| 我当然知道 RTTI 的存在属于 OO 的“瑕疵”,我只是觉得你提出的这种 pattern match 的做法更类似于 RTTI 而不是 visitor 的解决方案。 引用:
代码:
如果仅仅从实现角度,那是肯定没问题的,Effle 可以拿 C 当作“可移植的机器码”来用,构造出一个面向对象的语言,你当然也完全可以在 java 或者别的什么语言上构造出一个扩展来支持你的想法。 问题在于,你构造出来的扩展和原来的语法是不是可以很好的融合在一起,整个语言是不是仍有一致性。如果这个扩展加的很生硬,整个语言好象成了互不相干的两块,那即使你能实现,也没有太大的意义。 |
| ||||
| 引用:
引用:
不过,即使把它当作一个独立的类型,我也看不出问题来) 引用:
引用:
不过,就算可以,我觉得也没问题。至少,C++里同样的overloading规则可以用在这里。 我正在考虑的就是什么样的原因使它不适合加入OO. 你光跟我说:它不见得合适。等于什么也没说啊。 怎么个不合适呢? |