在我的工作中,我有个任务是为Erlang程序员应聘者设计一套选择题考卷,我决定包含一个关于变量绑定的棘手问题。在加入这个问题之前,我决定自己先尝试一下(你懂的……我这是为了预防万一)并且我发现了一些可能让你第一眼看到觉得很惊讶的事情,尽管这在事后是很明显的。
问题
不用说,这个问题最后没有列入考试,所以不要指望从这篇文章中得到一个简单的答案。这个问题是:
假设一开始没有任何变量被绑定,那么下面的表达式计算结束后,哪些变量被绑定了?
|
|
选项:
- 没有
- A 和 B
- A、B、C 和 D
- 不可能确切地知道
请不要先往下阅读,而是停在这里!你自己先思考答案是什么?而且不能启动一个Erlang shell来测试这条表达式。切接不要偷看下面的内容!
答案
让我们先剔除那些显而易见的选项。
答案不可能是选项1,因为我们在表达式里明确地绑定了一个变量。选项1唯一能被选上的条件是表达式发生错误。毫无疑问,这不可能发生。
选项3也不正确,因为代码执行路径有两个分支,其中一个不会绑定C变量,另一个不会绑定D变量。
在我看来,答案是选项2。我认为,C 和 D只是case表达式里的临时变量。
但是,情况并非如此。
|
|
如你所见,有时候C被绑定,有时候却是D被绑定,这依赖于case语句所执行的分支,即使被执行的case语句分支的表达式不需要这些变量,也会发生这样的情况:
|
|
到底是为什么呢?
正如我之前说过的,这个问题可能令人惊讶,但事实上,它是非常明显的,而且有据可查。case语句分支的头部匹配变量是完全有效的,它可以像下面这样使用:
|
|
这是一种非常复杂的编码方式,我根本不推荐这样做,你甚至可以把它弄得更糟糕:
|
|
但是,除了代码的样式和可维护性问题之外,这段代码是完全有效的,这意味着在case表达式的被计算之后,在case子句中(包括在头部和身体中)被绑定的变量都是绑定的。我知道有更学术的方式来表达这个问题,比如用像闭包或作用域等词汇,但是我将把这个解释权留给像iraunkortasuna这样的大牛们,他们比我能干的多。
考虑到这一点,在我们最初的例子中,无论是C还是D(但不是两者)都是在对整个表达式的计算之后绑定的,这是合理的。但是,你想知道,这样的代码是非常不安全的……难道Erlang不应该警告我吗?Erlang不应该阻止我写出这样一个非确定性的东西吗?
当然,Erlang会帮我们的!不过不是Erlang的shell,而是Erlang的编译器,它会对这类代码进行告警,而且它非常聪明。如下面的例子:
|
|
这是我们的原始代码,在这种情况下,即使C或D在case语句之后是未绑定的,因为它们没有被使用,编译器什么也不说。
现在我们看看另一个版本的代码:
|
|
在这个例子里,编译器将不会编译这个模块,相反,它会报告如下错误:
|
|
我们可以对上述错误消息的可用性和可读性进行深入的讨论,但是这个模块不能编译的事实毫无疑问对Erlang开发者是有帮助的。
小贴士:如果在编译Erlang模块时看到类似这样的错误,请注意警告:8(其中使用了不安全变量)和4(变量可能绑定的地方)中的两个行号。
*原文链接https://medium.com/erlang-battleground/tricky-question-25a956298b9d