访问抽象语法树(AST)的好处,Julia如何利用它?
我看到有人说,Julia可以访问它运行的代码的抽象语法树。这到底是什么意思呢?是说运行时可以访问它,还是代码本身可以访问,或者两者都可以?
接着问:
- 这是不是Julia和其他动态语言,特别是Python之间的一个关键区别?
- 能够访问抽象语法树有什么实际好处?
能否举个例子,说明在Python中不容易做到的事情,但在Julia中可以做到,原因就是这个?
3 个回答
举个例子:循环展开,不需要复制和粘贴,它使用编译时的参数,这样你可以轻松调整想要展开多少次循环。
这里有一个很棒的资源,介绍了Julia的元编程:https://en.wikibooks.org/wiki/Introducing_Julia/Metaprogramming
朱莉亚(Julia)和像Python这样的语言最大的不同在于,朱莉亚允许你在代码被执行之前对其进行干预。宏(Macros)其实就是用朱莉亚写的函数,它们让你可以在代码运行之前访问和修改这些代码。此外,朱莉亚并不是把代码当作字符串来处理(比如 "f(x)"
),而是把它作为一种朱莉亚对象提供(比如 Expr(:call, :f, :x)
)。
这让朱莉亚能够做很多在Python中无法做到的事情。主要有以下几点:
你可以在编译时做更多的工作,从而提高性能
两个很好的例子是正则表达式(regex)和打印格式(printf)。这两者都需要某种格式说明,并以某种方式进行解释。现在,这些可以相对简单地实现为函数,可能看起来像这样:
match(Regex(".*"), str)
printf("%d", num)
但问题在于,这些说明每次运行语句时都必须重新解释。每次解释器处理这个代码块时,正则表达式都必须重新编译成状态机,而格式说明也必须通过一个小型解释器来运行。另一方面,如果我们把这些实现为宏:
match(r".*", str)
@printf("%d", num)
那么 r
和 @printf
宏会在编译时拦截代码,并在那个时候运行各自的解释器。正则表达式变成了一个快速的状态机,而 @printf
语句则变成了简单的 println(num)
。在运行时,所需的工作量最小化,因此代码运行得非常快。现在,其他语言也能提供快速的正则表达式,比如通过提供特殊的语法来实现——但在朱莉亚中没有特殊处理的事实意味着开发者可以在自己的代码中使用相同的技术。
你可以为几乎任何东西制作迷你编译器
带有宏的语言往往能够支持更强大的嵌入式领域特定语言(DSL),因为你可以随意改变语言的语义。例如,代数建模语言 JuMP.jl。Clojure 也有一些很酷的例子,比如它的嵌入式 逻辑编程语言。Mathematica.jl 甚至在朱莉亚中嵌入了Mathematica的语义,这样你就可以写出非常自然的符号表达式,比如 @Integrate(log(x), {x,0,2})
。在Python中你可以做到这一点,但没有那么干净或高效。
如果这还不能说服你,想想看,有人用宏在纯朱莉亚中实现了一个 交互式朱莉亚调试器。试试在Python中做到这一点。
补充:另一个在其他语言中很难实现的好例子是 Cartesian.jl,它让你可以在任意维度的数组上编写通用算法。