Elixir 的 If 和 Elixir 的 Do
最近我发现 Elixir 充满了宏,并且允许你非常轻松地与其内部语法树进行交互(使用quote),我觉得这很酷。
简单来说,宏是特殊的函数,它们返回一个带引号的表达式,该表达式将被插入到我们的应用程序代码中。—— Elixir 语言
我遇到的一个例子如下——假设我们有一个表达式需要求值:
if true do
"hello world"
end
但有时候你只想写一行表达式。没问题,朋友,Elixir 可以满足你的需求!
if true, do: "hello world"
这句话虽然只有短短一行,却让我兴奋不已,忍不住想写篇文章来探讨一下。让我们一起来看看为什么吧!
在这个简单的表达中,有三点需要注意。
- 前面有个逗号
do - do后面有个冒号。
- 没有关键词
end
这三点暗示着一个令我震惊的真相。
你刚才看到的语句if?它实际上并不是语言关键字,而只是一个普通函数(实际上,它是一个宏)。
do你刚才看到的那个代码块?它也不是语言关键字,而是一个关键字列表(严格来说,根据源代码,它是一个关键字列表关键字)。如果你不知道什么是关键字列表,请继续阅读,我稍后会解释。
这些提示对你有什么帮助?
让我们重新审视这些线索,看看这些线索如何帮助我们揭开一直隐藏在眼前的真相!
1)逗号
如果你之前仔细观察过,第一个提示就指出了这一点,true并且这个do陈述是分开的。
你知道还有什么被分开了吗?函数参数。
if语句实际上只是一个参数数为 2 的函数,第一个参数是要评估的条件,第二个参数是要执行的操作,逗号分隔这两个参数。
这意味着我们可以把我们之前的陈述翻译成这样,它仍然有效。
iex(2)> if(true, do: "hello world")
"hello world"
我只是在它周围加了括号——现在看起来是不是更熟悉了? ;)
2)结肠
第二条和第三条提示实际上非常密切相关,因为它们都指向同一个方向:关键词列表。
让我们回顾一下什么是关键词列表,或者它长什么样。
官方定义Keyword List
关键字是一个包含两个元素的元组的列表,其中元组的第一个元素是一个原子,第二个元素可以是任何值。—— Elixir 语言
我们知道列表长什么样 ->[]
我们知道元组长什么样 ->{}
所以,关键词列表看起来像这样:
[{:atom, "any_value"}]
好吧,这样写起来不太美观/方便,所以 Elixir 提供了一种语法糖,可以像这样编写关键字列表:
[atom: "any_value"]
此时,我们可以运用这种新获得的逻辑来推导出我们之前的if结论,结论可以这样表述:
iex(2)> if(true, [do: "hello world"])
"hello world"
我在这里所做的只是在do关键字周围添加了方括号,现在它们的含义开始变得清晰了。
事实上,我们还可以对关键字列表的原始语法进行一些更疯狂的修改……
iex(2)> if(true, [{:do, "hello world"}])
"hello world"
太好了,还能用!
3)什么时候是end?(还有校对时间!)
好了,我已经告诉过你们它们是一样的,而且功能也完全一样,但你们可能觉得我是在胡说八道(虽然我保证我不是)。那么,让我们来证明我说的是对的!
我还没有告诉你第三个提示,那就是——end关键词去哪儿了?
所以,end实际上它被完全忽略了,因为在抽象语法树中没有必要表示end`of` 语句——你应该能够从语法树本身推断出来。稍后我也会展示 Elixir 源代码中的证据。
我不会过多谈论AST,一部分原因是它本身就是一个完全不同的话题,另一部分原因是说实话,我对它了解得也不够多(😬),但这里我先简单介绍一下我目前所理解的内容:
抽象语法树(AST)是机器理解代码流程的方式。它根据编程语言的语法以及机器执行代码的流程构建一个树状结构。
从本质上讲,你编写一段文本(代码),语言尝试理解它(解析),然后它会创建一个它当前理解内容的心理模型(抽象语法树)。
我之所以运行不同的表达式来生成 AST 以证明它们是相同的,是因为代码的相同性并不取决于任何语法糖,而是取决于语言真正理解的内容,在我们的例子中,就是语言的心理模型(AST)。
是时候核实了!
first = quote do
if true do
"hello world"
end
end
# {:if, [context: Elixir, import: Kernel], [true, [do: "hello world"]]}
second = quote do
if true, do: "hello world"
end
# {:if, [context: Elixir, import: Kernel], [true, [do: "hello world"]]}
third = quote do
if(true, do: "hello world")
end
# {:if, [context: Elixir, import: Kernel], [true, [do: "hello world"]]}
fourth = quote do
if(true, [do: "hello world"])
end
# {:if, [context: Elixir, import: Kernel], [true, [do: "hello world"]]}
fifth = quote do
if(true, [{:do, "hello world"}])
end
# {:if, [context: Elixir, import: Kernel], [true, [do: "hello world"]]}
我们可以进行最终检查,看看它们是否彼此相等!
first == second #=> true
second == third #=> true
third == fourth #=> true
fourth == fifth #=> true
坦白说,我不知道 Elixir 究竟是如何决定继续读取/解析语句直到end满足条件的,我觉得这涉及到词法分析、语法分析和标记化等完全不同的主题,我需要先理解它们(还有抽象语法树,我会理解的,但不是现在!)。
但是!我可以补充一点来支持这篇文章,那就是当 Elixir 将 AST 作为字符串输出时(使用 `\sigma` Macro.to_string/1),他们必须显式地将end单词附加到输出的字符串中,正如这里所看到的!
外卖
if true, do: "hello world"
我从这短短一行代码中学到了两件事:
if它只是一个语言宏(它会展开成一个case语句),事实上,在 Elixir 的世界中,关于元编程/宏,存在着一个全新的世界!do只不过是一个关键词列表而已。
我原以为像if语句或声明函数这样的基本事物应该是具有特殊含义的核心语言特性,但 Elixir 能够巧妙地利用 AST/关键字列表等基本特性,并以此构建其他特性。
这也意味着你可以构建任何你想要的宏!想构建unless类似 Ruby 的功能?你可以做到(如Elixir 官方指南所示),想构建循环?你也可以做到(Chris McCord 在他的Elixir 元编程入门视频while中演示了这一点)。
事实上……最后我只想强调一点。`self`def本身就是一个宏,使用 `.` 定义defmacro。而defmacro`self` 本身也是一个宏,使用 `.` 定义defmacro。是不是恍然大悟了?如果还没明白,请反复阅读这句话,直到理解为止。点击此处阅读更多内容。
作者注
我可能忽略了很多细节(特别是关于抽象语法树),因为坦白说,我对它们还不够了解(而且这篇文章的重点并不是抽象语法树,它们只是证明在不使用语法糖的情况下它们都是平等的手段)。
但这也就意味着我知道接下来要深入研究什么了,我已经迫不及待地想要学习更多关于词法分析器、语法分析器、分词器和抽象语法树的知识了 :) 如果你有任何相关的优质资源,请在下方评论区留言⬇️,我将不胜感激!
PS:必须承认,我是在 Elixir Slack 频道里了解到这一点的。Martin Svalin解释说它们都是等价的,于是我决定深入研究一下抽象语法树 (AST),以验证他的说法。剧透一下:他是对的!
文章来源:https://dev.to/edisonywh/elixirs-if-and-elixirs-do-jol
