在C ++中嵌入LISP - 一种食谱

2021-06-09 13:30:13

可以称,Lisp可以同时成为世界上最常见的最常见的最罕见的编程语言。我们可以量化这一点。在http://www.tiobe.com/index.php/content/papfo/tpci/index.html中掌舵编程语言的Tiobe指数。

截至2014年8月,包装的主要嫌疑人居住...... C,Java,Objective-C& C ++

除了普及,这当然没有任何人证明。单镜头反射相机,而对于摄影无限更有用,仍然不如智能手机内置的相机常见。

当我说Lisp比任何其他编程语言更常见时,我的意思是:无论你在Python或C ++中编程,Lisp都是总是阻止幕后脚手架的内容。这是真的,因为Lisp是您编译器的Lingua Franca。您的编译器不适用于您在Python中看到的C ++或大量空白空间中看到的语法。相反,它在最早的时刻丢弃了这一点,并将代码转换为抽象语法树或AST。 AST由包含陈述和表达式的列表列表组成。您的编译器更喜欢使用代码的这种格式,因为它需要一种适合代表代码作为数据的格式,Lisp的一个核心原则。它需要这是因为它必须能够转换和优化代码。例如,它可能希望ELINE,重新安排或并行化。然而,它必须推理它所做的转换的等价。换句话说,您的编译器需要“计算代码”,精确地通过其谐波语法来完善。因此,您的编译器借此概念来使用抽象语法树构建一个或多个中间表示(IR)。作为列表处理器并使用抽象语法树,您的编译器本质上可能被视为LISP引擎。我们可以从GCC和Clang等编译器中提取此中间表示。

让我们拍摄来自函数主的C ++函数方形()的非常简单的示例。

clang使用命令“clang ++ -cc1-ast-dump hellofun.cpp”来为函数方形()生成此AST

立即明确是Cononally正确的Lisp缩进和代码布局。此格式实际上非常亮起,因为它使得它可以显而易见,其中编译器推断出类型。同样,我们可能会引起来自GCC的AST,如下所示。我们使用“g ++ -fdump-rtl-dfinish hello fun.cpp”。

从摄影中返回我们的类比,内置于智能手机中的机会较低的摄像机比专业的单镜头反射相机更常见。但对于平均专业摄影师来说,这无关紧要。专业的婚礼摄影师捕捉生活在迷人的肖像中的闪亮时刻不会发现自己被迫到达智能手机相机。但是,如果婚纱摄影需要数月,这会如何不同,并且可以利用世界过度的智能手机用户多级的现有工作?进一步假设重新使用依赖于摄影材料的兼容性。在这里介绍了哈斯克尔,OCAML&amp等编程语言的问题;丽斯普。它们非常表达。但是他们需要某些数学敏锐,将其主流主流。因此,它在其上解决的大多数问题以较少的表达语言表示。所以,虽然Haskell,Ocaml& Lisp更具表现力,如果你必须表达最大部分的一切,所以更具表现力是什么?务实意味着实现重新使用非常成熟的Java,C ++和amp的财富; Python库可以与编写此类图书馆以一种更具表现性语言写入此类图书馆等级,也可以更有用。当然,这种考虑因素受其他因素,例如您是否需要证明正确性,或者自己宣传第三方图书馆的能力。了解您的要求将在这里走很长的路。

未来可以预期哪些趋势?我们强调了LISP上C ++的融合以及以前文章中的C ++中的功能规划的出现。对于C ++这是新的。在Java中,这种趋势是旧的,通过入场开放。 Guy Steele,Sun Microsystems的Java语言规范的共同作者被引用如同说“我们在C ++程序员之后。我们设法拖累了大约一半到丽斯普。“可以在上下文中阅读原始报价http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg04045.html。 Guy Steele也被称为Lisp方言计划的作者。然而,讽刺是:每次另一个编程语言都采用Lisp的更多功能 - 功能规划和Haskell,OCAML等的相同都是如此 - 这减少了Lisp本身,或Haskell或Ocaml。为什么 ?因为成本效益分析提示,支持较低的表现力,更多的主流语言。这是真的,因为主流语言将始终拥有卓越的图书馆支持,但专门由Lisp(或Haskell,OCAML等)专门赋予的优势列表刚刚减少。所以而不是作为倡导的情况,趋势变得彻底挫败。编程语言通常被认为是男机界面。它们只是程序员和软件工程师之间的沟通媒介。它受到推理,那么趋势将永远是主流。随着大学课程的目标是更广泛的受众,计算机科学家和数学家仍然是一个问题,所以太主流它将朝着规划语言的趋势趋势,这些语言需要较少的数学敏感,而不是由OCAML,HASKELL或LISP所要求的数学敏谱。

当他设计的Clojure时,富豪的HICKEY了解这一点。因为只有一件事比在你的指尖中重新使用的极端表达力或成熟的图书馆,所以这就是让它们都有。再次,有先例。常见的LISP通过ECL,嵌入式常见的LISP嵌入了一段时间内嵌入了C. ECL传统上专注于嵌入C中,但不太众所周知的也适用于C ++,包括最近的C ++ 11。巧合,随着C ++在封闭和功能中趋于建模状态,缺乏强调ECL中的对象方向将变得越来越少。

2)通过Python样式直接在C ++进程中进行“现场编程”

5)重新使用的能力不仅从LISP中使用C ++库,还重新使用所有Lisp的库

如果您从来源构建ECL,请确保使用C ++支持。有关示例,请参阅下面的屏幕截图。

以下是我们配方的C ++源代码。假设您在/ usr / local prefix下找到ECL安装,并且在进一步假设您在单个文件Main.cpp中保存了源代码,您可能会按如下方式编译此示例:

请注意,此示例假定具有LLVM后端的OSX和G ++,该后端需要-stdlib = libstdc ++标志。在Linux上,这是不需要的。请参阅代码中的注释以获取解释。

如果您运行上述G ++命令行,则将有一个名为a.out的二进制文件。让我们开始这个。

所以发生了什么事 ?我们在C ++中初始化了我们的LISP引擎,通过initrc.lisp执行了任何相关的初始化。这将在以后非常有用。然后,我们评估了一个LISP函数(Makeanumber)并将其输出流式传输到COUT。值得注意的是,C ++ 11很乐意推断从ECLS ECO_TO_FLOAT()函数的类型,从而消除了类型声明中的任何冗余。顺便提及(MakeAnumber)已被编译。随后我们进入了我们的程序的主循环。

我们现在使用LISP的优秀异常处理和重新启动系统,在C ++进程内有一个Repl。重新启动是LISP的粮食点之一,一个人尚未找到它进入C ++的方式。有一个答案意味着我们可以捅一下。无论我们在这里做什么都将被解释。我们定义的函数之一是(运行时)它表示我们的循环变量。让我们试试吧。

好的,所以我们的C ++循环变量具有值6.但是真的我们可以运行Lisp具有范围的任何东西...算术,任何内容。这真是个有用的,因为我们可以例如交互地重新定义LISP函数随后称为我们的C ++程序的定期执行的一部分。这会引发整个编程风格,否则外星为C ++:现场编程。

事实上,让我们现在做到这一点。我们从C ++调用的LISP函数(Makeanumber)评估到常量3.2。我们可以通过在REPT中重新评估它来验证这一点。让我们改变它。我们将重新定义返回其他内容的功能:6.4。

在这里使用常量没有任何固有的。这可能是任意复杂的操作。事实上,我们可能会发现其他方法可以将操作注入我们的程序,除了击中Ctrl-C并获取求助。例如,我们可以通过Zero-MQ的一些流行的消息总线技术来注入此逻辑,这是一种绘图一系列架构模式。当然,我们的C ++程序,不再调用(MakeAnumber),但如果它确实如此,你就会得到这个想法...立即反馈而没有编辑编译-调试周期。因此,名称直播编程。

现在让我们将C ++运行时间困惑一下。假设我们希望我们的循环变量遵守值60,然后从那里开始。还记得那些重启吗?诸如Ctrl-C等异常“重新启动”。只是告诉LISP(继续)。

超出实时编程和REPT,很容易看出如何扩展此范例以提供配置管理。如果我们可以在文件中设置应用程序参数和脚本,而无需编译并链接新的C ++二进制文件,那么我们可以提供配置管理的手段。但我们今天没有XML吗?我们的确是。并且XML与LISP基础配置的一对一比较可以填充页面并启动几种火焰战争。然而这不是目标。我们会观察到我们已包括一个单个标题文件,ECL.H,并在C ++中的REPT结束,直播编程和配置管理 - 全部。软件工程的一个关键目标是管理和降低复杂性。 Astute Reader将观察到所有锅炉板代码到目前为止,符合大约50行的代码 - 不包括评论。解决500行代码中的问题的范式是解决方法的较小范式,该范式可以解决50行代码中的相同问题。在50行代码中解决3个或更多问题的范式......

不仅仅是一个求助,这就是所谓的断裂循环隐藏了一个完整的符号调试器。

只是为了回顾,到目前为止,我们已经看到了C ++呼叫在线Lisp; Lisp呼叫C ++;在C ++进程内部的LISP求页; C ++内部完整的符号LISP调试器;字节编译和解释的执行方式;以及琐碎的实时编程。我们尚未与LISP的包管理系统完全集成,并在C ++内完全编译的LISP代码。

有关包管理的更多信息,您可能希望在ASDF和QuickLisp上读取。在QuickLisp下有一些1000多个库。我们将跳过细节,但认为cmake-yobon-pip组合。想象一下,我想使用sqlite - 我如何将其申请可用?喜欢:

这实现了相当于Python的PIP安装。我们如何在LISP应用程序中提供此功能?我们“要求”它。

Pythonistas知道这是“导入”。但这是完全编译的代码。没有翻译,没有GIL(Python全球解释器锁定并发)。与Python一样方便。

真正的问题是:我如何在C ++内部提供此功能?嗯,基本上与我们在求助中展示的方式相同。什么在REPT中有效,如果BYTE编译或完全编译,则相同的工作。当ECL启动时,它会从用户的主目录中加载名为.eclrc的引导文件。我的.eclrc文件有三行。前两个是:

第一个进口ASDF,第二个进口QuickLisp。默认情况下,嵌入的ECL实例不加载.eclrc,因为期望应用程序可能会部署在开发人员主目录的上下文之外。但我们的配方已经设想了自己的引导文件,称为initrc.lisp - 与我们的C ++应用程序一起关联。从我们的嵌入式LISP C ++应用程序中加载SQLite支持,因此基本上还减少了:

这将我们带来了最后一点:完全编制我们的LISP代码,以便在C ++应用程序内更好地表现。我们之后的是Python的表现因表现没有巨大的表现。要使这一点更有趣,我们将直接在Lisp内联机C ++。 Matthias Benkard的期刊在ECL中如何在C ++方面有一个很好的帖子。 (C-Inline)宏可以被说服为内联C ++以及C.从发布中没有立即显而易见的是,所呈现的代码不是立即使用的代码。相反,不含C ++假定静态编译。 Matthias提供以下示例:

要使用Matthias的代码,我们必须首先编译它 - 正如我们可能希望与任何C ++源都希望。我们通过直接从Lisp内直接通过(编译文件)和(加载)来执行此操作。现在正在执行函数(C ++ HEX)按预期工作。

如果我们希望在我们的C ++配方中利用这种技术,我们需要对我们的初始化()函数进行一次小修改 - 两行代码。我们用(Compile-File)替换(加载)和后续(加载),后者闪烁文件名扩展。

运行C ++应用程序时,这会产生有趣的JIT样式行为。我们甚至可以观察系统Clang编译器,以魔术为代表,因为Clang警告正在寻找标准化的方式。

要得出结论,我们已经改变了一行LISP代码,并为我们的C ++配方添加了另一行,并生效地添加了Lisp JIT和C ++ JitCapabilities - 原型和在编码一分钟内完成工作。解决复杂性与最小的移动部件:这是软件工程的全部!

像Clojure到Java一样,ECL可用于在C ++中托管Lisp。 在如何利用这种功能的功能抽象中,在功能抽象中转到Meta循环冒险经历。 与ECL和C ++的一个关键区别是我们对我们的JIT过程的AOT有效而有效地控制。 我们可能选择在我们选择的点时解释,字节编译或完全AOT / JIT编译。