INTERPRETER是一种反模式吗?

10
对我来说,解释器模式听起来非常像一个反模式,即 Greenspun 的第十条规则:

任何足够复杂的 C 或 Fortran 程序都包含了一半 Common Lisp 的临时实现,没有经过正式规范,并且存在漏洞和低效率。

也就是说,如果需要使用解释器,你很可能会创建出一些缺乏规范、低效且临时性质的东西。正确的解决方法是从一开始就使用合适的语言。
或者,另一种方法是将一个已知且规范良好的语言嵌入到您的应用程序中,例如 Guile(GNU 可嵌入 Scheme 语言),或者使用 Haskell 作为嵌入式领域特定语言。
但我没有在实践中看到这个--你们在构建自己的嵌入式语言方面有什么经验吗?这是一个好主意吗?比嵌入一个已经存在的语言更好吗?
(我并不是一个特别喜欢 Lisp 的粉丝。它不错,但 C、Haskell、Python 和许多其他语言也很棒。)

3
有一种思潮认为,“四人帮”中的Smalltalk程序员在书中加入解释器模式只是为了嘲笑C++社区。不幸的是,C++开发者没有领会这个笑话,现在我们却被困在这个模式里。(笑话的本意是,在Smalltalk中,程序员可以完全编程式地访问编译器和解释器,因此不需要像解释器模式这样的东西。) - Jörg W Mittag
10个回答

31

解释器模式中并没有规定必须使用其他编程语言的语法进行解释。如果需要解析一个简单的数学表达式,那么解释器正是合适的选择。

知道何时使用设计模式是防止它成为反模式的关键。


解释器模式是一种非常容易过时的模式(简单的数学表达式会变得越来越复杂,直到你拥有自己的Mathematica)。当比较短期和长期利益时,你真的必须考虑这种风险。 - coredump

14
任何设计模式如果被错误使用都是反模式。 解释器模式的好处:
  • 软件编译器
  • SQL评估引擎
  • 图形计算器输入解析器
  • XML解析器
这些都是解决语言中单词评估问题的程序,无论该语言是什么

1
"SQL 评估引擎" - 嗯...不是吧?据我所知,解释器模式要求从底部向上评估树。适度复杂的查询规划器将对足够复杂的查询进行转换,以加快查询速度,有时甚至可以大幅提升速度。使用自下而上的方式执行 SQL 不是正确的方法。(编译器也是如此)。但是...解释器是否只是指任何类型的 AST 遍历?另外,它不是与访问者相反/对称/正交的吗? - Jonas Kölker

8

请记住,“解释器模式”是面向对象编程中的一种特定设计模式。

它与“解释器”或其一般用途无关。


3
在某个时候,一个项目可能需要配置系统。一开始通常只需要简单的东西,例如键值对,以便系统可以连接到本地数据库等。如果只需要这些,通常会编写一个简单的解析器来解析INI或CSV方言,然后继续进行。但随着系统的增长和发展,配置变得更加重要。这是正常和健康的,将功能和责任重构到正确的抽象层。从这里开始,开发人员甚至(惊讶地)用户都希望有更具表现力的配置语言。在任何人注意到之前,系统内置并使用了图灵完备语言。这是Greenspunning的基本模式。在最后一步中,一个临时语言被推入了真正的计算工作领域。任何一个相当大的系统可能应该包含至少clisp的一半。

提前了解这一点是一个重要的步骤。编写大型系统的一种非常方便的方法是选择一个易于编写和修改的解释性语言,并在其中编写您的系统。TCL恰好就是为此而设计的,但如今很难让任何人在这种语言上支持大型项目。另一方面,现在有很多其他语言也提供了所有这些好处。

这样做的好处是Active File Pattern。简单的配置以与系统可用的语言解析器兼容的方式编写。由于文件正在被语言解析器解析,因此可以轻松地嵌入更复杂的逻辑。

一个在实际中的例子是 Django 的 settings.py。由于某种原因,Django 在猜测 Django 项目安装位置时并不聪明。通过在配置文件中使用少量 标准 Python,可以以一种便携的方式处理一般情况,适用于几乎所有可能的用户。尽管如此,大多数 settings.py 文件看起来像是典型的 .ini 风格配置文件中的普通键值对。
与解释器模式的关系在于,这些成熟的语言很可能会为甚至一些病态使用情况正确地使用该模式。一旦您知道需要解析某些内容,请想出一个非常好的理由不使用现有语言进行解析。

2
解释器模式并不意味着要编写一个完整的通用脚本语言。如果需要这样做,显然最好使用已经有人写过好书的知名语言(或者最好允许用户选择语言)。
解释器模式更多地涉及将对象图持久化,但选择可读且易于编辑的持久化格式(例如,2 + 3表示具有指向一对Integer对象的指针的Addition对象)。即使从未向客户端公开为“语言”,它仍有助于调试、支持、现场调试等方面。其他常见的例子是查询语言,如[N]Hibernate中的HQL——没有任何现有语言能够像此抽象级别描述查询那样好。
正如其他人建议的那样,XML通常用作基本语法(特别是在将持久化的对象图构想为“配置文件”时,还可以看到Microsoft的XAML),但实际上这是次优选择。JSON在UTF-8(或类似)中更加干净、易于解析、可读性更高、可编辑性更强,因此可能更适合大多数应用程序。但是,有一些出色的轻量级解析器库可以接受类似BNF的语法描述,然后可以在运行时将文本解析为类似DOM的可遍历结构,因此编写高度特定的简洁语法没有问题。

1
你显然不是Lisp的“粉丝”,因为符合该描述的男孩和女孩通常可以依靠知道Lisp是一种编译语言。Lisp出现于1958年。1961年的Lisp 1手册已经描述了编译。
解释很有用;它为我们提供了语义,而无需首先编写编译器。这种语义为编译语义提供了参考模型:理想情况下,解释和编译程序执行相同的操作。当我们发现它们没有时,我们要么解决它,要么以某种方式划分和证明情况。
解释可以用于引导编译的Lisp系统,而无需使用现有的Lisp实现,而是使用另一种语言,例如C。解释避免了在引导语言中编写Lisp编译器的需要。
ANSI Common Lisp的CLISP实现是一个很好的例子。要构建CLISP,您只需要一个C编译器。CLISP的Lisp编译器是用Lisp编写的。因此,当然,您没有任何东西可以运行该编译器。解决方案是使用用C编写的解释器来解释编译器。
在运行解释时,编译器会编译大部分Lisp库。其结果就是CLISP称之为“半编译内存映像”的内容:其中包含已编译的例程,但某些解释例程。(我认为编译器本身仍然是解释代码)。
这个半编译的镜像被用来编译剩余的代码,从而生成完全编译的镜像。
如果没有解释器,CLISP的编译器只能通过坚持先安装另一个Lisp实现来进行引导(就像需要C编译器来引导GCC一样)。否则,CLISP的编译器必须用C编写,以便使用现有的C编译器编译编译器,然后应用于Lisp代码以编译它,然后才能运行。
在明智的人眼中,没有人想用C编写Lisp编译器,在Lisp不普及的世界中,要求使用Lisp实现构建Lisp实现是一个巨大的劣势。在Lisp-compiler-in-C引导模型下,会出现用C编写的Lisp编译器,它是完全最小化且可能不完整的,只能发出质量较差的代码,仅足以启动引导过程,并且“真正”的编译器仍然是用Lisp编写的。

0
也许Lisp编译器的实现包括解释器模式的示例。
我认为你不应该说“轮子”是反模式,即使通常情况下你应该购买现成的轮子而不是重新发明它们。

0

解释器是JavaCC解析器生成器背后的理念。我认为它工作得很好。

解释器比单例更受尊重的GoF模式。那个需要被取消的是Singleton。也许还有备忘录。


0

XML被发明的原因之一是为了使我们免于编写EDI解释器;但至少该范围已经很好地定义了,而且我们都以大致相等的(至少足够)效率完成了它。至少我仍然足够幻想,认为这是正确的做法。

听起来你正在尝试开始一个新的城市传说?你是天生反对领域特定语言吗?


“你天生反对领域特定语言吗?”--一点也不。事实上,我正在使用嵌入式Scheme语言编写我的Wiimote-to-XTest适配器应用程序,并在用户配置文件API中使用了有限的原语集。我只是不确定如何看待解释器设计模式。//也许我对设计模式的理解方式不正确:将其视为一组严格的规则(我称之为“束缚模式的设计模式”),而实际上它们只是一组松散的指南和灵感(我称之为“启发模式的设计模式”)。 - Jonas Kölker

-1

通常编译器/解释器的阶段如下:

  1. 语法
  2. 解析树
  3. 抽象语法树(AST)
  4. 编译或解释

在Lisp中,第1和第2个阶段合并为第3个阶段。

因此,很明显复杂的程序可能会嵌入一个自定义语言,它是“一种特定于应用程序、非正式指定、错误丛生、实现缓慢的Common Lisp的一半”。

然而,我必须说手写AST树是不愉快的,并不是所有Lisper声称的终极语言。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接