MDSF:面向语言编程LOP(Language Oriented Programming)方法介绍

  好几年前看了JetBrains的CEO Sergey Dmitive一篇文章Language Oriented Programming - The Next Programming Paradigm才开始知道LOP的,当时也试用了MPS,觉得眼前一亮。到现在隔了好几年了,对LOP的具体内容也有点忘记了,而近期在思考OpenExpressApp模型驱动开发(MDD)方面的内容,所以在这里再次整理一下这方面的内容,这样也会有更多人了解它,下面的内容会参考前面说的那篇文章以及其他LOP内容与大家分享下。

  LOP和MPS背后的思想并不新鲜,实际上已经出现超过20年了;Language Oriented Programming本身这个词也已经提出十多年了。LOP放弃了传统的基于文本的语言,用创造新的语言来代替类库,可以和编辑器所整合,并且每个程序员都可以创造自己的语言。Martin Flower却对此饶有兴趣,并写了Language WorkbenchesA Language Workbench in Action - MPS两篇文章进行了介绍。

概述

  按照Dmitriev先生的观点,通用语言的问题在于:它们描述领域模型的能力太差。在完成概念性的领域建模之后,开发者们还必须经过一个漫长的编码过程,才能把模型描述为机器可理解的程序,反过来要理解这些代码所描述的领域模型也是同样困难。而DSL虽然能够很好地描述领域模型、解决领域问题,但这种语言又太少了,而且大多数开发者必须苦苦等待少数大厂商为他们提供适用的DSL。很大程度上,这种“语言的失位”造成了软件开发的低效。

  Dmitriev先生提出的解决方案就是LOP:借助工具的帮助,允许开发者创建自己的DSL。这样的DSL当然能够最贴切地描述领域问题,从而大大提高开发效率。而且,这样的“自定义DSL”也不必是文本形式的,它可以直接保存为树状结构(或别的结构),并直接以图象的形式展现。

主流编程中三个最糟糕的问题

  • 从理解问题后到实现的时间很长

在明白问题和解决方案后,将解决方案编码到计算机中将会花费很长的时间。这是因为可以使用非常丰富的自然语言表达问题,但我们只能通过通用的编程语言与计算机交流。而现在的编程语言只有几十种,而自然语言表达可以有千万上万种,因此这种转换的成本是比较大的。

在主流编程中,大部分时间都是在“编程”上,这实际上是在用编程层次的抽象术语来表达自然语言的概念,而这是很困难的,没多少创造性,或多或少也是一种时间的浪费。举个例子,今天大量的开发时间花费在面向对象的设计(OOD)上,在程序员表达类、继承、关联等方面这确实是一种还算有创造性的过程,但是使用Language Oriented Programming,OOD根本就不需要。

个人观点:这个我还不是很了解,我想底层还是需要的吧,有待进一步了解

  • 理解和维护代码 

在通用语言的程序中,很多高度概括的视角、蓝图都丢失了,这不利于我们对产品理解。解决这个问题的传统方法是写注释或用其它形式的文档来记录设计信息和模型信息,但这证明了这是一种脆弱的解决方案,因为它需要编写这些辅助文档的成本、以及文档和代码带来的不同步麻烦等问题;理想情况下,代码应该是自我描述的,我应该只阅读代码本身来理解代码,而不是什么注释和外部的文档。

  • 学习曲线高 

OOP很少能够直接表述领域概念,它们必须引入额外的枝节(如一个类的运行时行为)来完成到领域概念的映射。理解这些领域、学习这些类库不是一项简单的任务,我们需要用大量的指南和文档来解决这个问题,但是学习这些将花费大量时间;当一个类库变得复杂的时候,它也变得更难以学习,程序员将因此失去学习它的动机。即使掌握了领域问题和技术的这种复杂映射之后,依然还会很容易的误用类库,因为开发环境(像编译器和编辑器)不能帮助你正确的使用类库。

LOP的切入点  

  通过计算机改变世界,程序员都希望自己对计算机有完全的控制,但实际上,我们经常被那些不能轻易改变的编程语言和开发环境等基础设施限制。当需要进行一些语言扩展时,我们只能等待语言的设计者去更新它;如果需要我的IDE有一些额外的强大功能,只能等待供应商来添加新特性;就是这些依赖限制了我们完全的自由;当然,程序员也可以写自己的编译器和IDE,但这样将会花费大量的时间和努力,并且并不一定能成功。

LOP的切入点就是允许我们可以创建、重用、修改语言和环境。要理解LOP是什么,可以从下图的主流编程和LOP方法过程进行一下比较,它使得编程阶段已经不是瓶颈了,而转移到了“创建”这一步,作者开发可一个通用的平台(the Meta Programming System)来设计DSL,它允许程序员像现在编写程序一样非常容易的就可以定义自己的语言;这个平台将完全支持LOP,给程序员为程序的每一部分选择使用最合适的语言的自由,而不是将他们绑在某个固定的通用编程语言之上。

LOP可以用许多不同的方法来实现,MPS只是Language Oriented Programming的一个示例,就像OOP的概念不等同于Java或C++或Smalltalk一样。

LOP是什么

    Language - Oriented Programming is a style of programming that tries to produce code that looks like it came from a Domain Specific Language but is still valid in a general purpose programming language.

  • LOP将不只是编写程序,还包括创建用来编写程序的语言

今天,百分之九十九的程序员认为编程就是编写一串计算机能够执行的指令集,但这种编程的观点是有缺陷的,它混淆了编程的目的和手段,首先需要澄清一个事实:一个LOP的程序,不是一串指令集。那它不是指令集,又是什么呢?

当我有一个问题要解决,我会用单词、标记、概念等来形成自己的解决问题模型,这时候从未把它们想象成一堆指令集,而是一个在领域中一些特定的具有内在联系的概念集合。例如当思考GUI领域时,我会想象如何布局,这个按钮到那边去,这个输入域到这边来,这个组合框里面需要有一些数据的列表,这时可能只是在头脑中把它画出来,而根本不用任何语言。应该有一种方法允许我们使用这种表述作为真正的程序,而不仅仅是通过与其它程序员交流的手段来编写解决方案,一个程序是对某个领域的某个问题领域以及解决方案的清晰而明确定义的模型表示。

这就是我认为程序员应该拥有创建他们自己的语言的自由的主要原因:GPL是无歧义的,但是太冗余和易于出错;而自然语言表达能力十分丰富,但是又太不精确太不结构化了;所以我们允许程序员可以自己创建一种结构化的、精确定义的、能够更加自然表示的语言来形成解决方案。因此LOP将不只是编写程序,还包括创建用来编写程序的语言;我们的程序将被编写的更接近问题域而不是计算机指令集领域

  • 将程序直接存储为结构化内容

因为当前的阅读和编辑程序最方便和最通用的方法是使用文本编辑器,所以人们习惯性的认为程序是作为文本来存储的。但是程序的文本表示有重大的缺点,其中最重要的是基于文本的编程语言非常难以扩展;如果程序以文本的形式存储,你就会需要一个无歧义的文法器来解析程序;当为语言加入新特性时,维护语言无二义性的扩展变得日益困难;我们将需要发明更多类型的括号、操作符、关键字、顺序规则、嵌套,等等;语言的设计者们花费了无数时间来思考语法,并试图发现扩展语言的新方法。

如果打算让创建语言变得容易,我们就需要将程序的表示和存储从程序本身分离开;我们应该直接将程序存为一种结构化的内容,编辑器能够提供我们喜欢的可视化表现形式;我们可以把程序做成文本、表、图、树、或其它任何形式,我们甚至能为不同目的使用不同的表现形式,比方说,图形化表示用来浏览,文本化表示用来编辑;我们能够为代码的不同部分使用领域相关的表示,比如为数学公式使用图形化的数学符号、为图表使用图形化的图表、为spreadsheets使用行和列等等;我们能够为问题域使用最合适的表现形式,可以是文本,但不限于文本;最好的表现形式依赖于我们如何思考问题域;表现形式的灵活性也将使我们的编辑器比以往更加强大,因为不同的表现形式有不同的方式去编辑它们。

个人观点:感觉存储为结构化内容,不需要解释器进行文法分析,有点类似意图编程思想

  • 三要素:结构、编辑器和语义

在LOP中,一种语言有很多方面,如约束和类型系统,但LOP主要有下面三个要素来定义:

  • 结构:定义了抽象语法、支持的概念
  • 编辑器:定义了具体的语法,如何展现和编辑
  • 语义:定义了行为,如何被解释,以及如何被转换成可执行代码

  (个人观点:我觉得LOP语言有点建于在文本DSL和图形DSL之间的一种形态

IDE支持

  现今至少有三家主要厂商正为面向语言编程提供这一类工具:Charles Simonyi开发的Intentional Software,Microsoft的Software Factories和JetBrains开发的Meta Programming System。这些工具,Martin Fowler称之为语言工作台(Language Workbench),让设计并使用DSL变得更加简单。这些工具提高了面向语言编程的竞争优势,虽然Fowler相信“还要过几年大多数人才会考虑用[语言工作台]来开发实际的项目。”

  MPS(Meta Programming System)提供的软件开发环境可以创建新的定制语言,也可以扩展现有语言,然后用它们开发面向领域的应用。它是在2003时作为一个研究项目而启动的,2004年时对LOP概念进行了专门的描述(可参考此文)。MPS 是Martin Fowler在Language Workbenches: The Killer-App for Domain Specific Languages? 发明的,它是自由和开放源码,并提供一个有趣的建立在Java上的语言扩展的DSL新方法。  

参考内容:JetBrains元编程系统支持面向语言编程和DSL

使用MPS生成语言

  应用LOP的思想,使“创建新语言”更容易的方法,就是创建一种特定的专注于“创建新语言”这个领域的DSL;通过应用这些创建语言的DSL可以使得容易的创建一种新的领域语言。下面先看看几种创建语言的例子,以便我们能更好的理解它们是如何工作的。

  • 结构语言(Structure Language)

在实践LOP的时候,大部分情况下,你会工作在两个层次的编程中:元层次和程序层次;你在元层次中定义语言,在程序层次中编写程序。

用Structure Language定义一种语言的抽象语法,只是枚举这种语言所有的类型;类型简单的表示了这种语言支持的特性或者概念;每个概念应该用它的名字、实例的内部属性、实例与其它节点之间的关联关系来定义。存在两种可能的关联;第一种是类似聚合的关联,它形成了概念模型的父子树结构;第二种是非聚合的,自由形式的关联,它可以连接到系统中任何其它的节点;关联有两个端点:源和目标;关联有角色,你可以定义每个角色的名称、每个端点的多重性,每个目标节点的类型;多重性可以是1, 0..1, 0..n, 1..n等,让你能够约束关联可以创建多少连接;关联的目标类型可以被用来约束哪些类型的节点可以被连接在一起。

使用新语言编写程序包括:创建语言中概念的实例、为实例的属性赋值、根据语言概念定义的关系将程序中的节点连接在一起

  • 编辑器语言(Editor Language)

编写和操作概念模型的界面应该是什么样子的呢?经验表明通用的编辑器不能像我们希望的那样有用,所以MPS也支持我们为语言定做特定的编辑器,它把程序和语言构造成图形。

MPS将程序存储为图形,但这不是我们一般说的图形编辑器,MPS的Editor Language是一种创建编辑器的语言,它有更好的灵感来源(个人观点:这点也是吸引我的地方)。

如果你用文本编辑器浏览一个典型的程序,你可以想象编辑器被分成了矩形单元;一些单元包含必需的标识如关键字、花括号、圆括号等,其它的单元包含用户定义的标识,如类和方法的名称;大的单元由小的单元组成,像方法块包含语句,而语句可能包含自己的嵌套块;事实上,任何主流编程语言中任何良好构造的程序都可以分解为矩形单元的集合;那么,在Editor Language中,你不需要想象这些单元,因为编辑器就是简单的由矩形单元组成的。

单元的使用有一些有趣的优点;首先,当直接工作在程序图形而不是程序文本上时,单元可以完美的模仿甚至超过标准的文本编辑器;第二,单元不局限于文本,你可以往单元里塞进颜色选择器、数学符号、图表、矢量图、或任何别的什么;最后,这种单元形式的layout是可选的,程序员可以提供不同的机制,单元形式的layout只是一种有用的缺省设置。

因此,Editor Language帮助你定义语言中每个概念对应的单元的布局;你可以定义哪些部分是不变的,像括号或其它修饰符号,哪些是可变的,需要用户去定义的;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),并使用得到的信息对模板进行“填空”,得到最终的目标代码

在上图中,你可以看到为概念“Property”生成Java代码的模板的定义,模板为属性添加了field declarations, getters, setters等;这个模板是将代码从Structure Language转换为Java的生成器的一部分。当使用一种模板语言的时候,可以认为它是用目标语言编写的,只是某些部分的代码是参数化的,或者是由宏来计算的;这种技术极大的帮助简化了代码生成;模板还可以用在其它任务上,如重构、代码优化、还有更多...

  模式(Patterns)

 模型的模式匹配方法给我们一种作为Model Query Language的代替的查找模型的强大方法;你可以把模式想象成概念模型的正则表达式;与模板方法类似,我们基于源语言产生模式语言;模式语言看起来像源语言,只是添加了一些特性,来帮助你定义处理复杂源模型匹配的灵活的标准;你可以把这种方法想象成一种强大的“查找替换”的技术;再一次,模式语言不只是对代码生成有用,例如,它们在为源语言编辑器编写自动化的代码检查工具方面非常有用

记住Model Query Language, template languages, 和pattern languages都由强大的编辑器支持其自动完成、重构、引用检查、错误勘测、等等;即使复杂的查询、宏、模式,都可以很容易的编写;代码生成从来没有这么强大过

  • 语言之间互相使用

  前面有关代码生成的章节带来了一些关于这些语言如何一起工作的有意思的问题;事实上有几种方法能让语言一起工作;在MPS中,所有的概念模型都互相知晓;既然语言也是概念模型,那么便意味着所有的语言都彼此知晓,可以潜在的被连接在一起。

语言彼此之间可以有不同的关系;你能够通过扩展现存的语言来创建新语言,继承所有的概念,修改其中的一些,并加入你自己的概念;一种语言可以引用其它语言中的概念;你甚至能将一种语言插入到另一种语言中去;我将在以后的文章中讨论进一步的细节。

  • 平台、框架、类库和语言

  我们支持Language Oriented Programming的系统需要比元编程能力更多的功能才能更有用;它应该提供程序员依赖于当前的编程语言提供的所有事物:集合,用户界面,网络,数据库连接,等等;程序员不止是单单基于语言本身来选择语言;例如,Java的大部分功能不是语言提供的,而是有成千上万的framework和API供Java程序员选择;他们买的不是Java语言,而是整个Java平台;MPS也将有一个它自己的支持平台。下面介绍随MPS提供的三种最重要的平台语言:The Base Language, the Collection Language, and the User Interface Language

  Base Language

GPL支持近乎通用的语言特性诸如算术、条件、循环、函数、变量等等,而在MPS中也有这样一种语言,它被称为Base Language。Base Language之所以如此命名,是因为它是很多需要基本编程支持如变量、语句、循环等的语言很好的基础。

它能够以三种方式使用:你可以扩展它以创建出你自己的基于它的语言,你可以在你的程序中引用它的概念,你还可以以Base Language生成你的代码;将会有几种可用的生成器来将Base Language转换成其它语言如Java,C++等;当然,不是每种语言都需要使用Base Language,但是在很多情况下,它是一个很好的起点

  Collection Language

每种主要的主流语言都提供了对集合某种类型的支持,每个人都需要集合,如果每种DSL都提供自己的对集合的支持,那么将会出现语言之间的不兼容现象,这就是为什么MPS必须提供一种单一的Collection Language的原因。下图显示了一个例子,Collection Language是如何beats the tar out of a 类库的;例子是一个计算一组给定的点的convex hull的算法

  User Interface Language

User Interface Language是我们的平台中下一种最重要的DSL;有趣的是,我前面提到的Editor Language能够另人信服的用来提供用户界面,但是一种专为图形用户界面设计的语言将会更灵活;这种语言带来的益处是巨大的;Java Swing代码就是一个想成为DSL的类库的极好的例子:功能有了,但很容易被误用,并且Swing的代码是彻底杂乱的;很多如今的开发环境都包含GUI builder来简化用户界面的创建;User Interface Language将把这项任务带到一个更高的层次(这个我还不清楚

 

  下一篇我将介绍一个用MPS做的一个例子,这样大家就更能明白LOP是什么了。

参考

DSL(Domain Specific Language)介绍

面向语言的程序设计wiki

MPS网站

Download MPS

 A Language Workbench in Action - MPS

Language Oriented Programming article

Language Oriented Programming

Language Oriented Programming in F#

 

欢迎转载,转载请注明:转载自周金根 [ http://zhoujg.cnblogs.com/ ]

posted on 2010-09-30 12:33  周 金根  阅读(3878)  评论(2编辑  收藏

导航