《冒号课堂》连载之十二——超级范式

冒号课堂》连载之十二——超级范式:提升语言的级别

 

3.2  超级范式提升语言的级别

智能繁衍:机器人生产机器人。

——题记

关键词:编程范式;模板元编程;元编程;语言导向式编程;产生式编程

  :元编程简谈

预览

元编程作为超级范式的一个体现是,它能提升语言的级别。

如果说OOP的关键在于构造对象的概念,那么LOP的关键在于构造语言的语法。

离开IDE就无法编写、编译或调试的程序员,如同卸盔下马后便失去战斗力的武士,是残缺和孱弱的。

既然有重复的代码,不能从语法上提炼,不妨退一步从文字上提炼。

元程序将程序作为数据来对待,能自我发现、自我赋权和自我升级,有着其他程序所不具备的自觉性、自适应性和智能性,可以说是一种最高级的程序。

提问

什么是元编程?它与通常的编程有何不同?

元编程有何用处?它有哪些应用?

相比自编的元程序,用IDE自动生成的代码有什么缺陷?

语言导向式编程有何优点?它与元编程有何关系?

元编程与产生式编程有何异同?

为什么说元程序是一种最高级的程序?

讲解

问号忽然想起一事,问道:“有一本名为《C++模版元编程》的书,既然提到了模板,想来也属于泛型编程吧?”

冒号答道:“模板元编程即Template Metaprogramming,与泛型编程密切相关但自成一派,隶属于另一种编程范式元编程(Metaprogramming),简称MP。此处的前缀‘meta-’常译作‘元’,其实就是‘超级’、‘行而上’的意思。比如,元数据(Metadata)是关于数据的数据,元对象(Metaobject)是关于对象的对象,依此类推,元编程自然是关于程序的程序,或者说是编写、操纵程序的程序。”

叹号皱着眉:“听起来有点绕。”

冒号投影出如下代码

C++(元编程):

template <int N>

struct factorial

{

     enum { value = N * factorial<N - 1>::value };

};

 

template <>              // 特化(specialization

struct factorial<0>    // 递归中止

{

     enum { value = 1 };

};

 

void main()

{

    // 以下等价于 cout << 120 << endl;

    cout << factorial<5>::value << endl;

}

“以上用模板元编程实现了阶乘运算。”冒号讲解道,“与前面3种核心范式的阶乘实现有着根本的不同:这里阶乘的值是在编译时而非运行时计算出来的。换句话说,这段代码以模板形式通过编译器生成了新的代码,并在编译期间获得执行。”

叹号大惑不解:“这又说明什么呢?”

冒号并不直接回答:“假设你需要批量处理用户文档,其格式结构预先给定,但既不像CSV(逗号分隔)那么简单,也不像XML那么标准,并且用户随时可能改变格式标准,请问如何设计这段程序?”

叹号略一思索,便回答:“3大模块:阅读器读出输入文档,解析器按照格式标准去解析,处理器对解析结果进行处理。”

“显然关键在解析器,如果从头做起,那么问题至少有4个。”冒号扳着指头数,“第一,费时写解析器代码;第二,费时调试解析器代码;第三,如果用户更改格式标准,你得重复做上两件事;第四,如果这段程序是大型程序的一部分,任何改动都可能意味着软件的重新编译、连接、测试、打包、部署,等等。如果因为你的缘故公司不得不频频发布补丁包的话,你的饭碗恐怕是朝不保夕了。”

还是句号机灵:“既然谈到了元编程,一定是利用元编程,根据不同的格式标准自动生成相应的解析器代码。不过—此法虽一劳永逸,但难度似乎不小啊。”

“思路对头!”冒号赞许道,“大家听说过LexYacc吗?它们能根据格式标准生成相应的解析器代码。更妙的是,格式标准不限于静态数据,甚至可以含有动态指令!这意味着用户不仅能定义业务数据格式,还能定义业务流程。”

“这敢情好!”叹号兴奋地说。

“如果知道LexYacc本来就是编写编译器和解释器的工具,你就不会惊讶于它们的强大了。顺带说一句,编译器本身就是元编程的典型范例—把高级语言转化为汇编语言或机器语言的程序,不就是能写程序的程序吗?”冒号引申开来,“更进一步地,我们可以定义自己的领域特定语言DSL,更加灵活方便地处理客户逻辑。”

逗号有点糊涂了:“领域特定语言?就是前两堂课提到的非通用编程语言吧?怎么和元编程也扯上关系了?”

“不是扯上关系,而是它们之间本来就有着千丝万缕的联系。”冒号纠正着,“相比第3代的通用编程语言,领域特定语言由于其在应用范围上和语法上的限制而显得简单、针对性强,有时被成为‘小语言’(little language),也是一种特高级语言(very high-level programming language,简称VHLL),属于第4代编程语言。”

冒号说到此处,逗号猛地一拍脑门:“哦,我明白了。第4代语言最终须要编译为机器语言,而编译器就是元编程的应用。”

“你只说对了一半。”冒号不疾不缓地说,“DSL一般不会一步到位地编译为第1代的机器语言或第2代的汇编语言,而是通过现成的编译器生成器(compiler-compilercompiler generator)首先转化为第3代的高级语言。这样不仅大大降低了难度,也方便了程序的调试。刚才提到的YaccYet Another Compiler Compiler)便是这样的工具,能为解析器(parser)产生C程序,多用于Unix下的编程。更现代的工具如ANTLR (ANother Tool for Language Recognition),能生成CC++JavaC#Python等多种语言的源程序。”

引号立刻联想到:“我记得框架Hiberate的必备库中就含有antlr.jar文件,与这个ANTLR有关吗?”

“正是!”冒号很满意学员完美的配合,“Hiberate中的HQLHibernate Query Language)是典型的DSL,须要通过ANTLR来解析。你们可以验证一下,在HibernateAPI中有org.hibernate.hql.antlrpackage,但在其发布的源代码中相应的目录下却看不到一个Java源文件。却是为何?盖因此package中所有的源代码都是在ant build中自动生成的,这些非人工编辑的文件是不会放在版本控制中的。”

众人茅塞顿开。

句号想通了一个逻辑:“元编程作为超级范式的一个体现是,它能提升语言的级别。比如,有了编译器的存在,汇编语言升级为第3代高级语言;同样借助YaccANTLR之类的元编程工具,第3代语言可以升级为第4代的DSL语言。”

冒号并未就此止步:“将这一模式发挥到极致,便是更加激进的语言导向式编程[1]Language-Oriented Programming,简称LOP)。这种编程范式的思路是:在建立一套DSL体系之后,直接用它们来编写软件,尽量不用通用语言。”

叹号莫明其妙:“想法近乎疯狂啊!放着好端端的通用语言不用,先造一套专用语言,这么做划算吗?”

“如果一个大型系统涉及的领域十分专业,包含的业务逻辑十分复杂,为其定制DSL或许会磨刀不误砍柴工。我们通过图3-1和图3-2比较一下这种范式与主流编程范式的不同之处。”冒号映出新的投影

 

 

3-2  专用语言编程

“由于DSL比通用语言更简单、更抽象、更专业、更接近自然语言和声明式语言,开发效率显著提高,因此图中手工部分的时间相应减少。此外尤为关键的是,这种方式填补了专业程序员与业务分析员之间的鸿沟。要求一个非专业编程的业务分析员用DSL来开发固是勉为其难,但要做到读懂代码并审查其中的业务逻辑则已非难事。”冒号细解个中要点,“如果说OOP的关键在于构造对象的概念,那么LOP的关键在于构造语言的语法。有人认为LOP是继OOP之后的下一个重要的编程范式,我们不妨拭目以待。”

句号整理了一下头绪:“能不能这么说:如果处理一些复杂、非标准格式的文档,可以考虑用元编程;如果整个业务逻辑复杂多变,可以考虑利用现有的DSL或创造新的DSL来处理业务,即所谓的语言导向式编程。”

“总结得不错,不过特定格式的文档有了专门的解析器后,这种文档格式标准就可视为一种语言了,不是吗?这本质上就是DSL啊。”冒号出语点化。

句号顿时醒悟:“是啊,就像XMLHTML一样,能被程序认识的格式可不就是一种计算机语言嘛。”

冒号将话题延伸:“我们的想象力可以再狂野些,在文本DSL的基础上裹以图形界面,从而引进图形语言。如果再将部分业务逻辑开放给用户定制,那么你的客户会欣喜地发现,他们的经理只要点点鼠标就可以改变整个业务流程了,而这一切不仅不需要软件开发方或第3方的参与,连本公司的技术人员也免了。这时候倒是你的老板发愁了:你的设计太过完美,客户的后续开发费怕是赚不到啰。”

众人一乐。

问号继续发问:“还有其他元编程的应用吗?”

冒号随口举了几例:“元编程的例子比比皆是:许多IDEVisual StudioDelphiEclipse等均能通过向导、拖放控件等方式自动生成源码;UML建模工具将类图转换为代码;Servlet引擎将JSP转换为Java代码;包括SpringHibernateXDoclet在内的许多框架和工具都能从配置文件、annotation/attribute等中产生代码。”

引号仍不知足:“这些应用虽然典型,但都是些开发工具、框架引擎之类的基础软件,有没有平时编程就能用到的例子?”

“当然有!”冒号坚定地答复,“有时程序中会出现大量的重复代码,却囿于语法上的限制无法进一步抽象化和模块化。如果采用手工编写或单纯拷贝的方法,既费时又易错,显为下策。有时可借助IDE内置的代码生成功能,但一方面局限性很大,另一方面无法自动化版本化。”

问号插问:“什么叫版本化?”

冒号解释:“理想情况下,一个程序员对程序的贡献都应该保存在版本控制系统(version control system)中,以便跟踪、比较、改进、借鉴和再生成。在IDE下自动生成的代码本身可以被记录,但产生代码时的行为却不能被记录,几次简单的鼠标动作就能产生较大的代码差别,使得版本比较的意义大打折扣。顺便说一句,离开IDE就无法编写、编译或调试的程序员,如同卸盔下马后便失去战斗力的武士,是残缺和孱弱的。”

问号有些明白了:“这是因为鼠标行为本身在代码中是没有痕迹的。”

“不仅是鼠标行为,有些需要键盘交互的行为也是没有痕迹的。比如在命令行下用debugger来调试的行为无法被记录,也难以重复和自动化,只能作为权宜之策。相比之下,日志(logging)和单元测试(unit test)具有明显的优势[2]。”冒号答完,立马重返主题,“回到上面的问题,既然有重复的代码,不能从语法上提炼,不妨退一步从文字上提炼。我们可以利用AWKPerl之类的擅长文字处理的脚本语言,当然也可以用JavaC等非脚本语言,再辅以XSLT之类的模板语言,自动生成重复代码。这样不仅灵活性强,而且生成代码的代码—也就是元程序代码可以被重用,元程序的数据来源也能版本化。”

句号深得要领:“就像Hibernate中的antlr包一样,真正的源码反而不在版本控制中了。一方面没有保存的必要—可以自动生成;另一方面没有比较的必要—元程序的数据来源的变化比实际源码的变化更简明、更直观。”

冒号继续推进:“另外,有时程序的结构需要动态改变,而C++JavaC#等静态语言是不允许动态变更类的成员或实现代码的,利用元编程便可突破这种限制。”

逗号恍然大悟:“原来元编程就是编写能自动生成源代码的程序。”

“也不尽然。”冒号马上修正道,“自动生成源代码的编程也属于另一种编程范式—产生式编程(Generative Programming[3]的范畴。区别在于后者更看重代码的生成,而元编程看重的是生成代码的可执行性。另外,除了在编译期间生成源代码的静态元编程,还有能在运行期间修改程序的动态元编程。从低级的汇编语言到一些高级的动态语言如PerlPythonRubyJavaScriptLispProlog等均支持此类功能。比如,许多脚本语言都提供eval函数,可以在运行时将字符串作为表达式来运算[4]。”

问号突然问道:“编写病毒算不算元编程?”

“编写一个只是删除或感染文件的病毒,不必用到元编程。但如果要开发一个能自我变异的智能病毒,那就需要元编程了。不过你要是把元编程用在这方面,可别说是我教的。”冒号开了个玩笑。

引号自言自语:“程序的程序,就是程序的平方。”

“也可以是程序的立方,4次方……理论上是无限次方。在传统的编程中,运算是动态的,但程序本身是静态的;在元编程中,二者都是动态的。元程序将程序作为数据来对待,能自我发现、自我赋权和自我升级,有着其他程序所不具备的自觉性自适应性智能性,可以说是一种最高级的程序。它要求编程者超越常规的编程思维,在一种崭新的高度上理解编程。想象一下吧!”冒号激情勃发,“如果有一天机器人能自我学习、自我完善,甚至能生产新的机器人,实现‘智能繁衍’,是不是很美妙?”

“我怎么觉得特恐怖呢?岂止是程序员,所有地球人的饭碗都会被它们砸光了。”叹号此言一出,众皆忍俊不禁。

总结

元编程是编写、操纵程序的程序。在传统的编程中,运算是动态的,但程序本身是静态的;在元编程中,二者都是动态的。

元编程能减少手工编程,突破原语言的语法限制,提升语言的抽象级别与灵活性,从而提高程序员的生产效率。

元编程有诸多应用:许多开发工具、框架引擎之类的基础软件都有自动生成源代码的功能;创造DSL以便更高效地处理专门领域的业务;自动生成重复代码;动态改变程序的语句、函数,类,等等。

IDE下自动生成的代码通常局限性大且可读性差,小操作可能造成的源码上的大差异,削弱了版本控制的意义。用自编的无需人机交互的元程序来生成代码,只须将元程序的数据来源版本化,简明而直观。同时由于元程序可以随时修改,因此局限性小,更加灵活。

语言导向式编程(LOP)通过创建一套专用语言DSL来编写程序。相比通用语言,DSL更简单、更抽象、更专业、更接近自然语言和声明式语言、开发效率更高,同时有助于专业程序员与业务分析员之间的合作。

语言导向式编程一般通过元编程将专用语言转化为通用语言。

产生式编程与静态元编程都能自动生成源代码。产生式编程强调代码的生成,元编程强调生成代码的可执行性。此外,动态元编程并不生成源代码,但能在运行期间修改程序。

元程序将程序作为数据来对待,有着其他程序所不具备的自觉性、自适应性和智能性,可以说是一种最高级的程序。

参考

[1]  Martin WardLanguage Oriented Programming
               http://www.cse.dmu.ac.uk/~mward/martin/papers/middle-out-t.pdf

[2]  Sergey DmitrievLanguage Oriented Programming: The Next Programming Paradigm
               http://www.onboard.jetbrains.com/is1/articles/04/10/lop/mps.pdf

[3]  WikipediaMetaprogramming
         http://en.wikipedia.org/wiki/Metaprogramming

插语

[1]Martin Ward最早提出此范式,见参考文献[1]

[2]虽然调试与日志和测试不是一码事,但合理的日志和单元测试能大量减少调试工作。

[3]也译作“生成式编程”,属于自动编程Automatic Programming)范畴。

[4]考虑到eval过于广泛和强大,有些动态语言还提供其他更明确和更安全的元编程机制,如JavaScript可用字符串来构建FunctionRuby更是提供了define_methodinstance_eval class_evalmodule_eval等诸多元编程方法。

           欢迎转载,转载时请注明:

本文出自电子工业出版社博文视点(武汉)新书《冒号课堂——编程范式与OOP思想》。

 http://www.china-pub.com/196068&ref=ps

 http://www.douban.com/subject/4031906/

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值