GP技术的展望——道生一,一生二
GP技术的展望——道生一,一生二
by 莫华枫
长期以来,我们始终把GP(泛型编程)作为一种辅助技术,用于简化代码结构、提高开发效率。从某种程度上来讲,这种观念是对的。因为迄今为止,GP技术还只是一种编译期技术。只能在编译期发挥作用,一旦软件完成编译,成为可执行代码,便失去了利用GP的机会。对于现在的多数应用而言,运行时的多态能力显得尤为重要。而现有的GP无法在这个层面发挥作用,以至于我这个“GP迷”也不得不灰溜溜地声称“用OOP构建系统,用GP优化代码”。
然而,不久前,在TopLanguage group上的一次讨论,促使我们注意到runtime GP这个概念。从中,我们看到了希望——使GP runtime化的希望——使得GP有望在运行时发挥其巨大的威力,进一步为软件的设计与开发带来更高的效率和更灵活的结构。
在这个新的系列文章中,我试图运用runtime GP实现一些简单,但典型的案例,来检测runtime GP的能力和限制,同时也可以进一步探讨和展示这种技术的特性。
运行时多态
现在的应用侧重于交互式的运作形式,要求软件在用户输入下作出响应。为了在这种情况下,软件的整体结构的优化,大量使用组件技术,使得软件成为“可组装” 的系统。而接口-实现分离的结构形式很好地实现了这个目标。多态在此中起到了关键性的作用。其中,以OOP为代表的“动多态”(也称为 “subtyping多态”),构建起在运行时可调控的可组装系统。GP作为“静多态”,运用泛化的类型体系,大大简化这种系统的构建,消除重复劳动。另外还有一种鲜为人知的多态形式,被《C++ Template》的作者David Vandevoorde和Nicolai M. Josuttis称为runtime unbound多态。而原来的“动多态”,即OOP多态,被细化为runtime bound多态;“静多态”,也就是模板,则被称为static unbound多态。
不过这种称谓容易引起误解,主要就是unbound这个词上。在这里unbound和bound是指在编写代码时,一个symbol是否同一个具体的类型 bound。从这点来看,由于GP代码在编写之时,面向的是泛型,不是具体的类型,那么GP是unbound的。因为现有的GP是编译期的技术,所以是 static的。OOP的动多态则必须针对一个具体的类型编写代码,所以是bound的。但因为动多态可以在运行时确定真正的类型,所以是runtime 的。至于runtime unbound,以往只出现在动态语言中,比如SmallTalk、Python、Ruby,一种形象地称谓是“duck-typing”多态。关于多态的更完整的分类和介绍可以看这里。
通过动态语言机制实现的runtime unbound,存在的性能和类型安全问题。但当我们将GP中的concept技术推广到runtime时会发现,rungime unbound可以拥有同OOP动多态相当的效率和类型安全性,但却具有更大的灵活性和更丰富的特性。关于这方面,我已经写过一篇文章 ,大致描述了一种实现runtime concept的途径(本文的附录里,我也给出了这种runtime concept实现的改进)。
Runtime Concept
Runtime concept的实现并不会很复杂,基本上可以沿用OOP中的“虚表”技术,并且可以更加简单。真正复杂的部分是如何在语言层面表达出这种runtime GP,而不对已有的static GP和其他语言特性造成干扰。在这里,我首先建立一个基本的方案,然后通过一些案例对其进行检验,在发现问题后再做调整。
考虑到runtime concept本身也是concept,那么沿用static concept的定义形式是不会有问题的:
concept myconcept {
T& copy(T& lhs, T const& rhs);
void T::fun();
...
}
具体的concept定义和使用规则,可以参考C++0x的concept提案或这篇文章 ,以及这篇文章 。
另一方面,我们可以通过concept_map将符合一个concept的类型绑定到该concept之上:
concept_map myconcept {}
相关内容也可参考上述文件。
有了concept之后,我们便可以用它们约束一个模板:
templatevoid myfun(T const& val); //函数模板
templateclass X //类模板
{
...
};
到此为止,runtime concept同static concept还是同一个事物。它们真正的分离在于使用。对于static concept应用,我们使用一个具体的类型在实例化(特化)一个模板:
X x1; //实例化一个类模板
MyType obj1;
myfun(obj1); //编译器推导obj1对象的类型实例化函数模板
myfun(obj1); //函数模板的显式实例化
现在,我们将允许一种非常规的做法,以使runtime concept成为可能:允许使用concept实例化一个模板,或定义一个对象。
X x2;
myconcept* obj2=new myconcept;
myfun(obj2); //此处,编译器将会生成runtime版本的myfun
这里的含义非常明确:对于x2,接受任何符合myconcept的类型的对象。obj2是一个“动态对象”(这里将runtime concept引入的那种不知道真实类型,但符合某个concept的对象称为“动态对象”。而类型明确已知的对象成为“静态对象”),符合myconcept要求。至于实际的类型,随便,只要符合myconcept就行。
这种情形非常类似于传统动多态的interface。但是,它们有着根本的差异。interface是一个具体的类型,并且要求类型通过继承这种形式实现这个接口。而concept则不是一种类型,而是一种“泛型”——具备某种特征的类型的抽象(或集合),不需要在类型创建时立刻与接口绑定。与 concept的绑定(concept_map)可以发生在任何时候。于是,runtime concept实际上成为了一种非侵入的接口。相比interface这种侵入型的接口,更加灵活便捷。
通过这样一种做法,我们便可以获得一种能够在运行时工作的GP系统。
在此基础上,为了便于后续案例展开,进一步引入一些有用的特性:
1. 一个concept的assosiate type被视为一个concept。一个concept的指针/引用(concept_id*/concept_id&,含义是指向一个符合concept_id的动态对象,其实际类型未知),都被视作concept。一个类模板用concept实例化后,逻辑上也是一个concept。
2. 动态对象的创建。如果需要在栈上创建动态对象,那么可以使用语法:concept_id<type_id> obj_id; 这里concept_id是concept名,type_id是具体的类型名,obj_id是对象名称。这样,便在栈上创建了一个符合concept_id的动态对象,其实际类型是type_id。
如果需要在堆上创建动态对象,那么可以用语法:concept_id* obj_id=new concept_id; 这实际上可以看作“concept指针/引用”。
3. concept推导(编译期)。对于表达式concept_id obj_id=Exp,其中Exp是一个表达式,如果表达式Exp的类型是具体的类型,那么obj_id代表了一个静态对象,其类型为Exp的类型。如果表达式Exp的类型是concept,那么obj_id是一个动态对象,其类型为Exp所代表的concept。
那么如何确定Exp是具体类型还是concept?可以使用这么一个规则:如果Exp中涉及的对象,比如函数的实参、表达式的操作数等等,只要有一个是动态对象(类型是concept),那么Exp的类型就是concept;反之,如果所有涉及的对象都是静态对象(类型为具体的类型),那么Exp的类型为相应的具体类型。同样的规则适用于concept*或concept&。
4. concept转换。类似在类的继承结构上执行转换。refined concept可以隐式地转换成base concept,反过来必须显式地进行,并且通过concept_cast操作符执行。兄弟concept之间也必须通过concept_cast转换。
5. 基于concept的重载,也可以在runtime时执行,实现泛化的dynamic-dispatch操作。
下面,就开始第一个案例。
案例:升级的坦克
假设我们做一个游戏,主题是开坦克打仗。按游戏的惯例,消灭敌人可以得到积分,积分到一定数量,便可以升级。为了简便起见,我们只考虑对主炮升级。第一级的主炮是90mm的;第二级的主炮升级到120mm。主炮分两种,一种只能发射穿甲弹,另一种只能发射高爆弹。因此,坦克也分为两种:能打穿甲弹的和能打高爆弹的。
为了使代码容易开发和维护,我们考虑采用模块化的方式:开发一个坦克的框架,然后通过更换不同的主炮,实现不同种类的坦克和升级:
//一些基本的concept定义
//炮弹头concept
concept Warheads {
double explode(TargetType tt); //炮弹爆炸,返回杀伤率。不同弹头,对不同类型目标杀伤率不一样。
}
//炮弹concept,我们关心的当然是弹头,所以用Warheads定义一个associate type
concept Rounds {
Warheads WH;
...
}
//主炮concept
concept Cannons {
Rounds R;
void T::load(R& r); //装填炮弹,load之后炮弹会存放在炮膛里,不能再load,除非把炮弹打出去
R::WH T::fire(); //开炮,返回弹头。发射后炮膛变空,可以再load
}
//类型和模板定义
//坦克类模板
template
class Tank
{
...
public:
void load(typenam C::R& r) {
m_cannon.load(r);
}
typename C::R::WH fire() {
return m_cannon.fire();
}
private:
C m_cannon;
};
//主炮类模板
template
class Cannon
{
public:
typedef Rd R;
void load(R& r) {...}
typename R::WH fire() {...}
}
template concept_map <Cannons>{}
//炮弹类模板
template<Warheads>
class Round
{
public:
typedef W WH;
static const int caliber=W::caliber;
W shoot() {...}
...
};
template<Warheads W> concept_map<Round<W>>{}
[解决办法]
这篇文章好像和荣耀写的一篇叫《C++多态技术》有类似之处,核心的东西似乎不是在讲GP,而是在讲多态...
[解决办法]
为什么不混合语言编程,在C++0x还没从来时就可获得这些能力了。
[解决办法]
没有办法不顶啊,只可惜被过儿占了1层了。。。我哭。。。
[解决办法]
万变不离其中。和针对接口编程是一个道理,不过模板是编译时的,通过模板创建一系列的规则而已。主要你满足条件且支持编译时要求的接口就行。
始终觉得这个开发类库还不错。。别的方面用的少。。
[解决办法]
呵呵。楼主真可谓C++0x的传教士啊,现在我对C++0x是越来越期待了。同时也期待楼主更多更精彩的文章。
[解决办法]
赞lz,不过,内容有些牵强,感觉lz在用GP修饰自己的代码....
下面是我的一些看法:
首先有必要强调一下什么叫多态,或者说是我的理解,这里主要想指出为什么GP不能被牵强的说成是多态,多态应该指的是在某一个特定的环境下,对应多个对象的行为,而GP却是一个环境对应一个特定的对象,只不过GP可以同时创造多个环境,GP虽然也可以定义多个对象的行为,但是不要忘记GP也产生了多个环境,“静”多态无非是个好听的名字,实际上是“多-静态”
concept的概念不错,但在C++上是没有前景的,concept本身要求"对象"和concept之间建立联系,也就是要求建立concept体系,要求有与之相应的FrameWork,对于JAVA这样的有完整的类体系结构的语言或是那些动态解释型的语言来说,concept比较容易语言化,但是对于C++,这个从一开始崇尚自由的语言是和FrameWork格格不入的。很多人都抱怨说C++开发困难,归因于没有统一的库标准,而在这方面大做文章,而实际上根本的原因是因为C++语言本身就没有FrameWork,没有一个标准的体系结构的束缚,各个库的开发都在建立自己的FrameWork,而这些不同的库的framework根本就找不到一点点共同的切入点融合到一起,这一点是从C继承来的,也是C++的硬伤,一旦加入FrameWork,C++基本上就要和C说拜拜了,那也就不叫C++了...,没人提这些事情因为大家也都知道解决不了的事情讨论起来也没有意义,于是为了维护C++的“蒸蒸日上”,高手们开始玩GP的游戏,在各个领域绝望的煽风点火,然后又有很多有大师情节的程序员跟在后面仿效.....
GP--C++内嵌的脚本语言
以上只是一些个人观点,我还是喜欢在C的世界里快乐OOP~!
[解决办法]
concept在现有C++上实现不会是一个简单的活
“GP--C++内嵌的脚本语言”觉得用“GP applied in C++”比较合适,在C++里比较像是一个内嵌的脚本语言。
可惜这个脚本用起来限制太多,表达过于冗长,故从LZ N久前的帖子开始偶就叫嚣着把这个脚本独立化、规范化,从而才可能表达高级concept的语意。
如果GP体现了静多态,那么“甚至可以抛弃虚函数”则是应当。
GP代表的体系和OO的体系不同,C++是多体系的语言,应当重视体系的平衡、合作,不应该是一锅汤式的杂烩,或是厚此薄彼的虐待。
同意LZ的“concept除了描述一个类型的特征,还更加侧重于描述类型之间的关系”
concept = all about type(relationship ,traits etc.)
至于java辈,从开始就是瞄准OO的何必贪心地想模仿GP?
“C++是目前唯一“正在”实现concept的语言”
此外至于GP的未来,看好和看坏,甚至无视GP只是个人自己的观点,没有人能预测未来,大家只是猜测而已,我们只是希望从各自的猜测中窥见一些智慧的火花来更完善自己的知识体系。
P.S.很不好意思,虽然对LZ此文十分有兴趣,但限于时间没有及时拜读,至于评论,更是看了回复后的一家之疯语,有断章取义之处尽请斥责。
anyway , thank lz for his wonderful article series.
[解决办法]
与想像相反,编译时期的GP在C++很好实现,而且已经实现了。
我个人感觉CrySleeper对泛型的理解还是存在有一定的偏差。
其实C++目前的template已经非常强大,而且基本完备(指编译时期)。之所以需要concept这个东西主要是给程序员和编译器协作使用的。目前看来,concept只是一个给编译器的的说明清单,编译器根据这些清单可以明确、清晰的给出为什么它拒绝编译代码的原因。而这正是template目前使用上的艰难之处。
当然concept也能实现运行时多态,不过个人认为那不是concept的主要目标,至少不是当前的主要目标。
[解决办法]
concept explains to compiler our concepts~