Language Oriented Programming : The Next Programming Paradigm
Sergey Dmitriev, JetBrains
现在是软件开发中开始下一次技术革命的时候了,而这次革命的轮廓正变得越来越清晰。下一代编程范型也在接近我们,但仍然没有完全成形--不同的部分有不同的名称:Intentional programming, MDA, generative programming, 等等;我建议把把所有这些新方法归并为一个名字: ‘language-oriented programming’, 而本文将阐述这种新的编程范型的主要原则
今天主流的编程方法有一些内在的假定像脖子上的绳索一样桎梏着我们,尽管大部分程序员还没有意识到它;即使算上在编程领域取得的所有进步,我们也仍然处于石器时代;我们有我们信赖的石斧(面向对象编程),能够满足我们的需要,但是当用它来对付最困难的问题时,它会裂成碎屑;为了超越石器前进,我们必须驯服烈火,只有这样,我们才能铸造出新的工具,激发一个创作的新时代,和新技术的爆发
我将讨论编程的局限,它强迫程序员像计算机一样思考,而不是令计算机像程序员一样思考;这是严重的,根深蒂固的局限,需要花费巨大的努力去克服它;当我说这将是编程中下一个大的范型转换时我并没有自命不凡;我们需要彻底重新定义我们编写程序的方法
本文中,我表述了我的观点和我当前在Language Oriented Programming (LOP)上的工作;首先我将展示目前主流编程方法的错误,然后我会使用示例来解释LOP的概念,它们基于我已有的一个LOP的实现:the Meta Programming System (MPS). 本文有意只是给你一个对LOP的惊鸿一瞥,目的是激发你对这个思想的兴趣,并希望能够得到反馈和讨论
Part I. LANGUAGE ORIENTED PROGRAMMING OVERVIEW
Language Oriented Programming and the Meta Programming System
理想的,做一个程序员意味着我可以对计算机做任何事情,我有完全的自由,完全的控制;但实际上,今天的程序员只有非常受限的自由;当然,我确实可以在计算机上做任何事情,但其中一些事情花费了我许多年的努力,而它们实际上只需要少的多的时间;一定有什么事情不对劲
程序员被限制是因为他们深深依赖于那些他们不能轻易改变的编程基础设施:编程语言和开发环境;如果我需要一些语言的扩展,我只能等待语言的设计者去更新它;如果我需要我的IDE有一些额外的强大功能,我只能等待供应商来添加新特性;就是这些依赖限制了我完全的自由;当然,我可以写我自己的编译器和IDE,实际上,这也是我启动了IntelliJ IDEA的原因,因为我厌倦了依赖现有的弱弱的Java IDE;但是,这会花费大量的时间和努力,并且显而易见,对大部分程序员来说是不可行的;理论上的自由和实际的自由之间存在巨大的差异;下文中当我谈到自由时,我指的是实际的自由
获得自由的途径是减少我们的依赖层次;例如,Java的一个主要目标是减少对操作系统的依赖,给开发者在不同操作系统上部署的自由;因此,为了在语言和环境之上获得自由,我们需要减少对它们的依赖
为什么这是一个问题呢?任何general-purpose的语言,像Java和C++,给了我们用计算机做任何事情的能力;这是事实,至少理论上是这样,但是,general-purpose的语言趋向于如同后面我将讲到的般生产效率低下;作为一种替代,我们可以使用domain-specific languages(DSLs,aka ‘little languages’),它们被精心设计成在特定问题域具有高度生产率,比如用SQL编写数据库查询;DSLs的强大之处,领域相关,也正是它们的弱处,因为任何真实世界中的程序都会包括许多不同的领域
general-purpose 和 domainspecific 之间,并不是对立的;我需要所有的自由,我希望能够做任何事情,同时有很高的生产效率;目前为止还没有什么好方法能够做到这点;理想情况下,我能够为程序的各个特定部分使用不同的语言,而它们能够融洽的一起工作,并且开发环境会完全支持这些语言,包括重构,代码补全,导航,以及主流语言具有的所有其它生产力工具
为了获得这种独立性,我需要有创建、重用、修改语言和环境的自由;为了使这种自由是可行的,它需要很容易的被获得;如果我们解决了易于进行语言和环境的开发的问题,对程序员来说将是一个巨大的进步;这就是Language Oriented Programming的切入点
要理解Language Oriented Programming是什么,让我们首先看一下今天的主流编程方法,它基本上是这样:
思考: 你需要编程解决一个问题,因此你在你的头脑里形成了如何解决这个问题的概念模型
选择: 你选择了某种general-purpose的语言来编写解决方案
编程: 你通过将你的概念模型艰难的映射到编程语言来编写解决方案
编程这一步是瓶颈所在,因为大部分情况映射不是容易的和自然的;这种方法在程序员表达复杂的设计方面已经被证明是低效的;相对的,下面是LOP的工作方式:
思考: 你需要编程解决一个问题,因此你在你的头脑里形成了如何解决这个问题的概念模型
选择: 你选择了某些特定的DSLs来编写解决方案
创建: 如果没有合适的DSL适合你的问题,你便创建一种DSL来适应你的问题
编程: 你通过将你的概念模型相对直接的映射到DSLs来编写解决方案
现在,编程这一步is much less of a 瓶颈了,因为DSLs大大简化了如何将问题翻译成某种计算机能够理解的东西;看起来困难已经简单的转移到了“创建”这一步,然而,通过联合使用工具支持和将LOP应用到自身,将使这一步更加简单
LOP背后的动机基本是这样的:我想用我正试图解决的问题相关的概念和意图的词汇来工作,而不是被迫将我的思想翻译成某种general-purpose的语言所能理解的概念(比如:类,方法,循环,条件,等等...);为了达到这个目标,我需要使用domain-specific languages;怎样得到它们呢?创建它们;
我已经开始开发一个通用的平台(the Meta Programming System)来设计domainspecific languages,带有工具支持和开发环境;它将允许程序员像现在编写程序一样容易的来定义语言;这个平台将完全支持LOP,给程序员为程序的每一部分选择使用最合适的语言的自由,而不是将他们绑在某种固定的general-purpose的编程语言上
MPS只是Language Oriented Programming的一个示例,尽管我在这里使用MPS来做示例,而实际上LOP可以用许多不同的方法来实现,你自己就可能知道一些替代方法;LOP的概念不等同于它的实现,就像OOP的概念不等同于Java或C++或Smalltalk一样
What Is Wrong with Mainstream Programming
你知道这则古老的谚语:"If it ain't broke, don't fix it". 主流编程方法很明显不完整,我见过它带来的很多问题,而大部分滋生于这样一个事实:general-purpose的语言没有一种方法来完全支持任意的领域,同样也没有一种统一的domain-specific language;下面是将被LOP解决的主流编程中三个最糟糕的问题:
Time Delay to Implement Ideas
对我来说,最严重的问题是,在我确切的知道如何解决一个问题,和我通过一个程序成功的向计算机传达解决方案之间,有一个很长的时间差;我可以用几个小时的时间向另外的程序员解释问题和解决方案,而将解决方案编码到计算机中将花费长的多的时间;这是因为对另外的程序员,我可以使用表达能力非常丰富的自然语言,而对计算机,我只能使用某种表达能力差很多的general-purpose的编程语言;今天的编程语言只能表达几十种概念,而自然语言能够简洁的表达千万种概念;因此,向另外的程序员解释问题,我可以表达很高层的思想,而对计算机,我必须表达每一步的每一个细节
在主流编程中,大部分花在“编程”上的时间,实际上是在寻找用编程层次的抽象的术语来表达自然语言的概念的方法,而这是很困难的,没多少创造性的,或多或少是一种时间的浪费
举个例子,今天大量的开发时间花费在面向对象的设计(OOD)上,在程序员表达类、继承、关联等方面这确实是一种还算有创造性的过程;这项实践的目的是用面向对象的术语,如类和方法,来表达程序;OOD的过程是必要的,因为诸如类和方法等是面向对象语言能够理解的仅有的抽象,它看起来是必要和有创造性的,但是使用Language Oriented Programming,OOD根本就不需要
Understanding and Maintaining Existing Code
下一个问题是理解和维护现存代码;不管它是另一个程序员写的还是我写的,问题都一样;因为general-purpose的语言需要我把高层的领域概念翻译为低层的编程语言特性,在最终的程序中,很多高度概括的视角、蓝图都丢失了;当我在以后重新翻阅程序时,我不得不通过逆向工程来了解我最初的意图是什么,我头脑中的模型是什么;至少,我必须在脑海中重新建造最初在翻译到general-purpose的编程语言的过程中丢失的信息
解决这个问题的传统方法是写注释或其它形式的文档来记录设计信息和模型信息,已经有几个方面的因素证明了这是一种脆弱的解决方案,至少包括编写这些辅助文档的成本、以及文档和代码逐渐不同步的趋势;并且,还有一个没被广泛认识到的事实,就是文档并不能直接连接到它所记录的概念;注释和源代码被绑定到同一个地方,但是概念可能在源代码的多个地方被表达;其它类型的文档彻底从源代码中分离出来,只能间接的引用源代码;理想情况下,代码应该是自我描述的,我应该只阅读代码本身来理解代码,而不是什么注释和外部的文档
Domain Learning Curve
第三个主要的问题是对语言进行领域相关的扩展;例如,在OOP中扩展语言的主要方法是使用类库;问题是类库不是用领域概念相关的术语来表达的,而是用低层的general-purpose的抽象诸如类和方法等来表达;因此,库很少能够直接表述领域概念,它们必须引入额外的枝节(如一个类的运行时行为)来完成到领域概念的映射;两个很好的常见例子是GUI库和Database库
学习这些类库不是一项简单的任务,即使你是个领域专家;因为从领域到语言的映射不是直接的,你必须学习这种映射;这意味着一个陡峭的学习曲线;通常我们试图用大量的指南和文档来解决这个问题,但是学习这些将花费大量时间;当一个类库变得复杂的时候,它也变得更难以学习,程序员将因此失去学习它的动机
甚至当掌握了这种复杂的映射之后,依然还会很容易的误用类库,因为开发环境(像编译器和编辑器)不能帮助你正确的使用类库,对这些工具来说,调用一个GUI对象的方法和调用一个DB对象的方法是一样的:它们都只是对象上的方法调用,没有任何更多的意思;记住哪些类和方法应该被调用,以什么顺序被调用,等等,都是使用者的责任
甚至即使你既是领域专家又是类库的使用专家,也仍然有使用类库编写的程序十分冗长的问题;相对简单的领域概念需要复杂的措施来正确的调用;例如,任何用过Swing的开发者都清楚这一点;编写简单的程序就已经花费太长的时间了,复杂的程序甚至更糟
Details of LOP
What Is a Program in LOP?
今天,百分之九十九的程序员认为编程就是编写一串计算机能够执行的指令集;我们被教育说计算机建立在图灵机模型之上,因此它们用指令集的术语来“思考”;但是这种编程的观点是有缺陷的,它混淆了编程的目的和手段;我将为你演示LOP为什么优于传统编程方法,但首先我必须澄清以下事实:一个LOP的程序,不是一串指令集;那么它是什么呢?
当我有一个问题要解决,我在头脑中思考解决方案,这个解决方案用单词、标记、概念、思想,或者任何你喜欢的称呼来表述,它是我头脑中如何解决问题的模型;我几乎从未把它们想象成一堆指令集,而是我正在工作的领域中特定的具有内在联系的概念的集合;例如,当我思考GUI领域时,我想象“这个按钮到那边去,这个输入域到这边来,这个组合框里面需要有一些数据的列表”;我甚至只是在头脑中把它画出来,根本不用任何言语
我之所以认为这种意念模型是一种解决方案是因为我能够用足够的细节向另一个程序员解释这个模型,使他能够坐下来编写一个解决这个问题的程序(比如用Java);我不需要非得用编程语言的术语来解释这个方案,它可以是任意形式;比如,为了解释如何布局一个GUI的窗体,我只需要画出这个窗体;如果绘画有足够的细节,绘画本身就代表了解决方案;这种领域相关的表述应该就是程序。换句话说,应该有一种方法允许我们使用这种表述作为真正的程序,而不仅仅是与其它程序员交流的手段;于是这便导出了我对程序非正式的定义:一个程序是任何对一个问题无歧义的解决方案,或者,更精确一点:一个程序是对某个领域的某个问题的解决方案的任何使用领域相关概念表达的,精确定义的模型
这就是我认为程序员应该拥有创建他们自己的语言的自由的主要原因:这样他们就能够用更加自然的形式来表达解决方案;General-purpose的语言是无歧义的,但是太冗余和易于出错;自然语言(如英语)表达能力十分丰富,但目前它难以使用因为它太不精确太不形式化了;我们需要能够容易的创建形式化的,精确定义的,领域相关的语言;因此Language Oriented Programming将不只是编写程序,还包括创建用来编写程序的语言;我们的程序将被编写的更接近问题域而不是计算机指令集领域,因此它们将非常容易的被编写
Programs and Text
人们习惯性的认为程序是作为文本来存储的,也就是说,一个字节流;为什么不应该是呢?毕竟有无数的工具来编辑、显示、操作文本;今天的编程语言的核心部分是文法器,解析器,编译器和面向行的调试器;但是程序的文本只是程序的一种表现形式;程序不是文本;强行把程序塞到文本里引起了大量你可能还不知道的问题;我们需要一种不同的方法来存储并和我们程序一起工作
当编译器编译源代码时,它把文本解析成称作抽象语法树的树状结构;当程序员阅读源代码时,他们在脑海中做了本质上相同的事情;我们仍然不得不考虑程序的树状结构;这就是为什么我们要有花括号,方括号,圆括号等;这也是为什么我们需要格式化和缩进代码和遵守编码规范,因为这样就能够更容易的阅读源代码
我们为什么使用文本存储呢?因为当前,阅读和编辑程序最方便和最通用的方法还是使用文本编辑器;但是我们会为此付出代价,因为程序的文本表示有重大的缺点,其中最重要的是基于文本的编程语言非常难于扩展;如果程序以文本的形式存储,你就会需要一个无歧义的文法器来解析程序;当为语言加入新特性时,维护语言无二义性的扩展变得日益困难;我们将需要发明更多类型的括号、操作符、关键字、顺序规则、嵌套,等等;语言的设计者们花费了无数时间来思考语法,并试图发现扩展语言的新方法
如果我们打算让创建语言变得容易,我们就需要将程序的表示和存储从程序本身分离开;我们应该直接将程序存为结构图,因为这允许我们对语言做任何我们喜欢的扩展;有时,我们甚至根本不需要考虑文本存储;今天的一个很好的例子是Excel spreadsheet.百分之九十九的人根本不需要处理存储格式,当这成为问题时总会有各种导入导出功能可用;今天我们使用文本的真正原因是我们没有比文本编辑器更好的编辑器,但是我们可以改变这一点
问题是文本编辑器很愚蠢,并且不知道如何与程序的图状结构一起工作;但是使用正确的工具,编辑器将能够直接和图状结构一起工作,并且能够让我们自由的使用任何编辑器提供的我们喜欢的可视化表现形式;我们可以把程序做成文本、表、图、树、或其它任何形式;我们甚至能为不同目的使用不同的表现形式,比方说,图形化表示用来浏览,文本化表示用来编辑;我们能够为代码的不同部分使用领域相关的表示,比如为数学公式使用图形化的数学符号,为图表使用图形化的图表,为spreadsheets使用行和列,等等;我们能够为问题域使用最合适的表现形式,可以是文本,但不限于文本;最好的表现形式依赖于我们如何思考问题域;表现形式的灵活性也将使我们的编辑器比以往更加强大,因为不同的表现形式有不同的方式去编辑它们
What Is a Language in LOP?
最后,我应该阐明我认为的“语言”是什么;在LOP中,一种语言是通过三个主要的要素来定义的:结构、编辑器、和语义;结构定义了抽象语法、支持的概念、以及如何安排它们;编辑器定义了具体的语法,如何描绘和编辑语言;语义定义了行为,它如何被解释,和/或它如何被转换成可执行代码;当然,语言还可以有其它方面,比如约束和类型系统
Part II. INTRODUCTION TO META PROGRAMMING SYSTEM
Creating Languages in MPS
我已经解释了为什么我们需要容易的创建新的语言,但是,我们如何才能让它容易呢?如果你turn around这个问题,并且把Language Oriented Programming应用于它自身,你会很快看到答案;This calls for a little self-referential bootstrapping, which can seem tricky, but be patient. 一旦你理解了这个,你将得到LOP真正的力量(一个LOP的元层次)
回顾一下LOP的理念:使创建DSLs更容易,而这些DSLs将使编写程序更容易;但就像我已经说明的,LOP中的‘程序’不局限的意味着你用过的典型的“一堆指令集”的程序;对某个领域中某个问题任何无二义性的解决方案都是‘程序’;因此如果你设想一下“创建新语言”这个领域,那么这个领域中的‘程序’,本身就是一种新语言的定义,可以作为一个解决方案来思考,就像任何其它领域的解决方案一样;
因此,应用LOP的思想,使“创建新语言”更容易的方法,就是创建一种特定的专注于“创建新语言”这个领域的DSL;通过应用这些language-building DSL,我们可以使制造新语言更容易;让我们看几种language-building语言的例子,使你更好的理解它们是如何工作的;这里只是一个概述,以后的文章我会更详细的描述它们
Structure Language
最小最少,我们需要定义新语言的‘结构’;这是我们何以能够编写“精确定义”的程序的原因;语言的结构并不意味着它的文本形式的文法--像我提到过的,这种语言甚至根本就没有文本表示而只有图形化表示
在实践LOP的时候,大部分情况下,你会工作在两个层次的编程中:元层次和程序层次;你在元层次中定义语言,在程序层次中编写程序;当定义一种新语言的结构时,你会使用一种language-structure DSL来定义你的新语言,而这时,你将同时工作在这种language-structure DSL的程序层次和新语言的元层次中
在MPS中,程序层次的每个节点都有一种“类型”,简单的连接到元层次的另一个节点;程序层次的这个节点被称作这种类型的一个“实例”;元层次中的“类型”节点则定义了这种类型的实例能够拥有的关系和属性;描述这种元层次语言结构的语言,就被简单的称为“Structure Language”
用Structure Language定义一种语言的抽象语法,你应该只是枚举这种语言所有的类型;类型简单的表示了这种语言支持的特性或者概念;每个概念应该用它的名字、实例的内部属性、实例与其它节点之间的关系(通常是连接)来定义
存在两种可能的关联;第一种是类似聚合的关联,它形成了概念模型的父子树结构;第二种是非聚合的,自由形式的关联,它可以连接到系统中任何其它的节点;关联有两个端点:源和目标;关联有角色,你可以定义每个角色的名称、每个端点的多重性,每个目标节点的类型;多重性可以是1, 0..1, 0..n, 1..n等,让你能够约束关联可以创建多少连接;关联的目标类型可以被用来约束哪些类型的节点可以被连接在一起
因此,使用新语言编写程序包括:创建语言中概念的实例、为实例的属性赋值、根据语言概念定义的关系将程序中的节点连接在一起;所有这些将会被强大的编辑器支持,你能够为你的语言定义这种编辑器
Editor Language
那么,编写和操作概念模型的界面应该是什么呢?我们的语言需要几种类型的编辑器,但是我们不想要一个通用的编辑器;经验表明通用的编辑器不能像我们希望的那样有用;我们希望快速的编写模型,因此我们需要专为我们的语言概念定做的特殊的编辑器;一定程度上,编辑器是语言的一部分,而我们的目标是容易的创建新语言,那么创建新的编辑器也应该很容易;本质上,我们需要一种创建编辑器的语言,在MPS中,它被称为Editor Language
当人们听我说我们的程序将存储为图形并且我们需要特定的编辑器,我确信很多人认为我将要谈到图形编辑器,事实不是这样子的;尽管程序是图形形式,编辑器却不一定非得将程序描绘成图形;事实上,只有少数情况下图形编辑器才是有用的(也就是说,当它合适的时候,比如对数据库表);相反,我们的Editor Language有更好的灵感来源,讽刺的是,它来自文本编辑器
如果你用文本编辑器浏览一个典型的程序,你可以想象编辑器被分成了矩形单元;一些单元包含必需的标识如关键字、花括号、圆括号等,其它的单元包含用户定义的标识,如类和方法的名称;大的单元由小的单元组成,像方法块包含语句,而语句可能包含自己的嵌套块;事实上,任何主流编程语言中任何良好构造的程序都可以分解为矩形单元的集合;那么,在Editor Language中,你不需要想象这些单元,因为编辑器就是简单的由矩形单元组成的
单元的使用有一些有趣的优点;首先,当直接工作在程序图形而不是程序文本上时,单元可以完美的模仿甚至超过标准的文本编辑器;第二,单元不局限于文本,你可以往单元里塞进颜色选择器、数学符号、图表、矢量图、或任何别的什么;最后,这种单元形式的layout是可选的,程序员可以提供不同的机制,单元形式的layout只是一种有用的缺省设置
因此,Editor Language帮助你定义语言中每个概念对应的单元的layout;你可以定义哪些部分是不变的,像括号或其它修饰符号,哪些是可变的,需要用户去定义的;Editor Language也帮助你在你自己的编辑器中加入强大的特性,像自动完成、重构、导航、语法加亮、错误加亮、以及任何其它你想到的事情;因此你能够增加现在的编辑器如IntelliJ IDEA等拥有的功能到你自己的语言中;这是可能的,因为程序和语言被构造为图形,而我们有专门的Editor Language帮助我们创造强大的编辑器
Transformation Language
Structure Language和Editor Language已经共同提供了一些功能,你能够用它们和其他人交流思想,比如画UML图,或者编写其它类型的文档;然而,大部分时间我们是想让我们的代码做点什么,因此,我们必须找到一种方法让它能够执行;有两种主要的方式来做这件事情:解释和编译
DSLs支持的解释方式帮助定义计算机应该如何解释程序,DSLs支持的编译方式帮助定义如何为程序产生可执行代码;我将在以后的文章中讨论对解释方式的支持,现在我想说明一下MPS是如何支持编译方式的
编译意味着拿到源代码,并从中产生某种形式的可执行代码;对于结果代码有多种可能的形式;为产生可执行代码,你可以生成本地机器码,也可以生成虚拟机字节码;或者,你可以生成另外一种语言的源代码(比如Java,C++),然后用现有的编译器转换为可执行代码;类似的,你甚至可以产生某种解释型语言的源代码,用现有的解释器解释执行
为了避免处理这么广泛的目标格式,我们的方法是用MPS来做每一件事;首先,你在MPS中使用Structure Language定义一种目标语言,这种目标语言和目标格式之间应该有直接的一对一的映射;例如,如果你的目标格式是机器码,你应该用MPS定义一种对应机器码的目标语言;如果目标格式是Java源代码,你应该定义一种类Java的目标语言;目标语言不必支持目标格式所有的功能特性,只为你需要的语言特性进行简单的一对一的映射即可
那么现在,编译分为两个阶段:一个简单的从目标语言到最终结果的翻译,一个更复杂的从最初的原始语言到中间目标语言的转换;翻译阶段是微不足道的,因此我们把精力集中于更有意思的转换阶段;至少,现在的问题简化为了如何将模型从一种语言转换到另一种语言;但是,有可能源语言与目标语言是完全不同的,导致转换非常复杂,比如映射一个源节点到许多散布在目标模型中的目标节点;我们想让定义转换尽可能的简单容易,因此我们需要一种模型转换DSL来帮助我们;在MPS中,这种DSL被称为Transformation Language
代码生成有三种主要的方法,我们将结合使用它们来定义模型转换;第一种是遍历方式,你枚举源模型中所有节点,检视每一个,并基于检视到的信息生成目标模型中的一些目标节点;第二种方式是使用模板和宏来定义如何生成目标语言;第三种方式是使用模式匹配来查找在源模型中的哪些节点上应用转换
我们通过定义DSLs把这些方式结合起来以支持任何一种方法;这些DSLs将一起工作来帮助你定义从一种语言到另一种语言的转换;例如,遍历方式激发了Model Query Language的灵感,它使枚举节点和从概念模型中收集信息变得简单容易;你可以把它想象成某种针对概念模型的SQL;做为一种额外的奖赏,拥有一种强大的查询语言不只是对代码生成有用(例如,能够使编辑器更聪明)
Templates
模板方法工作方式类似Velocity或者XSLT;模板看起来很像目标语言,但是允许你在模板的任何部分中添加宏;宏本质上是当运行转换的时候被执行的代码段;宏允许你检视源模型(使用Model Query Language),并使用得到的信息对模板进行“填空”,得到最终的目标代码
在图5中,你可以看到为概念“Property”生成Java代码的模板的定义,模板为属性添加了field declarations, getters, setters等;这个模板是将代码从Structure Language转换为Java的生成器的一部分
既然模板看起来像目标语言,你可以想象模板是用某种基于目标语言的特殊的语言编写的;这也是它事实上的工作方式;我们实际上使用一个生成器来为你生成模板语言,而不是手工为每一种可能的目标语言创建模板语言;它基本上是复制目标语言,并添加所有模板特定的特性,诸如宏等;甚至模板编辑器也是从目标语言编辑器产生的,因此你同样不需要处理代码
当你使用一种模板语言的时候,你可以认为它是用目标语言编写的,只是某些部分的代码是参数化的,或者是由宏来计算的;这种技术极大的帮助简化了代码生成;模板还可以用在其它任务上,如重构、代码优化、还有更多...
Patterns
模型的模式匹配方法给我们一种作为Model Query Language的代替的查找模型的强大方法;你可以把模式想象成概念模型的正则表达式;与模板方法类似,我们基于源语言产生模式语言;模式语言看起来像源语言,只是添加了一些特性,来帮助你定义处理复杂源模型匹配的灵活的标准;你可以把这种方法想象成一种强大的“查找替换”的技术;再一次,模式语言不只是对代码生成有用,例如,它们在为源语言编辑器编写自动化的代码检查工具方面非常有用
记住Model Query Language, template languages, 和pattern languages都由强大的编辑器支持其自动完成、重构、引用检查、错误勘测、等等;即使复杂的查询、宏、模式,都可以很容易的编写;代码生成从来没有这么强大过
Using Languages Together
前面有关代码生成的章节带来了一些关于这些语言如何一起工作的有意思的问题;事实上有几种方法能让语言一起工作;在MPS中,所有的概念模型都互相知晓;既然语言也是概念模型,那么便意味着所有的语言都彼此知晓,可以潜在的被连接在一起
语言彼此之间可以有不同的关系;你能够通过扩展现存的语言来创建新语言,继承所有的概念,修改其中的一些,并加入你自己的概念;一种语言可以引用其它语言中的概念;你甚至能将一种语言插入到另一种语言中去;我将在以后的文章中讨论进一步的细节
Platforms, Frameworks, Libraries, and Languages
我们支持Language Oriented Programming的系统需要比元编程能力更多的功能才能更有用;它应该提供程序员依赖于当前的编程语言提供的所有事物:集合,用户界面,网络,数据库连接,等等;程序员不止是单单基于语言本身来选择语言;例如,Java的大部分功能不是语言提供的,而是有成千上万的framework和API供Java程序员选择;他们买的不是Java语言,而是整个Java平台;MPS也将有一个它自己的支持平台
在我进入细节前,我们先简要谈一下frameworks;什么是framework?在主流的编程中,它通常意味着一堆类和方法打包成的一个类库;让我们更近一点的观察这一点,看看在LOP的镜片下我们会看到什么
我们为什么想要把类和方法打包成库呢?程序员会背诵他们的教授曾经告诉他们的:“复用”;但这只是在原来的位置上留下了另一个问题:我们为什么要复用类库?答案是类库在解决特定类型的问题方面很有用,如制作用户界面,访问数据库等等;你可以说类库对应着某个领域;你瞧,我们看到了联系,Class libraries are wannabe DSLs!这个悲伤的事实真令我沮丧
今天的Domain-specific languages以类库的形式存在,除了它们不是语言,没有语言的优势,并拥有类和方法所有的局限;特别的,类和方法直接绑定到了特定的运行时行为,而特定的运行时行为是不能修改和扩展的,因为行为是提供“类”和“方法”的概念定义的;因为它们不是语言,类库很少被环境(例如编译器和编辑器)聪明的支持
我们应该忠于wannabe DSLs,还是应该拥有当需要DSLs的时候使用真正DSLs的自由?当然是自由;每个类库都是为我们的平台创建一种完全的DSL的候选;例如,JDK的所有类库都应该是MPS平台上的DSLs;其中一些DSL在现在刚开始的时候不是那么紧急需要,但其它一些从一开始就对平台的功能和可复用性有强烈的影响;我将介绍随MPS提供的三种最重要的平台语言:The Base Language, the Collection Language, and the User Interface Language
Base Language
我们首先需要的语言是对应最简单编程领域的,一种general-purpose的命令式编程;这种简单的语言应该支持近乎通用的语言特性诸如算术、条件、循环、函数、变量等等;在MPS中我们有这样一种语言,它被称为Base Language
对这种语言的需求应该是很明显的,例如,如果我们想把两个数字加在一起,我们应该能够简单的说一句“a + b”就可以;我们不需要到处去使用它,但几乎所有的程序都会有一些组成部分用到它,在那里,它是完成工作最合适的工具
Base Language之所以如此命名,是因为它是很多需要基本编程支持如变量、语句、循环等的语言很好的基础;它能够以三种方式使用:你可以扩展它以创建出你自己的基于它的语言,你可以在你的程序中引用它的概念,你还可以以Base Language生成你的代码;将会有几种可用的生成器来将Base Language转换成其它语言如Java,C++等;当然,不是每种语言都需要使用Base Language,但是在很多情况下,它是一个很好的起点
Collection Language
下一种我们需要的最重要的语言是和集合一起工作的语言;对集合支持的需求是普遍存在的;每种主要的主流语言都提供了对集合某种类型的支持,例如,在Java中你有java.util,在C++中你有STL;每个人都需要集合;如果每种DSL都提供自己的对集合的支持,那么将会有a Babylon of不同的集合语言,它们互不兼容;这就是为什么MPS必须提供一种每个人都使用的单一的Collection Language的原因
在很多主流语言中,集合并非语言特性而是类库,一个例子是Java的java.util包;这种支持技术上来说是存在的,但它是不方便的,杂乱的,并且易于出错的
Yuck!今天大部分的Java代码被一行接一行多余的、重复的处理集合的代码弄的杂乱无章;图6显示了一个例子,Collection Language是如何beats the tar out of a 类库的;例子是一个计算一组给定的点的convex hull的算法;更多关于Collection Language的细节会在以后的文章中提及
User Interface Language
User Interface Language是我们的平台中下一种最重要的DSL;有趣的是,我前面提到的Editor Language能够另人信服的用来提供用户界面,但是一种专为图形用户界面设计的语言将会更灵活;这种语言带来的益处是巨大的;Java Swing代码就是一个想成为DSL的类库的极好的例子:功能有了,但很容易被误用,并且Swing的代码是彻底杂乱的;很多如今的开发环境都包含GUI builder来简化用户界面的创建;User Interface Language将把这项任务带到一个更高的层次;我将在以后的文章中讨论更多细节
Getting Started with MPS
我已经能够听到一些对LOP怀疑的反应:“听起来不错,但是我们的项目已经步入正轨,现在切换到LOP是不可行的”,或者“听起来不错,但用一个像LOP这样的未经检验的方法来启动一个现实生活中的项目风险太大了”,或者“听起来不错,但是它什么时候才能为它的黄金时期做好准备呢?别忘了OOP用了20年才成为主流”
好消息是我们不需要一头扎进未知里,你可以先用脚趾头试一下水;你可以在你的项目中只是应用LOP的一小块来看一下它有没有提供一些实际的好处,然后如果你喜欢你可以用多一点;在不远的未来,你可以在MPS里试验两个可能的LOP应用:
Using MPS on Java Applications
已经有一个IntelliJ IDEA的原型插件允许你在你的项目中包含MPS的概念模型;当你编辑模型时,模型会自动在后台被转换成Java源代码;因此,你可以使用MPS来编写Java应用的部分模块,喜欢用多点就用多点,喜欢用少点就用少点;这意味着你得到了MPS全部的力量,比如创建和使用特定DSLs的能力,做任何你想要的语言扩展,同时使用定制的带有自动完成、错误加亮、重构功能的编辑器,等等;插件将和IDEA紧密集成,允许你在你的MPS模型中嵌入Java代码,导航到嵌入或生成的Java代码,甚至进行概念层次的调试,就像IDEA中已经可用的JSP调试支持一样;更多集成特性正在计划中;这将是使用IDEA的Java开发者可用的一个重要的新工具
Configuring and Scripting Your Applications
有一个我见过很多次的模式,一个应用程序启动时需要某种形式的配置,可能是一个简单的配置文件,或者更完整的部署描述符文件;最后,配置变的更复杂,应用程序最后需要一种脚本语言;对于简单的配置文件,XML很流行;对于脚本语言,你可以创建自己的,或者借用一种general-purpose的脚本语言,像VBScript,Python/Jyphon,Tcl,Javascript,或者Lisp;这些方案中的每一种都至少有一些主流编程方法的标准缺陷:很长的实现时间,陡峭的学习曲线,难以扩展,匮乏的环境支持,等等
作为替代的,你能够使用MPS创建你自己的配置/脚本语言;你的应用程序的用户将会拥有一种易于使用的、智能的编辑器来编写他们的脚本,包括语法加亮,错误加亮,代码完成,导航等;只需花费很少的时间来创建并集成这种语言到你的应用中;为了使用这种应用,你可以分发MPS的运行时
Conclusion
LOP和MPS背后的思想并不新鲜,实际上已经出现超过20年了;Language Oriented Programming本身这个词也已经提出至少10年了;新鲜的是这些思想一直在软件开发社区默默的渗透,而它们的时代最终到来了;通过这篇文章,我希望提供一颗种子,使这些思想能够产生新的讨论、意见、批评、实验、研究、和最终的真实生活中的项目
并且,因此,我邀请你以任何你能做到的方式参与到这种新的范型中来;在后面添加评论,或者发送Email给我:mps_article@jetbrains.com;在http://www.jetbrains.com/mps可找到更多,请关注更新;注意浏览从LOP的视点出发的网站,杂志,博客,书籍等,并思考事情到底能够多么简单;考虑一下你自己的项目,看看你有多频繁的实际上在设计和使用小的特定的用类和方法修补的语言;你是怎么认为的呢?我想知道
当我将LOP的概念用在开发MPS自身时,我已经看到了Language Oriented Programming是如何彻底改进软件开发的第一手资料;MPS目前并没有为真实世界准备好,但它已经成功的达到了目的;也还依然没有文档,除了这篇文章;我将很快的发布更多的文章,深入的探讨MPS;还有,我计划下月试验着使MPS可下载,因此你的耳朵要保持张开; 已经有其它的项目使用了类似的方法,特别是来自Intentional Software 和 Xactium
因此,探险愉快,让我看看你能发现什么
Acknowledgements
I would like to thank Rob Harwood for his help in editing this article. I would also like to thank the following people for their reviews, comments, and suggestions: Igor Alshannikov, Florian Hehlen, Jack Herrington, Guillaume
Laforge, Vaclav Pech, Thomas Singer, Dmitry Skavish,David Stennett, and Timur Zambalayev.
About the Author
Sergey Dmitriev (http://www.sergeydmitriev.com) is the co-founder and CEO of JetBrains Inc.(http://www.jetbrains.com), makers of the IntelliJ IDEA Java IDE.
References
Articles:
[1] Donald E. Knuth. Literate programming. The Computer Journal, 27, 97-111, May 1984.
[2] M. Ward. Language Oriented Programming.Software - Concepts and Tools, 15, 147-161 1994,
http://www.dur.ac.uk/martin.ward/martin/papers/middle-out-t.pdf
Intentional Programming articles:
Charles Simonyi. The Death of Computer Languages, The Birth of Intentional Programming. 1995.
ftp://ftp.research.microsoft.com/pub/tr/tr-95-52.doc also
ftp://ftp.research.microsoft.com/pub/tr/tr-95-52.ps
John Brockman. Intentional Programming: A Talk With Charles Simonyi. Edge. 2000.
http://www.edge.org/digerati/simonyi/simonyi_p1.html
Microsoft Research. Intentional Programming.
http://www.cse.unsw.edu.au/~cs3141/ip.asf (video)
Charles Simonyi. Intentional Programming: Asymptotic Fun?http://www.hpcc.gov/iwg/sdp/vanderbilt/position_papers/simonyi.pdf
Books:
Krzysztof Czarnecki and Ulrich W. Eisenecker. Generative Programming: Methods, Tools and Applications.Addison-Wesley, 2000. ISBN: 0201309777.
Jack Herrington. Code Generation in Action. Manning, 2003. ISBN: 1930110979. http://www.codegeneration.net/cgia/
Xactium. Applied Metamodelling: A Foundation for Language Driven Development. 2004.
http://albini.xactium.com/content/index.php?option=com_remository&Itemid=28
Other Resources on the Web:
[3] Matt Quail. Totally Gridbag.
http://madbean.com/blog/2004/17/
Jack Herrington. Code Generation Network.
http://www.codegeneration.net/
[4] Intentional Software
http://www.intentsoft.com
[5] Xactium
http://www.xactium.com
Intentional Programming interviews
Sergey Dmitriev.
http://codegeneration.net/tiki-read_article.php?articleId=60
Charles Symonyi.
http://codegeneration.net/tiki-read_article.php?articleId=61
Krzystof Czarnecki.
http://codegeneration.net/tiki-read_article.php?articleId=64
Andy Evans.
http://codegeneration.net/tiki-read_article.php?articleId=68
See Also:
《Thinking in Current Paradigms, Platforms, Frameworks, and Libraries》