将添加一个 -discontiguous
指令,以便将指定的函数呈现为多个子句组,这些子句组可能被其他指令和函数子句组分隔开。
添加一个新的指令
-discontiguous( Name_And_Arity_List ).
。 此类列表中命名的每个函数在该模块中必须至少有一个子句组,并且可以有多个。 对于未在此类列表中命名的任何函数,具有多个子句组仍然是错误的。
在 -discontiguous
指令中命名的函数不必具有多个子句组。 如果它有多个子句组,则就好像这些子句组在不重新排序的情况下移动在一起,并且除了最后一组之外的每组的句点都更改为分号。编译器不应该对多个子句组的存在或它们融合为单个子句组发表任何评论。
解析器阶段将执行重新分组,并且不会在其输出中包含 -discontiguous
指令的任何表示,以便下游工具永远不会知道 -discontiguous
曾经存在过。
有一个单一的机制可以解决三个问题。
第一个问题是 Erlang 具有条件编译,但没有真正令人满意的方法来选择函数的某些子句,而不是全部。
-discontiguous
指令允许您编写
-discontiguous([f/3]).
f(a, X, Y) -> .... .
-if(Cond).
f(b, X, Y) -> .... .
-endif.
f(c, X, T) -> .... .
第二个可以称为“面向主题的编程”。它涉及围绕计算的数据值而不是计算代码的人类代码结构。我在处理虚拟机时发现了这一点:我希望将汇编指令的代码、窥孔优化指令的代码、将其编码到内存中的代码以及将其解释的代码放在一个位置(涉及不同的函数),而不是按函数组织,从而将相关信息分散到模块的长度和宽度上。
从一个例子开始可能最清楚。 erl_syntax.erl
中的代码读取
-type syntaxTree() :: #tree{} | #wrapper{} | tuple().
%% All `erl_parse' tree nodes are represented by tuples
%% whose second field is the position information (usually
%% an integer), *with the exceptions of*
%% `{error, ...}' (type `error_marker') and
%% `{warning, ...}' (type `warning_marker'),
%% which only contain the associated line number *of the
%% error descriptor*; this is all handled transparently
%% by `get_pos' and `set_pos'.
get_pos(#tree{attr = Attr}) ->
Attr#attr.pos;
get_pos(#wrapper{attr = Attr}) ->
Attr#attr.pos;
get_pos({error, {Pos, _, _}}) ->
Pos;
get_pos({warning, {Pos, _, _}}) ->
Pos;
get_pos(Node) ->
%% Here, we assume that we have an `erl_parse' node
%% with position information in element 2.
element(2, Node).
set_pos(Node, Pos) ->
case Node of
#tree{attr = Attr} ->
Node#tree{attr = Attr#attr{pos = Pos}};
#wrapper{attr = Attr} ->
Node#wrapper{attr = Attr#attr{pos = Pos}};
_ ->
%% We then assume we have an `erl_parse' node,
%% and create a wrapper around it to make
%% things more uniform.
set_pos(wrap(Node), Pos)
end.
这里的类型有点模糊。附加的元组似乎是 {error,{Pos,_,_}}, {warning,{Pos,_,_}}
和 erl_parse
返回的 {Tag,Pos...}
元组。 这里的关键是有五种不同的情况。 对于某些目的,最好写成
-discontiguous([get_pos/1,set_pos/2]).
get_pos(#tree{attr = Attr}) -> Attr#attr.pos.
set_pos(#tree{attr = Attr} = Node, Pos) ->
Node#tree{attr = Attr#attr{pos = Pos}}.
get_pos(#wrapper{attr = Attr}) -> Attr#attr.pos.
set_pos(#wrapper{attr = Attr} = Node, Pos) ->
Node#wrapper{attr = Attr#attr{pos = Pos}}.
get_pos({error, {Pos,_,_}}) -> Pos.
% What should set_pos/2 do in this case?
get_pos({warning, {Pos,_,_}}) -> Pos.
% What should set_pos/2 do in this case?
get_pos(Node) -> element(2, Node). % assume erl_parse node
set_pos(Node, Pos) -> % assume erl_parse node
set_pos(wrap(Node), Pos). % wrap it for uniformity
这比任何其他可能的布局更清楚地显示了两个函数之间的并行性以及并行性失败的方式。它促使您要么用明显的
set_pos({error, {_,X,Y}}, Pos) ->
{error, {Pos,X,Y}}.
和
set_pos({warning, {_,X,Y}), Pos) ->
{warning, {Pos,X,Y}}.
子句完成并行,要么至少将注释更改为
% set_pos/2 falls through to the last case.
注释。
我们在该文件的另外两个函数中具有相同的模式,而没有并行失败
get_com(#tree{attr = Attr}) -> Attr#attr.com;
get_com(#wrapper{attr = Attr}) -> Attr#attr.com;
get_com(_) -> none.
set_com(Node, Com) ->
case Node of
#tree{attr = Attr} ->
Node#tree{attr = Attr#attr{com = Com}};
#wrapper{attr = Attr} ->
Node#wrapper{attr = Attr#attr{com = Com}};
_ ->
set_com(wrap(Node), Com)
end.
它们可以是
-discontiguous([get_com/1,set_com/1]).
get_com(#tree{attr = Attr}) -> Attr#attr.com.
set_com(#tree{attr = Attr} = Node, Com) ->
Node#tree{attr = Attr#attr{com = Com}}.
get_com(#wrapper{attr = Attr}) -> Attr#attr.com.
set_com(#wrapper{attr = Attr} = Node, Com) ->
Node#wrapper{attr = Attr#attr{com = Com}}.
get_com(_) -> none. % error, warning, erl_parse.
set_com(Node, Com) ->
set_com(wrap(Node), Com).
好吧,再一次,并行性并不完全完美。 wrap/1
的文档说它假设其参数是一个类 erl_parse
元组,这意味着它看起来不应该是错误或警告。
这里的兴趣点是,仅仅查看现有的函数并没有引起任何警报;直到我说“这些似乎是关于相同数据结构的;我想知道交错是否可以使连接更清晰并更容易确保正确关联 getter 和 setter?”时,我的注意力才真正被吸引到这些差异。
特别有趣的是,我查看的第一个 Erlang/OTP 源文件就提供了示例。第三个类似于第二个,但与计算机编写的代码有关,而不是人类编写的代码。例如,如果生成某种状态机的功能表示,围绕状态组织输出可能很方便,但目前的方案要求它围绕处理状态的函数来组织。
Prolog 系统已经支持 :- discontiguous
声明 20 多年了。这种方法是一种行之有效的方法。它是语言的简单概括,可以对所有“下游”工具隐藏。只有那些试图在没有完全解析的情况下处理 Erlang 语法的工具才能注意到差异,它们应该在很大程度上忽略它。
所有现有的 Erlang 代码在语义不变的情况下仍然可以接受。如果现有语言处理工具依赖于 erl_parse
,则不会受到影响。
此草案中没有。
无。
本文档已置于公共领域。