分享

领域特定语言开发现状与展望

 汉无为 2023-04-14 发布于湖北

  泛在计算环境下的软件系统横跨多个领域并使用大量领域特定语言,迫切需要一个高效可行的领域特定语言开发环境。文章简要介绍领域特定语言的发展脉络与特点,从设计、实现和编程开发环境3个维度梳理国内外在领域特定语言开发方面的研究现状,分析和定位现有工作面临的挑战,进而提出一种敏捷的领域特定语言开发工作台的构想,并突出其中有待重点探究的问题。

文章速览

  人机物深度融合催生了万物互联的泛在计算时代(Ubiquitous Computing)和新型软件产业形态。泛在计算环境下的应用横跨从可穿戴设备、健康监测、智能家居到无人驾驶、智能制造等众多场景,具有形态丰富多元、领域千差万别、计算资源异构的显著特点。构建这样的应用系统不仅要兼顾底层类型不一的异构计算资源,更要满足上层众多应用领域的业务需求和逻辑,因而单靠软件开发人员独力难支,还需各个相关行业的领域专家和专业人员深度介入来协同完成系统的设计与开发。由于领域专家和专业人员多未系统学习过计算机技术相关专业课程,且掌握传统的通用编程语言(General-purpose Programming Language, GPL)需要花费高昂的学习成本,这就需要为他们量身打造能够屏蔽底层复杂计算而聚焦相应领域概念和业务逻辑的特定语言,从而促使编程语言的格局由少量通用编程语言“一统天下”逐渐向众多领域特定语言(Domain-Specific Language, DSL)“各显神通”转变。

  领域特定语言,顾名思义,就是专注于某个应用领域的编程语言。面向领域需求并解决领域问题的领域特定语言通过设计贴合领域语义的语言构造对相应领域的概念、操作和业务进行高度抽象。随着泛在计算的应用范围不断延伸、规模持续增长,泛在应用系统面临环境多变、需求多样、场景复杂的挑战,这对泛在应用系统的开发提出了更高的要求,既要满足大量行业领域的业务需求逻辑,还要能够支持不同领域业务在复杂场景中的协同计算。通用编程语言因与不同领域间存在语言鸿沟,难以承载泛在计算环境下横跨众多领域的软件开发的重任。为此,需要开发大量的领域特定语言来协同构建泛在计算环境下的应用系统。
   融合编程语言技术与特定领域知识于一体的领域特定语言,其设计和实现需要相当的技术和知识门槛,单纯依靠编程语言设计专家和领域专家任何一方都非易事,而需双方合作实施。针对泛在计算环境下的应用系统开发对大量领域特定语言的迫切需求,提供一种高效可行的领域特定语言及其编程支撑环境的设计与实现框架不失为一种有效途径。基于此,将概述领域特定语言的形成与演变、特点与局限,从设计、实现和编程开发环境等维度梳理国内外研究现状,分析和定位现有工作面临的挑战,提出一种敏捷的领域特定语言开发工作台的构想,突出其中有待进一步研究的问题,以期为泛在计算时代领域特定语言的研究与实践提供启发。

1 领域特定语言的形成与特点

  设计和开发面向特定领域的编程语言可追溯到早期的高级程序设计语言,如Cobol、FORTRAN、Lisp,这些语言在诞生之初都是作为解决某一特定领域应用问题的专用语言而出现的。由名称可知,它们分别对应商业处理(Common Business Oriented Language)、数值计算(Formula Translation)和符号计算(List Processor)领域。用编程语言便捷精确地描述更多特定领域模型的需求,催生了如下解决方案。

(1)函数库(Subroutine Library):一种经典的封装领域特定知识的方法,指可重复使用的面向某一特定领域功能的函数例程库。
(2)对象框架(Component Framework):基本思想与函数库相同,采用面向对象(Object-oriented)技术封装特定领域知识。不同于函数库扁平状的组织结构,对象框架可提供层次化的构建和封装、隐藏框架内部的调用细节,提供更易调用的接口,便于演化。
这些方案均基于既有通用编程语言提供的抽象机制进行语言功能的扩展。
总体来说,这些方案大多数情况下都能很好地提供描述领域特定模型所需的功能。但这些方案存在两方面的问题:一方面,它们仍需领域专家使用通用编程语言构建和组合领域特定的模型,增加了使用难度以及不必要的描述复杂度;另一方面,并非所有领域特定的概念和抽象都能直接对应到一个函数或者对象上,只能采用间接的表达手段,导致无论语法还是语义都难以与领域习惯相匹配。这些不足驱动了为特定领域构建专属语言的需求。因此,领域特定语言作为一个研究话题进入人们的视线。目前业已涌现出为数不少的领域特定语言,如语法分析器生成语言YACC(Yet Another Compiler Compiler)、关系数据库结构化查询语言(Structured Query Language, SQL)、Web页面的描述语言HTML(Hypertext Markup Language)、排版系统TeX等不一而足,均在各自的领域发挥着重要作用。

1.1  领域特定语言的特点

领域特定语言就是专门用于表达某一清晰界定的特定领域而构建的计算机语言。领域特定语言为特定的问题领域设计了贴合其语义模型的语法记号和概念抽象,可以用来直接描述该领域内的模型,是编程语言技术与领域特定知识的融合。领域特定语言在一些场景下也被称作小语言(Little Language)、特定用途语言(Special-purpose Programming Language)、应用特定语言(Application-oriented Language)等。

尽管不同的语言面向的领域可能大相径庭,但领域特定语言具有一些共同特点。领域特定语言通常采用声明式(Declarative),其语言构造(Language Construct)直接对应领域内的基本词汇与操作。因领域特定语言具有较高的抽象层次,采用它描述领域业务往往只需给出“做什么”而尽可能屏蔽“怎么做”的计算细节。因此,一些领域特定语言也被视为描述语言(Specification Language)。除此之外,领域特定语言通常拥有精简的语言构造集合,无须引入额外构造来描述该领域之外的模型,其表达能力具有明确的指向性,其语义也因此易于被领域用户理解。

1.2  与通用编程语言的比较

领域特定语言和通用编程语言之间并无泾渭分明的界线,但在适用范围、抽象层次和表达能力等维度可进行定性区分。

通用编程语言的适用面广泛,使用其中精心设计的少量通用抽象(如过程抽象、对象抽象等)编写程序仍然是目前的主要手段。但使用通用编程语言来编写特定领域的程序往往会带来与问题本身复杂性(Essential Complexity)无关的描述复杂性,这些附带的复杂性(Accidental Complexity)是由于所使用的抽象和特定领域概念不匹配导致的。相比之下,领域特定语言仅适合用来描述某一特定的领域,而不适合用来描述更广泛的其他场景。但领域特定语言能在更高的层级描述特定专业领域内的问题和算法,利用领域特殊的语言构造在领域内获得更高的表达能力。从抽象的层面说,领域特定语言构建了特定领域知识的语言抽象,消除了描述特定领域模型时的附带复杂性,从而提高了开发效率。
为具体应用所涉及的不同问题领域构建对应的领域特定语言并解决对应的问题,这种范式又被称为面向语言编程(Language-Oriented Programming, LOP),已经在视频剪辑、文本出版、幻灯设计等领域获得初步应用。

1.3  领域特定语言的优势与局限

事物总是一分为二的,领域特定语言自有其优势和局限。二者的权衡是决定是否开发和使用领域特定语言的关键因素。

领域特定语言拥有更少的语言构造和更精简的语义,因而使用领域特定语言的优势在于以下4方面。
(1)封装和重用特定领域的知识,进行语言层面的领域知识建模和验证。
(2)编写出更符合领域直观的程序,便于领域专家的学习和使用。
(3)编写出更简洁的程序,提高编写效率,减少出错概率,并且更加易于演化和维护。
(4)便于应用领域特定的性质,能够更好地开展领域特定的分析(Analysis)、验证(Verification)、优化(Optimization),提高模型的可用性,确保模型的功能正确性(Functional Correctness)。注意,拥有这些优点的前提是领域特定语言具有清晰、独立的领域语义,否则无法在语言层面进行程序的理解。
领域特定语言也存在以下4方面的局限。
(1)难以确定合适的领域范围大小。面向领域的清晰界定需要平衡语言的适用范围和抽象程度(对应着描述特定领域内模型的表达能力)。
(2)语言设计的复杂度高。设计领域特定语言需要编程语言专家与领域专家通力合作,不仅需要对该领域有充分理解,还需要具备专业的语言设计能力。
(3)相对于直接使用现存的通用编程语言,领域特定语言本身的实现、维护和开发环境的实现都需要额外的代价。
(4)一个完整的应用可能涉及多个不同的领域。不同领域之间所需的沟通和数据交换导致领域特定语言之间的互操作(Interoperability)成为额外的问题。

2 国内外研究现状

领域特定语言的开发可以分为设计、实现和提供支撑环境3个步骤。领域特定语言的设计是指面向特定领域需求,确定如何用程序描述用户的目标,即语法(Syntax),以及程序的每个语言构造表达了怎样的行为,即语义(Semantics)。基于其设计,语言开发者需要为领域特定语言实现解释器或编译器,使语言能够实际执行。最后,为了让领域特定语言的使用者拥有更好的编程体验,语言设计者可以为用户提供一套易于使用的编程开发环境,完善工具链。将从这3方面讨论领域特定语言开发技术与相关研究。

2.1  领域特定语言的设计

编程语言的设计可以分为设计语法和语义两部分。语法即语言中的程序应该具备的正确形式,可以进一步细化为词法和句法;语义指语言中程序对应的含义或计算行为。语法和语义均可采用形式化的描述,例如巴科斯范式(Backus-Naur Form, BNF)文法可以描述上下文无关的程序语法,而指称语义(Denotational Semantics)、操作语义(Operational Semantics)、抽象状态机器(Abstract State Machine)可以从不同层次形式化描述语义;也可以通过语法解析示例、动态执行样例和自然语言描述等非形式化方法进行语法和语义的直观描述。从当前的普遍实践中看来,领域特定语言的语法描述多采用形式化的方法,而对语义的描述多采用非形式化方法。从而语义描述具有一定的歧义性。

2.1.1  领域特定语言的设计步骤
设计领域特定语言时通常包含如下步骤。
(1)确定领域及收集信息,进行全面的领域分析(Domain Analysis)。领域信息可以包括技术文档、现存代码及其内含注释、调查问卷、领域专家的口述等。这一步骤与知识工程(Knowledge Engineering)紧密相关。
(2)领域建模(Domain Modeling)。利用领域分析的结果,识别领域中的关键概念以及概念实体之间的关系。这一步需要领域专家与语义专家合作完成。建模结果可能包括领域的关键词汇集合、领域概念的描述、领域实体关联的特征模型(Feature Model)等。
(3)语言语法和语义的设计。领域建模结果可被视作领域特定术语及领域概念语义的高层次抽象描述,而语言的语法和语义设计需要将这些模糊的、不确定的抽象描述进一步具体化、精确化,最终形成一个计算机可执行、专家可理解的编程语言。
当然,实际场景下领域特定语言的设计并不是这样线性的过程,而是需要与后续的开发和使用相结合,根据具体实现和使用情况进行调整和修改,形成一个往复迭代式的更新过程。具体地,语言设计人员可以从实际应用中的反馈获得更多的领域分析内容,修改相应的模型,并对应调整语言构造的语法或语义。
2.1.2  领域特定语言的设计准则
鉴于不同领域具有各自的特点,如何设计一个良好的领域特定语言目前仍缺乏足够的研究,尚未有普适的方法。除了可以参考通用的编程语言设计准则外,文献[16]也提出了一些宽泛的面向领域特定语言的设计准测。其中,关于语法设计有如下准则。
(1)贴合领域惯用的表达记号,不要创建新的记号,确保语法设计风格与领域使用惯例的一致性。
(2)利用关键词(Keyword)区分不同领域概念的表达方式,易于理解辨识,消除可能的歧义性。
(3)平衡语言构造的语法在审阅时的可读性(Intelligibility)和编写时的紧凑性(Compactness)。
关于语义设计包括如下准则。
(1)尽量复用和组合现有的语言构造定义,而不是重新创建语义。
(2)使用精简的语义概念集合涵盖领域核心概念,减少开发负担和使用者的学习负担。
(3)避免领域内不需要的构造泛化,确保表达能力的领域指向性。
关于设计语言时应该语法优先还是语义优先时有讨论,可参见文献[17]。总之,一方面,良好设计的语法记号应该明确展现其对应的领域特定语义,保证用领域特定语言编写的程序的可读性和可理解性;另一方面,良好设计的语义应该能够自然地表达领域中的问题,无须领域专家额外构建抽象。

2.2  领域特定语言的实现

领域特定语言因其规模有限和目标人群特定,在实现的目标和方法上与通用编程语言实现有较大差异。一方面,需要控制领域特定语言的实现成本,如果以实现通用编程语言的方法来实现领域特定语言,会导致开发的代价较高;另一方面,程序的书写需符合领域习惯,编译器或解释器提供的错误信息等应当使用领域术语。

文献[19]依照实现模式对领域特定语言进行了分类,如通过解释器、应用程序生成器等方式实现领域特定语言;也可以依照语言的独立性,将领域特定语言分为内部领域特定语言(Internal DSL)和外部领域特定语言(External DSL)。外部领域特定语言的语法是由语言设计者专为领域设计的,与实现通用编程语言类似,需要为语言构建完整的语法解析器(Parser)来处理用户输入的程序。这类语言的程序以领域术语为基本词汇,语法符合领域习惯,是一门独立的编程语言,因此也称为独立领域特定语言(Standalone DSL),如TeX、SQL等。内部领域特定语言建立在某个既有的宿主语言(Host Language)之上,通过宿主语言来描述领域特定语言的语义,并利用宿主语言支持的用户自定义抽象(User-defined Abstraction)使宿主语言程序具有领域特定语言的形式,从而领域特定语言的程序实际上就是宿主语言的程序。这类语言不需要语言设计者花费精力描述语法和实现语法解析器,而是将领域特定语言的语言结构定义为宿主语言的宏、函数或类等,即领域特定语言被嵌入宿主语言中,因此也被称为嵌入式领域特定语言(Embedded Domain-Specific Language, EDSL),如Chisel、jQuery等。近年来,由于开发成本低、实现简便,内部领域特定语言越来越受到编程语言社区的关注。
2.2.1  领域特定语言的语法实现
1)外部领域特定语言的语法实现
外部领域特定语言的语法实现方法在编译领域已经有较为成熟的研究,一般分为词法分析和语法分析两个步骤。词法分析将源程序从左到右逐个字符读入,并根据规则组合为词法单元(Token);语法分析则是将上述的词法单元组合成句,如表达式(Expression)、语句(Statement)、声明(Declaration)等。常见的语法分析方法有语法解析器生成器(Parser Generator)和语法分析组合子(Parser Combinator)。使用语法解析器生成器时,设计者以语言的语法形式化定义(如BNF)作为输入,语法解析器生成器将自动产生一个语法解析器程序作为输出。常用的语法解析器生成器有 ANTLR(Another Tool for Language Recognition)、YACC等,它们对语言的语法要求各有不同。语法分析组合子允许设计者定义简单的解析器,并通过组合子生成更复杂的解析器,从而实现自顶向下的语法解析。由于语法分析组合子具有良好的模块化性质,设计者可以直接复用现有的解析器组件。当语言的语法做出调整时,语法解析器的调整也较为容易。但是语法分析组合子的执行效率往往要低于语法解析器生成器。常见的语法分析组合子实现有Haskell中的Parsec 库、JavaScript中的Parsimmon库等。选择哪种语法分析实现方法,需要设计者根据语言的复杂程度、对执行效率的要求等因素进行衡量。
此外,一类基于描述语言XML(Extensible Markup Language)的外部领域特定语言可借助XML直接构建出抽象语法树,并通过XML解析器进行解析。这种方法也被称为商用货架产品(Commercial Off-The-Shelf, COTS),如ACML(ANSI C Markup Language)、OWL-Light。通过这种方法,设计者只需要提供领域特定语言的结构,而无须给出具体的语法。语言用户需要按照XML的语法描述出程序的树结构。但是由于XML引入了过多的尖括号、引号及斜杠等符号,程序中包含较多的语法噪声,对于书写、阅读程序是不利的。用户可以借助专业的XML解析器、编辑器的图形界面来直观操作XML结构。例如,在开发环境MPS(Meta Programming System)的代码编辑器中所书写的程序实际上也是通过XML格式保存,可以基于语言设计者定义的方式展示给用户。
2)内部领域特定语言的语法实现
内部领域特定语言构建在宿主语言中,其语法实现成本较低,不需要单独的语法解析,但要受到宿主语言的限制:任何领域特定语言的程序必须是合法的宿主语言的程序。换言之,语言设计者只能基于宿主语言的语言特性并通过精巧的设计,使领域特定语言的程序更接近领域习惯,同时要保证作为宿主语言程序的正确性。因此,一个通用编程语言是否适合作为宿主语言,很大程度上是由其支持的用户自定义抽象机制决定的。例如,C++、Scheme等语言提供的宏机制,Haskell、Scala语言提供的高阶函数(Higher-Order Function, HOF)机制等都是有利于领域特定语言实现的。在语义的实现中会进一步讨论这些方法。
2.2.2  领域特定语言的语义实现
1)外部领域特定语言的语义实现
对于外部领域特定语言而言,其语义可以基于编译器或解释器直接实现,其基本思路类似于实现一个通用编程语言。在实现解释器时,需要给出领域特定语言的形式化语义,如依照其操作语义实现大步求值的解释器。在实现编译器时,需要将领域特定语言程序进行代码生成,翻译到如机器码、中间表示或是其他语言,如TeX代码会被编译至dvi格式。语言设计者在实现编译器或解释器时,对于程序计算中的错误状态需要指定错误信息。为了使用户更好地修改调试程序,该信息应当使用领域特定的术语描述。此外,语言设计者可以实现领域特定的分析、验证、优化、并行化或转换。
但实现编译器或解释器通常较为烦琐,对于一些规模有限的领域特定语言,现有工具如Spoofax和K-framework可以简化上述工作。Spoofax是一种语言工作台,允许用户为领域特定语言定义一系列重写规则,Spoofax将会自动地为语言设计者生成一个可以运行领域特定语言程序的Eclipse插件。与Spoofax类似,K-framework也通过重写规则描述领域特定语言的语义并自动生成相应的环境。通过这些方法,语言设计者可以着重于对领域特定语言语义的描述,而不必关注底层实现细节。
2)内部领域特定语言的语义实现
对于内部领域特定语言,主要通过在宿主语言中描述领域特定语言的功能的方法来定义语义。
正如前文所述,内部领域特定语言的实现依赖于宿主语言的语言特性。下面介绍几种常见的适合于领域特定语言开发的语言特性。
(1)基于宏的领域特定语言。宏(Macro)是一种古老的构建抽象技术,本质上是在编译器或解释器对文本进行处理之前(预处理阶段),根据宏的定义规则对程序进行文本转换,这也是其与函数调用之间的差别。宏可以被分为文本宏和句法宏两类。C和C++中定义的宏是文本宏,可以根据宏的定义任意地对文本进行替换。即使某个宏的定义本身不符合语法,只要在替换后是合法的宿主语言程序即可。很多现代的程序设计语言均不支持文本宏,开发者也尽可能地避免使用它们以防止产生预期之外的错误。句法宏也是通过替换实现的,但是句法宏作用在抽象语法树上。Lisp家族的语言均支持句法宏,自诞生以来,Lisp便受到领域特定语言社区的青睐,宏系统在其后的Scheme、Racket等语言中也得到进一步加强。通过宏实现领域特定语言的示例有S-XML。C++的模板也是在编译时期完成模板替换的,因此被看作句法宏的一种,这种设计领域特定语言的方式称为模板元编程,并在数学库的开发中取得了一些进展,如Blitz++。基于宏的领域特定语言的主要问题是,领域特定语言的用户只能获得宏展开后的程序中可能的报错信息,难以与源程序对应,这是由于宏本身没有出现在下游工具中,用户没有办法对宏的定义与使用进行有效的追踪。
(2)基于函数、类等嵌入式实现。设计者可以在宿主语言中将领域术语实现为函数、类、操作符等,并构建成一个库嵌入到宿主语言中。用户只需要在宿主语言中导入相关的库,即可编写领域特定语言的程序,其语法与宿主语言一致。这种方法是近年来实现嵌入式领域特定语言的主要方式。例如,在Haskell语言中,领域术语可以被实现为特定的类型或类型类(Type Class),对它们的操作则实现为函数。由于在Haskell这类函数式语言中,函数作为一等公民(First-class Citizen),可以作为参数或返回值传递,因此可以大幅简化程序的书写难度。如在Scala语言中,可以将领域特定语言的“关键词”实现为一个单例对象,并为其实现apply方法,使得领域特定语言用户以一种直观的方式书写代码。这种方法是现代内部领域特定语言的主要实现方法,现存实例有构建在Haskell语言上的Dhall、Frob语言,构建在Scala语言上的Chisel语言,构建在ML(Meta Language)上的FPIC语言,构建在JavaScript语言上的jQuery语言。
内部领域特定语言的核心问题是语言使用者的学习成本。一方面,由于领域特定语言程序是宿主语言的程序,语言使用者不可避免地需要学习宿主语言的语法和概念;另一方面,宿主语言所提供的错误信息可能使用的是宿主语言的概念,而非领域特定语言的描述,语言使用者需要花费更多的时间理解错误信息并调试程序。

2.3  领域特定语言的开发环境

语言的编程开发环境(也称支撑环境)是指能够编辑、运行、调试某一语言程序(或拥有其中部分功能)的软件。毋庸置疑,任何一门语言,如果要走向成熟、获得更庞大的用户群体,都必须具有一个完整且功能丰富的开发环境。这里所谓的开发环境,既包括集成开发环境,也包括嵌入于各种通用编辑器中的插件,还包括其他一些特定的开发工具生成的开发环境。领域特定语言的实现方式在一定程度上决定了其开发环境的实现方式。例如,独立实现的领域特定语言的开发环境一般也是独立实现的,而使用嵌入式方式开发的语言能够借用其宿主语言的开发环境。下面将领域特定语言开发环境按照实现方式及体量进行分类并概述。

2.3.1  轻量级支持
对一门语言的开发环境的支持可以是渐进的。一方面,语言开发环境的实现是一项十分困难的工作;另一方面,并不是所有语言都需要体量庞大的开发环境支持。因此,很多领域特定语言由于其特性或使用广泛程度仅具有轻量级的支持。
例如,很多情况下,代码高亮显示是一种轻量级的支持。语言实现者通过编写轻量级的语法定义(如TextMate语法定义),识别程序中的关键词、字面量、语法结构等,并使用不同颜色显示。这样的支持十分常见,主要是因为编写这样的语法定义工作量很小,只要为语言编写了这样的定义,编辑器通常能自动识别这种格式并对代码进行高亮显示。大部分通用编辑器支持识别这样的语法定义,例如Visual Studio Code能够识别TextMate格式定义,Sublime Text能够识别一种自定义的基于 YAML标记语言的语法定义,Emacs也支持基于Emacs Lisp的语法高亮定义。从语言的角度来看,大部分语言,无论常用与否,多数都具有代码高亮的支持。这一功能大幅降低使用这门语言编程的难度,而实现成本又很低,因此成为了支持广泛的功能。此外,一些简单的代码补全,例如对于关键字的补全,也可通过简单的编辑器插件实现。再如,一些语言工具支持代码片段功能,允许用户插入常用的代码片段以提高效率并起到提示作用。这些功能均可通过轻量化的方式实现,不会过度增加语言实现者的负担,但能够大大提高语言的易用程度。
这类开发环境的支持可说明,对一门领域特定语言的支持不一定是完善的,任何能够辅助用户开发的支持都是有益的。同时,对于开发环境的支持也能够逐步演化:轻量级的开发支持通常能和更完善的支持方式合并,逐步演进为完整的开发环境。
2.3.2  专用开发环境
领域特定语言最传统也最常见的开发方式为专门式的开发,即语言开发者根据语言特性、用户需求等因素,将开发环境作为独立的程序进行开发。这类方法的优点为可定制化程度高,功能完善,开发者对于成品有着完全的控制。但其缺点则为工作量大,需要专业开发团队对产品进行设计。正因为如此,这类产品主要集中在商业化的软件。尽管这类开发环境的编写十分费时,但在目前语言工程技术尚不完善的情况下,这种开发方式仍位居主流地位。专用开发环境可进而分为两类:独立开发环境和嵌入式开发环境。下面分别介绍这两种开发环境。
1)独立开发环境
有些十分常用的领域特定语言具有专门的集成开发环境支持。例如,数据库查询语言SQL的集成开发环境就可能包含对SQL代码编写的智能提示、图形化地显示查询语句、动态显示结果、数据库之间的跳转等功能。再如,用于显示网页的HTML,其集成开发环境则可能支持基于项目上下文的自动补全、所见即所得等功能。一个完善的领域特定语言的集成开发环境的编写是一项非常困难且具专业性的任务。对于一个功能不同的其他语言,就必须对其重新编写相关开发环境。
2)嵌入式开发环境
领域特定语言在软件中的应用十分常见。例如,大多数表格处理软件都采用领域特定语言描述数据的计算、统计等,这类公式语言即可被认为是一门函数式的用于数据处理的领域特定语言。同时,表格处理软件通常对这一语言有着完善的图形界面支持。例如,对于公式中需要指定的表格范围参数,用户既可以选择使用文本输入的方式键入,也可以通过在表格中拖选单元格范围的方式实现。这类软件通过此方式大大降低了使用领域特定语言的门槛,让用户感受不到领域特定语言的存在,而采用更友好的编辑方式是领域特定语言演化的方向之一。
领域特定语言在图形化用户界面中也有广泛的应用,例如XAML(Extensible Application Markup Language)语言用于为Windows Presentation Foundation设计图形界面,QML(Qt Markup Language)用于为Qt程序描述图形界面等。它们一般被嵌入于更大的集成开发环境中。这些编辑器也采用所见即所得的特性,支持双向编辑等功能,能够大大提高设计者及程序员的协同工作效率。
2.3.3  开发环境复用
开发环境复用是最能代表领域特定语言开发环境发展前沿的一类方法,基本思想是通过重复使用开发环境的界面、逻辑等元素,快速高效地实现一门语言的开发环境。这一方法可进一步分为4类。
1)基于函数、类等嵌入式实现
一些内部领域特定语言的嵌入方式较为简单,直接作为宿主语言的库存在。一般来说,它们会利用宿主语言的强大语法功能,使之变得美观易懂。这类语言可以直接利用宿主语言的集成开发环境。例如,嵌入于Scala语言的Chisel,就可直接使用Scala的集成开发环境进行编码。
这类领域特定语言的显著优点是几乎无须为其专门提供环境支持,仅仅使用宿主语言的集成开发环境即可自动获得相当的功能。但其缺点也是明显的,例如难以在领域层面提供定制化的信息提示,所有错误都会报告于宿主语言层面,对于缺乏宿主语言知识的领域专家理解有困难。同时,获得的功能往往也是不完善的,例如代码补全可能缺少上下文敏感度(Context Sensitivity)。
2)基于宏的嵌入式实现
实现内部领域特定语言的方式之一为借助宏系统等元编程方式。这类定义方式对于程序的展开(Macro Expansion)发生在预处理阶段,通常宿主语言的集成开发环境并不能处理这类程序。因此,一些系统采取了其他方式解决这一问题。其中,最典型的为Racket面向语言开发平台。Racket的官方编辑器为DrRacket。用户可在Racket语言上自由定义语言,采用宏系统翻译到Racket核心语言上。而只要用户在翻译过程中保留了必要的信息(如代码位置信息),DrRacket即可对任意定义于其上的语言提供编码环境。
3)生成式开发环境
自从高级编程语言诞生,人们就想方设法简化一门编程语言的开发。例如早期工具YACC,即可根据语法定义生成语法分析器。语言工作台(Language Workbench)即为这类工具的集大成者。语言工作台提供了开发一门语言所需的各种基础设施,根据用户提供的词法、语法、语义定义生成相应语言工具,提供编辑、编译、运行、调试等功能。文献[27]详细对比了10款语言工作台的各项功能。
语言工作台生成的开发环境大多利用了某种一般性的集成开发环境。例如,MPS生成的语言开发环境就使用自身的图形界面,XText依赖Eclipse平台,Spoofax亦生成Eclipse插件等。
4)语言服务器协议
语言服务器协议(Language Server Protocol, LSP)是微软公司推出的一款用于实现编辑器功能的协议。语言开发者根据语言服务器协议编写“语言服务器”,它们负责响应编辑器发来的请求(如代码补全、符号重命名、跳转至定义等功能),以及向编辑器发送信息以告知用户。所有这些信息交换都按照语言服务器协议,因此对于同一种语言,只需实现一次语言服务器,则任何支持此协议的编辑器均可以使用这一实现为相应语言提供语言特定的功能。
语言服务器协议在微软公司对Visual Studio Code的推广下已经获得了广泛的语言支持,几乎所有主流语言都支持这一协议。使用该协议实现的语言功能已经能够达到与集成开发环境媲美的效果,因此开发者在很多情况下不必再安装独立的集成开发环境,大大方便了语言工具的使用与推广。

3

敏捷的领域特定语言开发面临的挑战与展望

客观地说,通用编程语言的适用范围广泛,可用于描述众多不同领域的计算模型。常见的通用编程语言多拥有成熟可靠的执行实现和完善的程序开发环境支持,具有很好的可用性。相较之下,领域特定语言的可应用范围窄,局限于其面向的特定领域。这导致除了少量用户群体基数庞大的领域外(如数据库查询领域有成熟的SQL),设计和实现领域特定语言的初始代价必须在较低的范围内,否则开发领域特定语言的可行性低。因此,大部分领域特定语言缺乏基本的编程开发支撑环境,或者开发环境无法给出相应领域概念的反馈信息,从而大大降低了领域特定语言对于领域用户的可用性。

语言的构建代价过高与可用性较差是推广面向语言编程范式和使用领域特定语言的最大阻碍。
图1给出了使用领域特定语言与传统方法的理想化的比较(修改自文献[28]),可以认为初始的语言设计和实现代价决定了图中总代价的截距,而可用性和维护代价等决定了总代价的斜率。这二者共同决定了用户应该使用领域特定语言还是其他传统方法。
图片图1 使用领域特定语言与传统方法的比较

3.1  面临的挑战

具体地,在泛在计算、全民编程的时代,要使领域特定语言能够切实有助于相应领域应用的开发,目前仍面临着以下挑战:一是降低领域特定语言设计与实现的代价;二是增强语言的编程支撑环境可用性。

已有少量工作尝试从不同角度着手给出解决方案。
(1)构建开发语言用的语言工作台,如MPS、XText、Spoofax等。这些工具构造了一组用于描述语言语法、编译翻译过程、编辑环境信息等各语言开发环节的领域特定语言,实现语言开发的“一站式服务”。从这个角度上说,虽然语言工作台一定程度上降低了语言实现的难度,本质上却没有减少开发代价,仍然需要完整的开发流程,即没有彻底地解决语言开发代价大的问题。
(2)构建嵌入式领域特定语言。这种方法试图在现有的宿主语言上实现可以相对独立使用的库作为领域特定语言,实现代价低。其主要缺点体现在领域用户在使用该语言的过程中,获得的编程环境反馈信息通常并不能匹配领域概念,而是暴露了领域特定抽象在宿主语言层面的实现细节,易发生抽象泄漏(Abstraction Leakage)。这导致语言的可用性问题没有得到很好的解决。
综上可知,降低领域特定语言的设计开发代价、赋能领域用户编程亟需一套敏捷开发领域特定语言的方法学。

3.2  展望

经过观察,可将领域特定语言的语言构造大体分为两类:一类是领域特定的词汇与操作,另一类是相对通用的抽象手段和组合构造。这与文献[30]中描述的构造编程语言的模式是一致的。根据上述观察,不难推断出领域特定语言的设计开发过程中存在许多重复定义和实现的部分,若能识别、定义和实现这些重复的成分同时予以有效复用,则可有效降低领域特定语言的开发代价并增强其可用性。

基于上述观察和思考,提出嵌入式领域特定语言工作台这一构想,以试图结合嵌入式领域特定语言和语言工作台的优点。这一设想的核心是扩展和复用一个实现完善的、良好定义的通用宿主核心语言,提供便捷的翻译方式完成领域特定语言的语义定义,并实现语言求值器和轻量级开发环境的自动生成。
在嵌入式领域特定语言工作台构想中,要求宿主语言应是具有可见的模块化语义的“白盒子”,而非单纯的直接运行使用的“黑盒子”。满足这种需求的宿主语言就可在其之上通过如下3个步骤来定义和实现新的领域特定语言及其支撑环境,如图2所示。

图片

图2 嵌入式语言工作台的双层架构
(1)利用现有的语义组织技术,如单子(Monad)、代数效应及其处理程序(Algebraic Effect and Handler)等,在保留原本定义的基础上,按需对宿主语言进行模块化的语义域(Semantic Domain)扩展、基本操作扩展和值空间扩展,实现宿主语言横向的模块化扩展。这一扩展步骤是为了能够更加直接地表达领域特定语言的领域特殊操作和词汇。
(2)使用简单翻译过程将上层领域特定语言的语言构造嵌入下层扩展后的宿主语言中,在概念上完成对上层语言的精确语义定义,实现语言的纵向层叠生长。通过局部翻译来定义(Definitional Translation)的方式降低了定义语言的难度。由于在步骤(1)已经对宿主语言进行了必要的功能扩展,期望翻译到扩展后的宿主语言的过程是简单的(简单局部翻译的形式化定义及其合理性可参考文献[33]),所以又可以将领域特定语言的构造视为宿主语言上的语法糖(Syntactic Sugar),这一翻译过程也可以被称为“解糖”(Desugaring)过程。
(3)利用宿主语言定义中的可组合性质(Compositionality),实现语义规则、编辑环境工具、分析优化算法等宿主语言定义的“提升”(Lifting),即自动推导出独立的上层领域特定语言的语义定义、可执行实现和编程环境工具等。这一自动化推导过程,一方面降低了领域特定语言及其开发环境的实现代价,另一方面维护了为领域所构建的抽象屏障,防止领域特定语言发生抽象泄漏。提升是保持领域特定语言相对宿主的独立性和降低领域特定语言实现代价的关键步骤。值得一提的是,关于静态语义(即类型规则和作用域规则等)已有文献[35]给出了较为完善的提升方案,部分佐证了这一设想的可行性。
实现领域特定语言敏捷开发的嵌入式语言工作台这一构想充分利用了构建复杂系统所需的模块(Modularity)、层叠(Layering)和抽象(Abstraction) 3种关键技术,能够更好地解决语言设计与实现代价过高和可用性差的问题。
关于嵌入式领域特定语言工作台的构想存在以下需要重点关注的研究内容:①如何设计和实现一个具有模块化语义和良好可扩展性的核心宿主语言;②如何确定翻译空间的大小以平衡提升算法的难度和语法糖定义的难度;③如何实现语义定义、分析优化算法、编程环境工具的自动提升算法。其中,最核心的问题是如何实现自动提升算法,而前两个问题均为使提升成为可能的基础。下面简要讨论语义规则和开发环境的提升方法,以及可能存在的关键问题。
3.2.1  语义规则的提升
通过单子技术对语义进行模块化的扩展已有较多成熟的研究。这里主要讨论语义的提升,即对简单翻译定义的语言构造的语义规则的推导。
该构想要求宿主语言的语义是“白盒子”,即需要通过一种元语言准确地描述宿主语言的语义规则,如自然语义(Natural Semantics)、小步操作语义(Structural Operational Semantics, SOS)。领域特定语言构造通过简单的翻译定义,被嵌入扩展后的宿主语言中。最后,语义规则的提升是指自动推断出领域特定语言构造的元语言语义描述。
例如,在一个包含分支计算语句(if构造)的宿主语言中定义一个描述布尔运算的领域特定语言,“和”表达式的简单翻译定义如图3左下方绿色▢所示。规定if语句首先计算e1,并根据所得结果执行两个分支之一。其形式化的小步操作语义规则如图3下方的橙色▢宿主语言语义规则所示。

图片

图3 将宿主语言的语义规则自动提升至领域特定语言

利用if构造语义的可组合性,可以推导得到and构造的语义规则:首先计算e1,如果结果为“真”则执行e2的计算,否则直接返回“假”。形式化的语义规则如图3上方的蓝色▢领域特定语言语义规则所示。可以看出,此时and的计算是与if无关的——即使它由if定义。换言之,当用户试图单步调试and语句时,在计算过程中不会翻译到if上,这就是期望维护的“抽象性”。
在上面的讨论中提到了语言构造语义的可组合性,这是保证语义规则可提升的关键。可组合性质是指一个表达式的求值结果是其各个子表达式的计算结果的组合。当某个翻译规则的规则体中所使用的所有语言构造的语义均具有可组合性质时(即领域特定语言的语言构造是由若干可组合的表达式组合的),那么其语义规则自然是对各个子表达式的计算结果的另一种更复杂的组合。
并非所有语义规则都可以写成结构化的形式,这正是语义提升的难点。例如在ML中,λ抽象会对内部表达式延时求值、函数应用时对子表达式中的变量进行替换、递归会产生对当前结构的重复计算,这些均不是结构化的语义模式。期待这些问题能够在未来的研究中攻克,给出完善的语义规则提升算法,而其中的细节与正确性的验证,还需要研究人员进行更深入的探索。
3.2.2  开发环境的提升
随着领域特定语言的数量的高速增长,减少领域特定语言的开发环境本身的开发代价成为十分有价值的问题。文中2.3.3节中提到的4种集成开发环境复用的方式均存在一些不足。例如,作为库实现的内部领域特定语言高度依赖宿主语言,用户必须对宿主语言有一定了解才能够使用这门语言,并且宿主语言集成开发环境并不能提供领域特定的信息,这使得上层用户难以理解程序内部行为。而作为宏定义实现的语言通常只能实现有限的开发环境功能,在易用性上无法让人满意。生成式的开发环境、语言服务器协议两种方法,提供了实现开发环境所需的基础设施,但对于领域特定语言的语义并没有轻量级的指定方式,使得开发一门领域特定语言的代价仍然十分高昂。
基于现存工具的这些问题,可以认为减少环境开发代价的关键在于合理地复用现有的开发环境工具,并将相对完善的功能提升至上层的领域特定语言,确保对于用户的可理解性。通过基于语法糖简单翻译的轻量级构建领域特定语言方式,期望利用宿主语言的语言功能实现构建领域特定语言的开发环境。一个理想的宿主语言拥有较为完善的表达能力,并且具有丰富的语言功能支持,可以一般地将一个领域特定语言通过语法糖翻译到宿主语言上,并通过自动提升的方式为领域特定语言实现对应的语言功能。
一个概念性的例子如图4所示。语言设计者设计的如图中上方的代码编辑器中显示的领域特定语言,并使用语法糖将之翻译到宿主语言中。为这一宿主语言实现各种语言功能,如代码补全、跳转、高亮显示等,如果给这些结果一个结构化的接口规范,就有可能将结果“提升”到领域特定语言中,从而实现领域特定语言上的相应功能。与现存方法相比,这一方式显著的优点在于大大降低了语言设计者的工作量,只需一个轻量级的语法糖定义即可自动生成出各种开发环境功能。这样的轻量化的方式有助于对一门新的领域特定语言提供编辑器支持,但具体应该如何提供尽可能完善的环境,还需进一步探索与试验。

图片

图4 将宿主语言的语言功能自动提升至领域特定语言

4

结束语

当前正迈入一个“计算无处不在、计算赋能万物”的人机物融合的泛在计算时代,万物均可互联、一切皆可编程是其鲜明的特征。随着泛在计算的应用范围不断延伸、规模空前增长,泛在计算面临环境多变、需求多样、场景复杂的挑战,这对泛在计算环境下的应用系统开发提出了更高的要求:不仅要满足众多行业领域的业务需求和逻辑、兼顾底层类型众多的异构计算资源,还要支持这些众多异构计算资源和复杂领域业务在不同层次不同场景中的协同计算。面向领域特定语言敏捷开发的嵌入式语言工作台构想从软件的基石——“编程语言”出发,设计一个语法简洁、语义清晰、表达力强的核心宿主语言作为基础,通过扩展领域特定原语和语义将领域特定语言翻译嵌入核心宿主语言,并进一步将核心宿主语言自动提升为面向不同领域的领域特定语言,搭建高效开发可信领域特定语言的平台,形成领域特定语言“百花齐放”的局面,奠定基于领域特定语言的泛在应用系统开发的基石,服务于“要加快构建高速、移动、安全、泛在的新一代信息基础设施,形成万物互联、人机交互、天地一体的网络空间”国家战略。

END

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多