Core Erlang 示例
这篇博文是关于 Core Erlang 格式的第一篇。在这篇博文中,我们将通过比较 Erlang 代码和相应的 Core Erlang 代码的示例来介绍 Core Erlang 格式。
我使用了以下命令将我的示例模块转换为 Core Erlang 代码
$ erlc +time +to_core core_example.erl
Compiling "core_example"
parse_module : 0.000 s 10.8 kB
transform_module : 0.000 s 10.8 kB
lint_module : 0.003 s 10.8 kB
expand_records : 0.000 s 10.8 kB
core : 0.000 s 89.9 kB
sys_core_fold : 0.000 s 58.6 kB
core_transforms : 0.000 s 58.6 kB
listing : 0.002 s 58.6 kB
上一篇博客文章探讨了从 parse_module
到 expand_records
的过程。core
过程将抽象代码转换为 Core Erlang 代码。我们将在未来的博文中更多地讨论 Core Erlang 过程。
我稍微编辑了这些示例,使它们更容易阅读。在本博文的最后将有一个未编辑的示例。
有很多内容要讲,所以我们开始吧!
最简单的函数 #
让我们从最简单的函数开始,一个没有参数并返回原子值的函数
simplest() -> 'ok'.
在 Core Erlang 中,它将是
'simplest'/0 =
fun () ->
'ok'
从这个例子中,我们可以总结出以下原则
-
原子值总是被引用。
-
函数的命名已与函数的实现分离。
-
fun
的主体后面没有像 Erlang 中那样的end
。
稍微不那么简单 #
这是一个稍微复杂一些的函数
id(I) -> I.
在 Core Erlang 中
'id'/1 =
fun (_@c0) ->
_@c0
注意:所有示例均使用 OTP 20 编译。在即将到来的 OTP 21 中,生成的变量名称将有所不同。
本质上,变量的命名方式与 Erlang 中相同。在转换为 Core Erlang 时,编译器会为函数头中的参数生成新的变量名称。以下代码也是有效的 Core Erlang 代码
'id'/1 =
fun (I) ->
I
多个子句 #
这是一个具有多个子句的函数
a(42) -> ok;
a(_) -> error.
在 Core Erlang 中
'a'/1 =
fun (_@c0) ->
case _@c0 of
<42> when 'true' ->
'ok'
<_@c2> when 'true' ->
'error'
end
-
一个
fun
只能有一个子句。 -
模式匹配必须在
case
中完成,而不是在fun
头中。 -
case
中的每个子句都必须有 guard。 -
_
在 Core Erlang 中不是有效的变量名。不感兴趣的值必须绑定到新变量。 -
模式周围的
<
和>
将很快解释。
在 Erlang 中,也可以使用 case
来编写多个函数子句,如下所示
b(N) ->
case N of
42 -> ok;
_ -> error
end.
Core Erlang 代码与 a/1
的 Core Erlang 代码基本相同
'b'/1 =
fun (_@c0) ->
case _@c0 of
<42> when 'true' ->
'ok'
<_@c3> when 'true' ->
'error'
end
两个子句,三个参数 #
让我们尝试多个参数
c(inc, Base, N) ->
Base+N;
c(_, Base, _) ->
Base.
在 Core Erlang 中
'c'/3 =
fun (_@c2,_@c1,_@c0) ->
case <_@c2,_@c1,_@c0> of
<'inc',Base,N> when 'true' ->
call 'erlang':'+'(Base, N)
<_@c6,Base,_@c7> when 'true' ->
Base
end
-
<
和>
表示一个值列表。case
中每个子句的模式始终是值列表的一部分。case
表达式是一个值列表,除非只有一个表达式。 -
诸如
+
之类的运算符不是 Core Erlang 语言的一部分,因此编译器已将+
的使用转换为对 BIFerlang:'+'/2
的调用。
If #
让我们看看 if
是如何实现的
d(A, B) ->
if
A > B ->
greater;
true ->
not_greater
end.
在 Core Erlang 中
'd'/2 =
fun (_@c1,_@c0) ->
case <> of
<> when call 'erlang':'>'(_@c1, _@c0) ->
'greater'
<> when 'true' ->
'not_greater'
end
case
表达式和模式都是具有零个元素的值列表。所有的操作都在 guard 中。
重复变量 #
在 Erlang 中,一个变量可以在一个子句或一个模式中重复出现,以表明这些值必须相同
cmp(Same, Same) -> same;
cmp(_, _) -> different.
Core Erlang 不允许重复变量
'cmp'/2 =
fun (_@c1,_@c0) ->
case <_@c1,_@c0> of
<Same,_@c4> when call 'erlang':'=:='(_@c4, Same) ->
'same'
<_@c5,_@c6> when 'true' ->
'different'
end
- 在这里,变量
Same
的第二次出现已重命名为名为_@c4
的新变量,并添加了一个 guard 来比较Same
和_@c4
。
异常 #
如果使用除 42
之外的任何其他值调用此函数,则该函数将失败并抛出 function_clause
异常
e(42) -> ok.
在 Core Erlang 中
'e'/1 =
fun (_@c0) ->
case _@c0 of
<42> when 'true' ->
'ok'
<_@c1> when 'true' ->
primop 'match_fail'({'function_clause',_@c1})
end
-
Core Erlang 中的
case
不能在末尾失败,也就是说,必须始终有一个子句可以匹配。 -
在此示例中,最后一个带有变量模式和
true
guard 的子句保证匹配。 -
最后一个子句的主体调用一个 primop 来生成函数子句异常。Primop 是 Erlang 实现提供的原始操作,但未在 Core Erlang 语言规范中指定。
这是一个类似的函数,只不过它使用了 case
,因此,如果使用除 42
之外的任何其他参数调用它,它将生成 case_clause
异常
f(N) ->
case N of
42 -> ok
end.
Core Erlang 代码与 e/1
的代码类似
'f'/1 =
fun (_@c0) ->
case _@c0 of
<42> when 'true' ->
'ok'
<_@c1> when 'true' ->
primop 'match_fail'({'case_clause',_@c1})
end
- 唯一的区别是
match_fail
primop 的参数。
让我们再次重写此函数
g(N) ->
42 = N,
ok.
在 Core Erlang 中
'g'/1 =
fun (_@c0) ->
case _@c0 of
<42> when 'true' ->
'ok'
<_@c1> when 'true' ->
primop 'match_fail'({'badmatch',_@c1})
end
- 同样,唯一的区别是
match_fail
primop 的参数。
使用 ‘let’ 绑定变量 #
这是一个绑定变量 I
的函数
h(A) ->
I = id(A),
I + A.
在 Core Erlang 中
'h'/1 =
fun (_@c0) ->
let <I> =
apply 'id'/1(_@c0)
in
call 'erlang':'+'(I, _@c0)
-
apply
调用一个 fun 或本地函数。 -
apply
的返回值绑定到变量I
。 -
变量
I
只能在in
关键字后面的代码中使用。 -
变量名在值列表中。这是因为
let
可以一次绑定多个变量。
在 ‘let’ 中绑定多个变量 #
Erlang 本质上没有作用域。绑定变量后,该变量将保持绑定到函数末尾。例如,在 case
中绑定的变量可以在 case
之后使用
i(E) ->
case E of
a ->
X = 1,
Y = 10;
b ->
X = 23,
Y = 17
end,
{X,Y}.
在 Core Erlang 中
'i'/1 =
fun (_@c0) ->
let <_@c7,X,Y> =
case _@c0 of
<'a'> when 'true' ->
<10,1,10>
<'b'> when 'true' ->
<17,23,17>
<_@c5> when 'true' ->
primop 'match_fail'({'case_clause',_@c5})
end
in
{X,Y}
-
Core Erlang 中的
case
不导出任何变量。要在case
之后使用的所有变量都必须显式返回。 -
在此示例中,
case
的前两个子句返回一个包含三个值的值列表。第一个值是 case 的返回值,在这种情况下会被忽略。其他两个值分别是分配给X
和Y
变量的值。 -
从
case
返回的值在let
中绑定。忽略的返回值绑定到一个新变量(_@c7
),该变量永远不会被使用。导出的值绑定到X
和Y
变量。
未编辑的 Core Erlang 代码 #
到目前为止,所有 Core Erlang 示例都经过编辑,以使我试图表达的观点更加突出。让我们看一下先前示例的未编辑版本
'e'/1 =
%% Line 33
fun (_@c0) ->
case _@c0 of
<42> when 'true' ->
'ok'
( <_@c1> when 'true' ->
( primop 'match_fail'
({'function_clause',_@c1})
-| [{'function_name',{'e',1}}] )
-| ['compiler_generated'] )
end
-
-|
将注释与 Core Erlang 构造关联。注释的含义未在 Core Erlang 语言规范中指定。 -
与最后一个子句关联的
compiler_generated
注释是编译器添加的提示,如果发现该子句永远不匹配并被删除,则后续优化过程不应生成警告。 -
开头的注释“Line 33”实际上是一个注释,美化打印机已将其转换为注释,以避免呈现美化打印的代码不可读。
结论 #
Core Erlang 比 Erlang 更简单,因此比抽象格式更适合代码分析工具(例如 Dialyzer)和优化器。
要了解有关 Core Erlang 的更多信息 #
所有详细信息都可以在 Core Erlang 1.0.3 语言规范中找到。