Elixir 和 Julia 之类的语言在啥意义上是谐音的?

Posted

技术标签:

【中文标题】Elixir 和 Julia 之类的语言在啥意义上是谐音的?【英文标题】:In what sense are languages like Elixir and Julia homoiconic?Elixir 和 Julia 之类的语言在什么意义上是谐音的? 【发布时间】:2015-10-22 09:36:54 【问题描述】:

Lisp 中的同音性很容易看出:

(+ 1 2)

既是对+ 的函数调用,以12 作为参数,也是一个包含+12 的列表。它同时是代码和数据。

不过,在像 Julia 这样的语言中:

1 + 2

我知道我们可以在 Julia 中将其解析为 Expr

:(1 + 2)

然后我们可以获取 AST 并对其进行操作:

julia> Meta.show_sexpr(:(1+2)) (:call, :+, 1, 2)

因此,我们可以在 Julia(和 Elixir)中操作程序的 AST。但它们是否与 Lisp 具有相同的意义?任何代码的 sn-p 真的只是语言本身的数据结构吗?

我看不出像 Julia 中的 1 + 2 这样的代码是如何立即成为数据的——就像 Lisp 中的 (+ 1 2) 那样只是一个列表。那还是谐音吗?

【问题讨论】:

您可能有兴趣知道 Julia 至少不再声称它是同音字了。主要原因是它的定义不明确,因此声称如此引起了一些纯粹主义者的愤怒。您不会在文档或网站中找到该词。 @IainDunning 确实很有趣 - 也许可以肯定地说,像 Julia 和 Elixir 这样的语言支持元编程和宏,但不是因为同音性。 是的,我认为这是删除它的逻辑。 Julia 可能不是同义词,但你可以做与这个想法相关的大部分事情(我非正式地认为它是内置的相对易于使用的编写代码来操作代码的能力)。 【参考方案1】:

用比尔克林顿的话来说,“这取决于‘是’这个词的含义是什么”。好吧,好吧,不是真的,但这确实取决于“同音”这个词的含义。这个词有足够的争议,我们不再说 Julia 是谐音的——所以你可以自己决定它是否符合条件。我不会试图定义同音性,而是引用 Kent Pitman(谁知道关于 Lisp 的一两件事)在 2001 年 Slashdot interview 中所说的话:

我喜欢 Lisp 愿意代表自己。人们经常将此解释为它代表自己的能力,但我认为这是错误的。大多数语言都能够代表自己,但他们根本没有意愿。 Lisp 程序由列表表示,程序员知道这一点。如果它是数组也没关系。重要的是它表示的是程序结构,而不是字符语法,但除此之外,选择是相当随意的。代表是正确的选择并不重要。重要的是它是一个共同的、商定的选择,这样才能有一个丰富的程序操纵程序社区在这种共同的表示中“进行交易”。

他也没有定义同音性——他可能比我更不想进入定义论证。但他切入了问题的核心:一种语言有多愿意代表自己? Lisp 非常愿意——你甚至无法避免它:程序作为数据的表示只是坐在那里,盯着你的脸。 Julia 不使用 S-expression 语法,因此代码作为数据的表示不太明显,但它并没有隐藏得很深:

julia> ex = :(2a + b + 1)
:(2a + b + 1)

julia> dump(ex)
Expr
  head: Symbol call
  args: Array(Any,(4,))
    1: Symbol +
    2: Expr
      head: Symbol call
      args: Array(Any,(3,))
        1: Symbol *
        2: Int64 2
        3: Symbol a
      typ: Any
    3: Symbol b
    4: Int64 1
  typ: Any

julia> Meta.show_sexpr(ex)
(:call, :+, (:call, :*, 2, :a), :b, 1)

julia> ex.args[3]
:b

julia> ex.args[3] = :(3b)
:(3b)

julia> ex
:(2a + 3b + 1)

Julia 代码由Expr 类型(以及符号和原子)表示,虽然表面语法和结构之间的对应关系不太明显,但它仍然存在。更重要的是,人们知道代码只是可以生成和操作的数据,因此正如 KMP 所说,有一个“丰富的程序操作程序社区”。

这不仅仅是 Julia 代码作为数据结构的肤浅呈现——这就是 Julia 将其代码呈现给自己的方式。当您在 REPL 中输入表达式时,它会被解析为 Expr 对象。然后将那些Expr 对象传递给eval,这会将它们“降低”为更常规的Expr 对象,然后将其传递给类型推断,全部实现in Julia。关键是编译器使用与您看到的完全相同的代码表示。 Lisp 中的情况并没有什么不同。当您查看 Lisp 代码时,您实际上并没有看到列表对象——它们只存在于计算机的内存中。您所看到的是列表文字的文本表示,Lisp 解释器将其解析并转换为列表对象,然后进行评估,就像 Julia 一样。 Julia 的语法可以看作是 Expr 文字的文本表示——Expr 恰好是一个比列表更不通用的数据结构。

我不知道细节,但我怀疑 Elixir 很相似——也许 José 会插话。

更新(2019 年)

在过去 4 年多的时间里,我对此进行了更多思考,我认为 Lisp 和 Julia 之间的主要区别在于:

在 Lisp 中,代码的语法与用于表示该代码的数据结构的语法相同。 在 Julia 中,代码的语法与表示该代码的数据结构的语法完全不同。

为什么这很重要?在支持 Julia 的一方,人们喜欢事物的特殊语法,并且经常发现 S 表达式语法不方便或令人不快。在支持 Lisp 方面,当您尝试生成的数据结构的语法(表示代码)与您通常编写的代码的语法相同时,更容易弄清楚如何正确进行元编程.这就是为什么当人们尝试在 Julia 中编写宏时,最好的建议之一是执行以下操作:

    编写一个您希望宏生成的代码类型的示例 在该代码上调用 Meta.@dump 以将其视为数据结构 编写代码以生成该数据结构 - 这是您的宏。

在 Lisp 中,您不必执行第 2 步,因为代码的语法已经与数据结构的语法相同。 Julia 中有 quasiquoting(在 Lisp 中)quote ... end:(...) 构造,它们允许您使用代码语法构造数据结构,但这仍然不如让它们首先使用相同的语法那么直接。

另请参阅:

https://docs.julialang.org/en/v1/manual/metaprogramming/ What is a "symbol" in Julia?

【讨论】:

因此它更像是一个 PEPL,而不是一个 REPL。因为它不读取数据表达式(像 Lisp 那样),而是将程序表达式解析为 expr 类型的专用数据。 我不确定这有什么不同——尽管 S-exprs 的语法很少,但仍然需要解析,所以 Lisp REPL 也确实是 PEPL。我认为主要区别在于Expr 类型不是通用类型——它专门用于表示代码。 不同之处在于 Lisp 中的编程语言解析不是在 READ 中对文本输入进行的,但在 Lisp 数据结构中的 EVAL 中略有不同。 EVAL 可能不会将 Lisp 数据转换为表达式数据结构 - 无需该步骤即可实现评估器。 @RainerJoswig,我没有关注你——列表是 Lisp 表达式数据结构。 列表只是列表。没有关于 arg 是什么、数据是什么、定义是什么、调用是什么、绑定是什么、函数是什么等的编码。Lisp 列表对 Lisp 语法一无所知。 READ 读取任何 s 表达式格式的数据,并且它对 Lisp 语法的了解

以上是关于Elixir 和 Julia 之类的语言在啥意义上是谐音的?的主要内容,如果未能解决你的问题,请参考以下文章

在处理错误时,在啥意义上,使用异常比使用 if...else...switch... 等更好?

这个Elixir函数中的(反向)赋值匹配有什么意义?

在 Elixir/Erlang 中的(本地)Mnesia 实例上实现最佳写入性能

C语言中的 局部变量,存储在啥地方?

如何在 Julia 语言中绘制圆形扇区?

Elixir 变量真的是不可变的吗?