Generic Programming - What are you, anyway?

Generic Programming - What are you, anyway?

 

刘未鹏(pongba)

C++的罗浮宫(http://blog.csdn.net/pongba)

 

One Ring, to rule them all.

- The Lord of the Rings

 

The Word Around Town

Google "generic programming" and you will find a bunch of definitions of it, among which the more notable ones are:

 

[From http://www.generic-programming.org/, arguably the most authoritative information source of GP:]

 

Generic Programming is a programming paradigm for developing efficient, reusable software libraries. Pioneered by Alexander Stepanov and David Musser, Generic Programming obtained its first major success when the Standard Template Library became part of the ANSI/ISO C++ standard. Since then, the Generic Programming paradigm has been used to develop many generic libraries.

 

The Generic Programming process focuses on finding commonality among similar implementations of the same algorithm, then providing suitable abstractions so that a single, generic algorithm can cover many concrete implementations. This process, called lifting, is repeated until the generic algorithm has reached a suitable level of abstraction, where it provides maximal reusability while still yielding efficient, concrete implementations. The abstractions themselves are expressed as requirements on the parameters to the generic algorithm.

 

From this definition we can derive some interesting conclusions.

 

First, Generic Programming is sort of invented by Alexander Stepanov and David Musser, who, and several others including Bjarne Stroustrup and Andrew Knoenig, introduced the whole C++ Templates system and essentially “the” STL into C++. This means that Generic Programming is tightly bounded to C++ Templates and the paradigms illustrated by STL.

 

Second, the gist of generic programming is finding commonality, and abstracting (or, lifting), such that one implementation can fit them all. This one should be of no surprise, because abstracting is one of the most common activities when we’re programming, be it OOP or GP. The more important question is, however, does generic programming provide an essentially new abstraction mechanism? We’ll get to that later.

 

Third, the major advantages of generic programming are considered to be reusability and (at the same time) efficiency. This is also confirmed when we redirect to boost, the most significant and modern C++ library in the world:

 

Generic programming is about generalizing software components so that they can be easily reused in a wide variety of situations. In C++, class and function templates are particularly effective mechanisms for generic programming because they make the generalization possible without sacrificing efficiency.

 

A more important thing to notice is that of the two characteristics of C++ generic programming (not the general notion of GP), efficiency is more important than reusability. It’s not that reusability isn’t important. It’s just that the popularity of C++ generic programming comes mainly from the fact that it implements generic programming so efficiently that the abstraction penalty is reduced to nearly zero.

 

However, the abovementioned words don’t define exactly what generic programming is. They are all sort of limited to the generic programming notion a la C ++. So here’s another quote, from ACM SIGPLAN – Workshop on Generic Programming:

 

Generic programming is about making programs more adaptable by making them more general. Generic programs often embody non-traditional kinds of polymorphism; ordinary programs are obtained from them by suitably instantiating their parameters. In contrast with normal programs, the parameters of a generic program are often quite rich in structure; for example they may be other programs, types or type constructors, class hierarchies, or even programming paradigms.

 

This might be the most “generic” definition of generic programming. Just as what the word “generic” has implied, generic programming is about making programs more general such that they could become more adaptable (to a wide range of use context).

 

Now it’s clear that C++ templates is one of the mechanism to approach generic programming, because STL, powered by C++ templates, is clearly adaptable to a very wide range of use context. We can, for instance, make a container of our own and plug it into the STL algorithm framework without a flicker.

 

Another important thing this definition shows is that generic programming makes use of a new kind of polymorphism compared to traditional OO techniques. Moreover, we often call this new kind of polymorphism “parameterized polymorphism”.

 

So…

So what is generic programming? So far it seems pretty clear that the point of GP is to make programs more “generic”. But wait, isn’t it the same thing OO used to do (and is doing now)? Consider the code below:

 

template<typename InputIter, typename UnaryFun>

UnaryFun for_each(InputIter first, InputIter last, UnaryFun func)

{

  for(;first != last; ++first) func(*first);

  return func;

}

 

This is the way C++ approaches GP. Now let us take a look at how we can use OO (in Java) to archive the same thing:

 

interface IUnaryFun

{

void invoke(Object o);

}

 

interface IInputIter

{

IInputIter preIncrement();

boolean equals(IInputIter otherIter);

… // other methods

}

 

IUnaryFun for_each(IInputIter first, IInputIter last, IUnaryFun func)

{

  for(;!first.equals(last); first.preIncrement())

func.invoke(*first);

  return func;

}

 

This actually works. Putting aside the awkwardness of using named function call instead of operator overloading, the problem of the Java version is threefold:

 

1) Efficiency. The C++ version is very efficient because it trades code size for efficiency, that is, for each combination of InputIter and UnaryFun, it will generate a separate instantiation. On the other hand, in light of the Golden Law of Software Engineering, the Java version works by adding a middle-layer (i.e. the interfaces). This is where it introduces the so-called abstraction penalty. The code is probably slow in some cases because it contains another layer of method-invoking. (However, we should note that, as a result, what we get is binary reusability. If we’re going to put the code into a binary library, this is probably the only way.)

 

Nominal Subtyping vs. Structural Subtyping

 

The Java version uses what is called “nominal subtyping”(or “named conformance”). Basically it means that to say that a type conforms with a concept (represented by interfaces (Java/C#), type classes(Haskell) or concepts(C++09) ) we must explicitly stipulate it (e.g. by deriving the type from the interface)

 

On the other hand, “structural subtyping”(or “structural conformance”) is what we do in C++ templates. That is, a type is compatible with a concept if it has every feature the concept has (usually this means that the type should implement every method the concept stipulates).

 

2) Coupling/Anti-OCP. This one is somewhat interesting. Say we have a home-made container class:

 

class MyContainer

{

}

 

class MyContainerIterator

{

}

 

for the Java version, if we want to plug this container into the algorithm (for_each) framework, we have to derive MyContainerIterator from IInputIterator, which requires us to change the code. So what’s the big deal? So we just change the code, and everything is OK again.

 

But image you didn’t write MyContainer and MyContainerIterator, instead, they’re from another library, and you want to hook the container and the algorithm together (i.e. an adaptation). What can you do? You can’t change the code because it’s somebody else’s code. The only thing you can do is writing a new iterator class that adapts MyContainerIterator (i.e. the adapter pattern), but that’s obviously not the ideal way.

 

Another thing you should notice is that even if MyContainerIterator has exactly the same methods as IInputIter (i.e. full structural conformance), we still need to use the awkward adapter pattern:

 

class AdaptedMyContainerIterator implements IInputIterator

{

public IInputIter preIncrement(){

iter.preIncrement();

return this;

  }

 

public boolean equals(IInputIter otherIter){

  return

iter.equals(((AdaptedMyContainerIterator)otherIter).iter);

  }

private MyContainerIterator iter;

}

 

Notice how the forwarding is done on a per-method basis, this is pretty cumbersome and fragile (let alone the fact that it invites much more typing).

 

Ideally, we should be able to simply say that MyContainerIterator is compatible with IInputIterator and leave the compiler to check if they really are compatible, which is already the case in Haskell, and will be in C++09.

 

Note that this problem can be alleviated by the so-called “Duck Typing”, which is a fundamental part of Ruby’s type system, and, however, isn’t without its own problem.

 

To quote David Vandevoorde & Nicolai M. Josuttis in “C++ Templates”:

 

“Polymorphism implemented via inheritance is bounded [in that] the interfaces of the types participating in the polymorphic behavior are predetermined by the design of the common base class (other terms for this concept are invasive or intrusive)”

 

“Polymorphism implemented via templates is unbounded [in that] the interfaces of the types participating in the polymorphic behavior are not predetermined (other terms for this concept are noninvasive or nonintrusive)”

    

3) Type-safety. This is an intensively-debated-upon aspect of programming language. And yet the problem of how important type-safety is is still wandering in the darkness. Every language has its own trade-off/balance between type-safety and dynamic-expressiveness. I know that this dichotomy between static-typing and dynamic-typing is far too inaccurate, but this problem is far beyond the scope of this post so I’ll just redirect you to this paper written by Erik Meijer & Peter Drayton for a starter. The reality is that type-safety is the principal motivation of adding generics into Java/C#.

 

The “Working” Definition

The definition below is from David Musser, one of the inventors of STL:

 

My working definition of generic programming is "programming with concepts," where a concept is defined as a family of abstractions that are all related by a common set of requirements. A large part of the activity of generic programming, particularly in the design of generic software components, consists of concept development -- identifying sets of requirements that are general enough to be met by a large family of abstractions but still restrictive enough that programs can be written that work efficiently with all members of the family.

 

This one isn’t really a definition of GP, but rather a description of what we do when we’re writing generic code. It seems that generic programming revolves around concept development, just as object-oriented programming revolves around type development.

 

An essentially new abstraction mechanism?

So let’s get back to the problem we dropped at the beginning of this post. The problem was: is GP an essentially new abstraction mechanism? Well, it depends on the way you look at it. If you look at it as a naturally perfect way to abstract procedural code (algorithms), as opposed to the fact that OO is the natural way to abstract entitative code (data structures), then yes, it is a new abstraction mechanism. However, if you look at it as a supplemental way of archiving efficiency, loose-coupling, and type-safety, which OO doesn’t get, in reusable algorithm development, then it is no new thing.

 

As a result, perhaps the right way to look at GP is not to focus on whether it’s something new, but whether it offers something different.

 

Further Reading

[1] What is Generic Programming

[2] An Extended Comparative Study of Language Supports for Generic Programming

转载于:https://my.oschina.net/abcijkxyz/blog/720567

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当读者有一定c/c++基础 推荐的阅读顺序: level 1 从<>开始,短小精悍,可以对c++能进一步了解其特性 以<>作字典和课外读物,因为太厚不可能一口气看完 level 2 然后从<>开始转职,这是圣经,请遵守10诫,要经常看,没事就拿来翻翻 接着是<>,个人认为Herb Sutter主席大人的语言表达能力不及Scott Meyers总是在教育第一线的好 顺下来就是<>和<>,请熟读并牢记各条款 当你读到这里,应该会有一股升级的冲动了 level 3 <>看过后如一缕清风扫去一直以来你对语言的疑惑,你终于能明白compiler到底都背着你做了些什么了,这本书要细细回味,比较难啃,最好反复看几遍,加深印象 看完上一本之后,这本<>会重演一次当年C++他爹在设计整个语言过程中的历程 level 4 <>是stl的字典,要什么都可以查得到 学c++不能不学stl,那么首先是<>,它和圣经一样是你日常行为的规范 <>让你从oo向gp转变 光用不行,我们还有必要了解stl的工作原理,那么<>会解决你所有的困惑 level 5 对于c++无非是oo和gp,想进一步提升oo,<>是一本主席这么多年的经验之谈,是很长esp的 一位stl高手是不能不去了解template的,<>是一本百科全书,足够你看完后对于gp游刃有余 <>是太过聪明的人写给明眼人看的 好书有很多,不能一一列举 以上我的读书经历,供各位参考。接下来的无非就是打怪练级,多听多写多看;boost、stl、loki这些都是利器,斩妖除魔,奉劝各位别再土法练钢了。 at last,无他,唯手熟尔。 忘了一本《thinking in C++》 也是经典系列之一 <>这本圣经的作者Scott Meyesr在给<>序言的时候高度的赞赏了Andrei同志的工作:C++社群对template的理解即将经历一次巨大的变化,我对它所说的任何事情,也许很快就会被认为是陈旧的、肤浅的、甚至是完全错的。 就我所知,template的世界还在变化,速度之快就像我1995年回避写它的时候一样。从发展的速度来看,我可能永远不会写有关template的技术书籍。幸运的是一些人比我勇敢,Andrei就是这样一位先锋。我想你会从此书得到很多收获。我自己就得到了很多——Scott Meyers September2000。 并且,Scott Meyers 在最近的Top5系列文章中,评价C++历史里面最重要5本书中、把Modern C++ Design列入其中,另外四本是它自己的effective c++、以及C++ Programming Language、甚至包括《设计模式》和《C++标准文档》。 显然,Scott Meyers已经作为一个顶尖大师的角度承认了<>的价值。 并且调侃地说,可以把是否使用其中模板方法定义为,现代C++使用者和非现代C++使用者,并且检讨了自己在早期版本Effective对模板的忽视,最后重申在新版本Effective第七章节加入大量对模板程序设计的段落,作为对这次失误的补偿。 并且,在这里要明确的是<>并不是一本泛型编成的书,也不是一本模板手册。其中提出了基于策略的设计方法,有计划和目的的使用了模板、面向对象和设计模式。虽然Andrei本人对模板的研究世界无人能敌,但对其他领域的作为也令人赞叹。 任何做游戏的人都不能忽视OpenAL把,你在开发者的名单里能看到Loki的名字:) 最近很忙,无时间写文章,小奉献一下书籍下载地址。虽然经过验证,但是不感肯定各位一定能下: 中文 http://www.itepub.net/html/ebookcn/2006/0523/40146.html 英文 http://dl.njfiw.gov.cn/books/C/Essential%20C
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值