RSS 2.0 Feed
2005-11 Entries
摘要:除了J#外,所有微软支持的.NET开发语言现在均支持运算符重载,因此纯粹为C#简化写法一样特性现已成为一种.NET开发中值得研究的一项重要语言特性。有人认为运算符重载其实就是简化写法,满足模拟基本类型操作的小功能,没有必要深究。但我觉得要多思考一层,为什么我们总希望模拟基本类型的操作?因为运算符重载能够将操作中缀化,能够自动推测静态过程的主体。 首先是操作中缀化。函数调用其实是一种前缀操作,函数(代表操作)总是在参数(代表操作数)之前写出。这样执行序列操作时运行的顺序其实和书写的顺序相反: H(x,y)G(H(x, y), z)F(G(H(x, y), z), w) 序列运行的顺序是H->G->F但是却要反过来写,二元参数距离函数名越来越远。我们按照计算机执行的顺序思考,却要反过来写,多少有些不爽。成员函数与扩展方法的写法则是将操作数(对象)写在前面: x.H(y)x.H(y).G(z)x.H(y).G(z).F(w) 这样就将书写的顺序正过来了。这是一个甚好的方案,但是在不具备扩展方法的今天,有些事情是成员函数做不了的。比如在我的VBF里,我希望Functor<T, bool>可以进行And, Or等逻辑运算,而Functor<T, int>之间只能进行算术运算,Functor<T, string>之间只能进行连接运算,而且规则还不一样……但是成员函数没有根据类型参数选取不同重载的能力,也就是说.NET泛型无法进行特化操作。在.NET中具有编译期类型判定的机制只有两个:函数根据参数类型的重载和用户自定义隐式转换(相当于根据返回类型重载)。我们可以用Functor<,>类型的静态方法来实现根据类型参数不同的不同重载。但是静态方法不但要写全类型的名字,还是前缀操作,使用起来让人甚为不爽,这时就会发现,运算符重载是我们梦寐以求的东西。 Type.op_Operator(x, y) '静态方法x op y '运算符写法 以上两种是等价的,可以看到运算符重载不仅可以通过x,y的类型推测静态方法的调用主体Type,还可以将操作转化为中缀写法——比后缀更适合表现二元运算。既然这么完美,我们能不能这样写呢? Class Functor(Of T, U)    Public Shared Operator And(x As Functor(Of T, Boolean), y As Functor(Of T, Boolean)) _        As Functor(Of T, Boolean)    End OperatorEnd Class 很遗憾,这样会编译错误。作为运算符重载过程,其参数至少有一个必须是定义运算符的类型。在编译器看来,必须是Functor(Of T, U),两个类型参数都必须是该泛型类定义的。就在我对此大感抱怨时,我偶然在C#编译器的源代码(见Rotor)中看到了它识别运算符的规则,其中并没有这些限制,只有两条规则——方法必须是静态的,特定名称的方法;方法必须带有specialname属性。那么我们完全可以骗过编译器,不用它提供的Operator关键字来声明运算符重载过程,而是使用自己编写特定名称的方法,并加以specialname的手法来打造运算符重载过程: Imports System.Runtime.CompilerServicesClass Functor(Of T, U)    <SpecialName()> _    Public Shared Function op_BitwiseAnd(x As Functor(Of T, Boolean), y As Functor(Of T, Boolean)) _        As Functor(Of T, Boolean)    End FunctionEnd Class System.Runtime.CompilerServices.SpecialNameAttribute是一个指示编译器为声明成员添加specialname的特殊属性,C#和VB编译器都支持。op_BitwiseAnd是VB和C#等语言所识别的与操作运算符过程名称。这样写完以后编译成类库,再以引用DLL的方式引用它,你就会看到编译器将他识别成了我们要的运算符重载过程。当你在Functor<T, int>这样的类型上使用And操作时,编译器会告诉你不支持该运算符,仅在Functor<T, bool>上才能进行这一操作,编译错误信息准确无误,真是太棒了。 在我们结束前,我们还可以看看如此手工打造还能突破哪些编译器人为的限制: 可重载Protected和Private的运算符(尽管这样做几乎没有意义)可不成对重载比较运算符(=, >, >=, <=, <, <>)可以让移位运算符的第二个操作数不是int(>>和<<样子很好看,但是有了这个限制我们就不能拿它来干别的事情,现在好了)可以在C#中重载仅VB支持的运算符,也可以在VB中重载仅C#支持的运算符(当然要到对方语言中才能生效)可以让用户自定义显式转换支持泛型类型参数之间更加神奇的写法用了这种手法,似乎还可以重载诸如operator+(int, int)之类的运算符,但它们并不能生效。 .NET语言编译器中每一项特性,都可能有隐藏在其表面之下的深层次用途。善加研究后常能发现原来所认识不到的功能。我当然不是在推荐大家乱用运算符重载,只是一种思考,一种新的灵感。...[阅读全文]

posted @ | Feedback (8) | Filed Under [ 技术随笔 灵感记录 ]

摘要:很早就开始VBF的设计,在经历开发和调整了两个月的时间后,VBF的第一个BETA版终于和大家见面了!我是从前一阶段博客园上较为流行的动态语言讨论上获得灵感。我主要不是想写一个动态语言,而是发现很多动态语言共有的特征——不同程度地支持函数式编程(Functional Programming,FP)。这是一种显著不同于命令式编程(目前的VB,和C#均为命令式编程语言,面向过程和面向对象也是命令式编程范式的扩展)的编程范式。它讲究组合、高阶函数和延迟计算。在纯粹的FP语言(Lisp, SML)可以不使用变量,函数执行没有副作用,表达式的意义更接近数学式的意义而不是计算机的执行方式。函数式语言具有比命令式语言更高的数学抽象力,因此在描述某些算法的时候比命令式语言更佳。现在人们发现,在命令式语言中引入局部的函数式编程思想,能将两种范式的好处合二为一。VB9和C#3都将不同程度地引入函数式编程的特性。 我开发VBF主要就是希望利用Visual Basic 2005和C# 2.0自身所具有的语言抽象力将函数式编程的思想引入。我不对编译器进行改造,也不编写任何插件,只要引用我的类库,就可以实现一些非常精妙的语法。 [VB]Dim list As List(Of Integer) = GetList() '假设用一些数填充listDim result As List(Of Integer)'找到所有大于25或者小于等于17的数,存入resultresult = list.FindAll(IdInt > 25 Or IdInt <=17) [C#]// using I = VBF.Functional.IdentitiesList<int> list = GetList(); //假设用一些数填充list//找到所有大于25或者小于等于17的数,存入resultList<int> result = list.FindAll(I.IdInt > 25 | I.IdInt <= 17); 如你所见,“IdInt”就如同表达式中变量的占位符,由它生成的表达式并不求值,而是将表达式的运算规则保存下来传递给FindAll,而FindAll作为一个高阶函数将表达式作用于列表中的每一项上。这就是延迟计算。这里IdInt为“恒等组合子(Identity Combinator),是VBF初期所支持的几个组合子之一。通过组合子之间的组合就可以重用函数的功能,比如IdInt和+(对结果进行加法的组合子)和7(常函数)三者组合,就生成了对一个数加7的函数。这就是我VBF BETA1的最基本功能。我在VBF 1.0 BETA1中支持了以下特性: Functor 函数体基类,支持大量组合运算符和延迟求值Combinator 组合子基类Identity和Constant组合子Predicate 谓词ComparisonPredicate 比较谓词(大于、小于等)进行通用四则运算的组合子进行字符串处理的组合子 我的VBF代码中非常频繁地使用泛型和运算符重载,大量手法均为主流类库所见不到的。我也因此获得了非常丰富的在.NET中使用泛型的经验。大家均可下载我的代码参考。项目主页:http://www.sf.net/projects/vbf 希望大家对我的VBF提出建议,同时敬请关注VBF的后续版本,有更多精彩的内容。  ...[阅读全文]

posted @ | Feedback (16) | Filed Under [ 技术随笔 ]

摘要:.NET 2.0的泛型,是在何时进行建造(Construct)?编译器在何时知道泛型的类型参数,何时不知道?不妨先来思考一下这几个Quiz: Quiz 1:写出以下代码的输出class Program ...{ static void Main(string[] args) ...{ A<int> a = new A<int>(); a.InternalCall(12); } } class A<T> ...{ public void InternalCall(T obj) ...{ Test(obj); } public void Test<U>(U obj) ...{ Console.WriteLine("U"); } public void Test(int obj) ......[阅读全文]

posted @ | Feedback (19) | Filed Under [ 技术随笔 ]