建立为模型驱动开发设计的领域专用语言外文翻译资料

 2022-07-17 14:50:42

英语原文共 8 页,剩余内容已隐藏,支付完成后下载完整资料


建立为模型驱动开发设计的领域专用语言

中文摘要:如今,Python 和 Ruby 等动态语言的流行程度日益增长,甚至超越了其作为脚本语言的使用。事实上,Sun ,微软和其他公司正在他们的平台上逐步支持动态语言。开发者们越来越觉得动态语言的特性可以帮助他们提高生产力,而对这些语言的常见的误解(例如它们的低性能和不可靠性)正在渐渐消失。在动态语言中嵌入领域专用语言,而不是重新构建一个编译器和解释器,可以提高程序的可读性并且节省开发时间。

关键字:领域专用语言,面向对象模型,元模型,生产力,程序处理机,运行时,代码编写

过去,我们对动态语言的经验和理解来自于 Smalltalk,但近两年,通过使用 Ruby 为模型驱动开发(MDD)开发工具,让我们获得了对动态语言更深入的理解[1]。这些工具是基于领域编程语言的概念,领域编程语言指的是为在某一个领域执行某些任务而设计的专用语言。在嵌入的 DSL (领域专用语言)中,它的设计继承了它的宿主语言的结构,而不是构建一个语法分析器。在这里,我们将证明动态语言的特性使它们非常适合作为宿主语言。

通过提供几个领域专用语言,我们的工具让我们的用户能处理基本的 MDD 任务。在这里我们展示 DSL:

  • 一个用于创建元模型的 DSL 让我们可以指定一个模型语言的抽象语法。
  • 一个用于验证模型的 DSL 提供了一个类似于 OCL(对象约束语言)的语法用来指定模型的限制。
  • 一个模型对模型的转换语言让我们可以指定描述性的映射关系。
  • 一个模型对代码的语言让我们可以从源模型生成代码。

因为我们想让我们的工具主要用来实验,我们决定在一个很短的开发时间内使用嵌入的 DSL 去完成一个可配置的实现方式。

我们使用我们创造的几个 DSL 去说明在动态语言中发现的公共的机制和特性(例如元编程,动态域,动态求值等等)。这些机制和特性已经能让我们写出高可适配性的代码。在某些情况下,我们使用这种方式的运行时的性能超过了基于 Java 的实现。

图1.(a)使用嵌入的 DSL 来指定一个关系型的元模型;(b)定义元模型的 DSL 的代码实现片段。

一个定义元模型的简单的 DSL

元模型是模型驱动开发的一个关键的层面。一个元模型定义了一门模型语言或 DSL 的抽象语法。一个元模型语言例如 MOF(元对象工具)或 Ecore 让我们使用面向对象架构(如类,关联和泛化)定义一个元模型。

一个图形化的编辑器是定义一个元模型最方便的方式,但是开发一个元模型是非常耗时的。因为一个元模型语言可以被认为是一个为了定义元模型而设计的 DSL,为了这个目的我们决定实现一个元模型语言;然而,我们实现使用嵌入的 DSL 而不是去造一个语法分析器。在一门语言中嵌入 DSL 是一个在动态语言中众所周知的机制。 由于这些语言的不会妨碍的语法和它们的执行出现在程序文本中的表达式的能力,所以在这些语言中构建 DSL 有与生俱来的基础和根据[2,3]。一个嵌入在类 Lisp 语言中的 DSL 是在宏系统的基础上——也就是说,在求值程序文本前它就继承了宏系统。总而言之,一个基于面向对象语言(例如 Ruby 和 Smalltalk)的 DSL 是用方法的方式定义的,这些方法会在程序运行时被求值。

所以,为了创造一个嵌入在 Ruby 中的 DSL,我们定义每个 DSL 关键字作为一个方法,目的是语言的解释器执行一个方法调用当它读取到相应的关键字时。我们能使用一个类的内部作为关键字方法的上下文,并且使用继承在上下文中设置这些方法。(我们能定义 DSL 关键字作为实例方法,但是我们需要在使用了 instance_eval 方法的对象的上下文中显式求值 DSL 程序文本)。

图 1a 展示了一个关系型模型的定义,图 1b 展示了定义元模型的 DSL 的代码实现片段。我们在一个继承于 MetamodeKeywords 的名为 Relational的类中定义元模型,这个类真正实现 DSL 是通过定义类方法。关键字 metaclass,attribute 和 reference 和类中的元模型关键字一一对应。(类方法通过 def self.method_name 定义)。Ruby 的人性化的语法让我们调用这些方法甚至不需要使用括号去包裹参数,因此 Ruby 提供了简洁的方式来编写 DSL。

然后我们映射每个嵌套结构的 DSL 到一个代码块。举个例子,一个嵌套的结构是类和参数之间的包含关系,一个 Ruby 代码块是一个被 do hellip; end 或 {} 包裹的代码片段。元类的实现创造了一个新的元类并且调用 yield 来求值传递的代码块。接受方法调用的对象是隐式的。因为我们映射关键字到元类方法,所以接受的对象是 Relational 类本身。

实现 DSL 这种方式比使用一个语法分析器生成器要花费的时间少得多:因为我们不需要处理任何语法和抽象语法树。另一方面,定义一个武断的句法是不可能的,因为我们被限制在宿主语言句法的下面。举个例子,Ruby 句法是最适合定义块结构 DSL 的句法,Lisp 句法是最适合定义表达式 DSL 的句法。

图2.一个 UML 到 Java 转换的摘要。UML 类的字段被转换到 Java 类中的参数。

一个用于模型转换的 DSL

模型转换时模型驱动开发的核心,因为它弥补了在领域概念和实现技术间的隔阂[5]。我们研究嵌入 DSL 的根本来自于一个项目,在这个项目中我们开始定义 RubyTL,它是一个具有扩展特性的基于规则的模型转换语言[6]。除此之外,这门语言在 Ruby 的最顶层加了一个声明层用来通过指定模型元素映射关系的声明规则的方式转换模型。

图2展示了在 UML 类模型和 Java 类模型间的转换定义的摘要。第一个规则转换每一个 UML 类到 Java 类(由 from 和 to 关键字指定)。 Mapping 关键字取一个 Ruby 代码块,其中我们放置名为 binding 的特别的声明(为了指定被转换成什么)。我们实现这样的绑定通过重新定义存储器方法来寻找能转换右部分到左部分的规则。举个例子,field2feature 规则解决了 klass2java 规则的第二个绑定。

RubyTL 的声明形式提供了一个简洁的方式来编写转换定义。然而,这或许不足够表示需要必要构造的具体而复杂的转换。在这些情况下,因为每个规则的映射部分是一个真正的代码块,我们能编写任何我们需要的 Ruby 代码。所以,使用嵌入 DSL 的一个重要的优势就是拥有自由使用通用语言的能力。举个例子,在 field2feature 规则中,我们使用 case 这个 Ruby 声明来设置可见的参数,我们甚至可以重用 Ruby 断言机制(注意 raise 的使用)。

我们可以说,扩展性是 RubyTL 的关键的特性。它提供了加入新特性到他的核心特性集的扩展点,因此让我们来测试一下新的特性是否对解决转换问题有帮助。其中一个扩展默认规则行为的扩张点让我们可以定义新的规则。我们使用扩展点来说明我们如何应用动态语言的三个公共机制:

  • 元编程——写一门语言来写另一门语言的能力
  • 自省——在运行时获取类和对象信息的能力
  • 调解——在运行时查看和修改它的值的能力

实现一个新的例如 toprule 的规则(如图2)依赖从 Rule 类中的继承(它实现了默认的规则的行为)和定义一个新的关键字(它的实现仅仅是实例化一个新合适类型的 rule 对象)。所以,关键字实现仅仅在 Rule 的子类名有差异,我们会自动生成它。为了完成这个,我们需要一个方法来知道 Rule 类何时被继承以及动态语言何时添加了新的关键字(即一个新的方法)到该语言。我们通过自省解决第一个需求,通过调解解决第二个需求,这需要一个元编程的形式。

图3展示了我们如何实现它。在 Ruby 中,每个类都有一个生命周期回调集,用来提示我们一个方法何时被添加和移除,一个类何时被继承等等。特别是一个类何时被继承上,Ruby 触发继承的类方法。我们在 Rule 类中实现这个方法,目的是 Ruby 解释器将提示我们用户核实定义一个新的规则通过继承这样的类。

图3.当用户定义一个新的规则时 Rule 类自动生成 DSL关键字的实现

我们对继承方法的实现动态添加了一个自定义的方法到 RubyTLKeywords 类的 DSL 关键字列表中。解释器调用继承方法当它求值 “class TopRule lt; Rule”的时候,然后,这个方法的实现动态生成了 toprule 方法,它负责新规则的初始化。

定义字符串文字和操纵字符串的设施在动态语言中很常见,他们使代码生成成为可能。生成新代码的代码被定义为一个字符串,在 %{hellip;} 中被包裹起来(处理多行字符串比较容易)。任何包裹在 %{hellip;} 中的表达式的值都代替了字符串本身。

RubyTLKeywords.class_eval 的调用导致了解释器去求值在实现了 DSL 的类中的上下文的代码,RubyTLwords,因此添加一个新方法到了类中。图3的白框圈出的部分展示了当 DSL 被求值时执行的真正的代码和解释器执行 toprule 方法。

上面的例子展示了动态语言的特性——因为它们能在运行时处理代码,所以编写可适配性、可扩展性的代码非常高效。既然这样,我们使用强大的自省的类型来找到类型等级是如何够贱的,使用元编程来避免编写重复的代码以及可以在运行时修改 DSL。

一个用于验证的 DSL

我们也必须是否用作模型转换的输入模型和来自视图转换点的代码生成被完成了。这个任务通常被在 OCL 中编写限制完成。但是因为我们在 Ruby 中开发了整个环境,我们决定既不建立一个 OCL 解释器,也不重用另一个语言的解释器(比如说 Java)。我们使用另一个嵌入在 Ruby 中的 DSL来构建一个限制语言。

图4展示了用OCL和我们的约束语言编写的UML元模型(“对于每个类,如果存在称为id的属性,其类型必须是整数”)的类元类的不变式。 对由上下文子句指定的每个类的实例,都将检查不变量。

这个 DSL 只包含两个关键字,context 和 inv,所以使用之前展示的定义语言关键字的机制来实现它非常容易。此外,代码块对于实现类似于 OCL 中的迭代是非常有用的。为了 DSL 像我们期待的那样工作,我们必须做两件事:

  • 在默认执行上下文不是定义期的代码块中明确指定不变量
  • 在适当的位置实施OCL预定义操作库,如 forAll,implies 和 oclKindOf

在图4c的实现片段中,上下文方法简单存储了来自了将被检查不变量的实例的元类。这些实例将在 inv 代码块的上下文中被求值。一些动态语言能改变代码块被求值的环境。对于这个验证 DSL ,这个特性被用来改变 inv 代码块求值的域。DSL 用户的观点是代码块应该在上下文子句中指定的元类实例中被求值,那与在默认域中被求值是不同的。我们在代码块中编写self 变量,但是这个变量没有被绑定到任何实例中直到这个代码块在合适的地方被执行了。在 inv 代码块中,我们使用 instance_eval 来求值上下文中每个实例的域中的代码。通过这种方式,这个被编写在代码块中的实例变量(例如 self.attributes)被动态绑定在相应的对象上。

在 Ruby 中,类是开放的所以你能添加和删除方法在一个可以存在的类里面,甚至改写一个已经存在的类里的方法。这个甚至应用在标准库里。在我们的例子里,我们重写一些类来提供方法类似于 OCL 库里的方法。举个例子,我们重写 Array 类来提供类似于 forAll,exists 和 includes 这些类似于 OCL 标准库里的方法

全文共8531字,剩余内容已隐藏,支付完成后下载完整资料


资料编号:[9246],资料为PDF文档或Word文档,PDF文档可免费转换为Word

原文和译文剩余内容已隐藏,您需要先支付 30元 才能查看原文和译文全部内容!立即支付

以上是毕业论文外文翻译,课题毕业论文、任务书、文献综述、开题报告、程序设计、图纸设计等资料可联系客服协助查找。