那么,您将编译时计算用于什么呢?
随着时间的流逝,该博客将涵盖以下示例:
- 您想要一个具有多个字段的结构。 将有算术(+,-,*,/)的成员函数应应用于所有字段。 您如何确保所有操作员都覆盖所有字段? 您是否想手工编写所有这些方法,希望您永远不会忘记一个字段,以后再继承的人也不会?
- 代码文字,比如说基于字符串的正则表达式。 是否要在运行时,启动时进行编译? 为什么? 如果在编译时已知它们,则将花费不必要的额外时间。 但是它们在编译时可能未知。 在Lisp中,您编写了一个宏,该宏在编译时会编译一个正则表达式(如果它是文字),如果不是则将其推迟到运行时。 另外一个好处是,您不仅可以在此处节省运行时间,还可以在编译时启用有关损坏的正则表达式文字的警告 。 Perl说,许多语言都这样做。 但是他们将其内置到语言和编译器中。 在Lisp中,您可以执行此操作而无需修改编译器。
- 与例如printf格式字符串检查(写入对象参数的类型和数量)相同。 在Common Lisp中,您可以在不修改编译器的情况下实现这种编译时检查。 现在,您真正想要的是编写自己的警告发射器,例如那些printf格式的字符串警告和正则表达式检查, 您可以将它们提供给您的库用户,而不必向他们提供被黑的编译器。 使用真正的编译时计算,您可以做到这一点。
- 让我们看看将非OO语言转换为面向对象语言所花费的精力。 需要多少编译器支持? 对我来说看起来很丑。 C ++是否完成? 好吧,C ++放弃了面向对象,不是吗? 使用Common Lisp中实现的编译时计算,您可以将structs-n-functions语言转换为面向对象的语言,而无需修改编译器。 实际上,这是Common Lisp为获得OO而所做的 。 您可以稍后添加编译器对速度优化的支持,但不必这样做 。 CL社区实现的OO语言最初是纯宏。 或者说您讨厌OO编程,并且对更好的东西有一个想法。 您是要为新的顶级元语言(例如模板)破解编译器,还是要使用相同的基本语言来进行编译? 此示例将主要涵盖现有的CLOS包。 哦,还有其他可以替代使用的Common Lisp OO软件包,甚至可以混入同一程序中。
- 带有单位的数字源代码文字,以及如果在编译时(以及在运行时)知道的话,则在编译时完成从记下的单位数字转换为内部使用的单位的转换。 如果可以在编译时证明,则跳过对单元兼容性的检查。 如果在编译时证明了单元兼容性,则不必通过拖拉单元信息来减慢代码速度。 不要小看它的重要性。 由于科学单位的混乱,我们(人类)炸毁了很多东西,包括飞船和水船。 让我们确保安全。
- 您用代码写下了一个数学公式,并用一个变量解决了。 您的程序还需要由另一个变量解决的相同公式。 为什么要在一张纸上解决此问题,然后将第二个功能写到源代码中呢? 计算机应该足够聪明,您可以一次将其传递给它,并要求它为变体生成代码,并通过该定义的不同变量来解决。 一个源代码定义,几个要调用的编译函数。 它可以防止人为错误,如果您以后需要调整公式(否则称为“不再…”),则将为您提供很大的帮助。 您无需在多个位置进行编辑,而是可以在一个位置控制此公式及其所有变体。 Common Lisp中的编译时计算功能足够强大。 现在已经说了……我们正在推动我到目前为止实际执行的工作。 这是相当先进的,并且编写起来并不很快。 我希望我可以为此使用Maxima。 在我接触到这一部分时,我们将看到,您将了解解决此类问题的方式。
- 在运行时避免反射。 这不是Common Lisp的例子。 我只是对在运行时用反射代替编译时计算有多糟糕而感到恼火。 除了将所有速度放到地板上之外,您还打开了以前静态键入和编译的程序,以解决运行时错误。 对于更多的香料,想象一下在运行时反映的那些结构代表数据库模式,现在有些东西不同步并在生产中抛出运行时错误消息,而不是在编译时使用反射进行检查。 是的,您的语法正确的CL源代码可以在编译时执行(检查(读取(/ bin / sh“ ./gimmetheschema”))。运行时反射在这里确实是一个糟糕的替代品,如果在大多数情况下不需要您已经进行了编译时间计算(这是上面示例1的更一般的情况)。
- 希望我能将其应用于更深奥的物品。 这样的事情就是Common Lisp如此强大,以至于如果您的编译器太笨拙而没有内联函数,则您可以自己修复它。 您编写一个宏来转换您的函数定义。 它逐条语句,然后将函数重写为宏。 然后,它将宏版本放入要编译的代码中,您就在这里。 现在是内联的,机器代码明智的。 当然,这在很多方面都是疯狂的,而且我认为我无法向您展示执行此操作的ITA代码。 哦。 Nonono,这并不是我所希望的理论。 我们有。 它说明了语言的原始表达能力。 在较温和的气候下,我会使用此功能根据自己选择的任意随机标准(在编译期间基于内联或非内联)将功能更改为即时内联或非内联(编译的代码大小,接近开始的局部出口,基准测试中调用的次数) ,而不必使用任何外部工具(也不必在源代码文件之间移动这些功能)。
错误处理
在本系列中,您还将逐渐认识到在编译时和运行时使用相同语言的主要优点之一。 这是错误处理。 您用于实现上述目标的所有这些宏都是常规的 Common Lisp代码。 整个语言都可用,包括您曾经为它编写的所有库。 并且所有的错误处理功能都可以在编译时使用 。 这不是一些疯狂的C ++模板,它会给您一个错误消息^ H ^ H ^ Hnovel,它需要一个Web应用程序才能将其转换为可读性。 使用真正的编译时计算,编译时错误消息来自以您的主要语言编写的代码,并且编译时错误消息与运行时一样清晰或不清楚。 是的,在编译时,您具有回溯,调试器,逐步评估,检查和常规数据结构。 您甚至可以使用常规探查器来修复会大大降低编译速度的性能错误。
您可以在编译时的Lisp代码上使用常规的Lisp调试器。 曾经尝试在C ++模板扩展上使用gdb吗?
目标
总体而言,您将看到示例如何实现我们的主要目标:
- 避免重复人工编写/可能被人类更改/不同的源代码。
- 因为它更安全(编译器的警告和编译时运行的您自己的代码的警告),所以它在编译时(而不是运行时)将尽最大的努力,并使程序运行得更快(*)。
同时也追求次要目标:
- 使具有复杂项目(例如复杂文字)的源代码尽可能易于阅读,而不必编写自定义词法分析器。 如果现在还不很清楚,您可以在Common Lisp中将运行时可能的任何数据结构表示为源代码文字。 其中包括图形和树。
- 保持变更历史记录上的源代码差异小且可读。 最初的小变化不会产生样板和巨大的级联差异,而淹没了真正的变化。 永远记住,您可能想在以后(拥有大量)长达15年的更改中阅读此内容。 其中最糟糕的是需要大量更改噪声样板的语言,以至于人们让他们的IDE替您做样板更改。 对于下周的截止日期来说,这很好,但它并没有解决15年后跟进变化的观点。
- 当它不适合手头的项目时,不要被迫进行面向对象的编程-只是因为您的编译器恰好只支持OO范例(对C ++的称赞,以使该语言对其他方法开放,BTW)。
在我完善,重新思考和解释所有这些内容时,请稍候,如果您想同时更深入地介绍这些主题,我特别推荐两本书:
保罗·格雷厄姆(Paul Graham)的《论Lisp》 。 这是Lisp书,最着重于Macros。 现在可以免费获得(还有一本纸质书):http://www.paulgraham.com/onlisp.html
彼得·诺维格(Peter Norvig)的“人工智能编程范例:Common Lisp中的案例研究” 。 与“ On Lisp”相比,这本书在复杂的Lisp宏上要轻得多。 彼得·诺维格(Peter Norvig)的魅力在于,以一种元素的方式向您展示了它的力量,以您想要的方式表达想法和假设,并使事情保持多变。 仅纸质书:https://www.amazon.com/Paradigms-Artificial-Intelligence-Programming-Studies
脚注:
(*)极端,如果没有未知的数据实际影响输出流,那么正确的编译时计算可能会完全消除运行时。也不是理论上的问题,请查看Jeffrey Mark Siskind的Scheme的“ Stalin”编译器。 https://engineering.purdue.edu/~qobi/software.html
再次感谢dcooper8 @进行编辑和提供常规帮助。
这是系列的第2部分。 第1部分在这里:https://medium.com/@MartinCracauer/a-gentle-introduction-to-compile-time-computing-part-1-d4d96099cea0
第3部分即将推出。
