在线文档教程
Erlang 20

ms_transform

ms_transform

模块

ms_transform

模块摘要

将有趣的语法转换为匹配规范的解析转换。

描述

此模块提供了对etsdbg:fun2ms/1翻译成文字匹配规范。当从Erlangshell调用时,它还为相同的函数提供后端。

从funs转换到匹配规范的转换通过两个“伪函数”进行访问。ets:fun2ms/1dbg:fun2ms/1...

由于每个人都试图使用ets:select/2dbg似乎最终阅读本手册页,此说明是对匹配规范概念的介绍。

如果是第一次使用转换,请阅读整个手册页。

匹配规格或多或少地用作过滤器。它们类似于在列表理解或与之一起使用的乐趣中常用的Erlang匹配lists:foldl/3,等等。然而,纯匹配规范的语法很尴尬,因为它们完全由Erlang术语组成,并且该语言没有语法来使匹配规范更具可读性。

由于匹配规范的执行和结构就像一个有趣的事情,所以使用熟悉的函数语法编写它会更直接,并且会自动将其转换为匹配规范。真正的函数显然比比赛规格允许的更强大,但考虑到比赛规格,以及他们可以做什么,将它作为一种函数写出来更加方便。该模块包含将函数语法转换为匹配规范术语的代码。

例1

使用ets:select/2和匹配规范,可以过滤出表的行,并构建包含这些行中数据相关部分的元组列表。我们可以用它ets:foldl/3来代替,但是这个ets:select/2调用要高效得多。没有提供的翻译ms_transform,人们就必须努力编写匹配规范来适应这一点。

考虑一个简单的员工表:

-record(emp, {empno, %Employee number as a string, the key surname, %Surname of the employee givenname, %Given name of employee dept, %Department, one of {dev,sales,prod,adm} empyear}). %Year the employee was employed

我们使用以下方法创建表:

ets:new(emp_tab, [{keypos,#emp.empno},named_table,ordered_set]).

我们用随机选择的数据填充表格:

[{emp,"011103","Black","Alfred",sales,2000}, {emp,"041231","Doe","John",prod,2001}, {emp,"052341","Smith","John",dev,1997}, {emp,"076324","Smith","Ella",sales,1995}, {emp,"122334","Weston","Anna",prod,2002}, {emp,"535216","Chalker","Samuel",adm,1998}, {emp,"789789","Harrysson","Joe",adm,1996}, {emp,"963721","Scott","Juliana",dev,2003}, {emp,"989891","Brown","Gabriel",prod,1999}]

假设我们想要销售部门每个人的员工人数,有几种方法。

ets:match/2可用于:

1> ets:match(emp_tab, {'_', '$1', '_', '_', sales, '_'}). [["011103"],["076324"]]

ets:match/2使用更简单类型的匹配规范,但它仍然不可读,而且对返回的结果几乎没有控制。它总是一个列表。

ets:foldl/3ets:foldr/3可用于避免嵌套列表:

ets:foldr(fun(#emp{empno = E, dept = sales},Acc) -> [E | Acc]; (_,Acc) -> Acc end, [], emp_tab).

结果是["011103","076324"]。函数很简单,所以唯一的问题是表中的所有数据都必须从表格传输到调用过程进行过滤。与ets:match/2过滤可以在模拟器“内部”完成并且只有结果被转移到过程的调用相比,这是低效的。

考虑一个“纯粹的”ets:select / 2调用,它执行ets:foldr的操作:

ets:select(emp_tab, [{#emp{empno = '$1', dept = sales, _='_'},[],['$1']}]).

尽管使用了记录语法,但它仍然很难阅读,甚至更难写。 元组的第一个元素#emp {empno ='$ 1',dept = sales,_ ='_'}告诉匹配的内容。 不匹配的元素不会返回,如在ets:match / 2示例中。 第二个元素是空列表,是一个守护表达式列表,我们不需要。 第三个元素是构造返回值的表达式的列表(在ETS中,这几乎总是包含一个单一项的列表)。 在我们的例子中,'$ 1'绑定到头部的雇员编号(元组的第一个元素),因此返回雇员编号。 结果是[“011103”,“076324”],如在ets:foldr / 3示例中那样,但结果在执行速度和内存消耗方面检索的效率更高。

通过使用ets:fun2ms/1,我们可以将ets:foldr/3纯粹ets:select/2示例的易用性和效率结合起来:

-include_lib("stdlib/include/ms_transform.hrl"). ets:select(emp_tab, ets:fun2ms( fun(#emp{empno = E, dept = sales}) -> E end)).

这个例子不需要特别了解匹配规范。乐趣的头部匹配你想要过滤掉的东西,并且身体返回你想要返回的东西。只要乐趣可以保持在匹配规范的限制范围内,就不需要像在ets:foldr/3示例中那样将所有表格数据传输到筛选过程。读取比例更容易ets:foldr/3,因为select调用本身会丢弃任何不匹配的东西,而ets:foldr/3调用的乐趣需要处理匹配的元素和不匹配的元素。

在上面的ets:fun2ms / 1例子中,需要在源代码中包含ms_transform.hrl,因为这是触发ets:fun2ms / 1调用有效匹配规范的解析转换的原因。 这也意味着转换是在编译时完成的(除了从shell调用的时候),因此在运行时没有资源。 也就是说,尽管您使用更直观的有趣语法,但它在运行时与手写匹配规格一样高效。

例2

假设我们希望获得2000年以前雇用的所有员工数量。在ets:match/2,使用不是一种替代方案,因为关系运营商无法在那里表达。再一次,ets:foldr/3可以做到这一点(缓慢但正确):

ets:foldr(fun(#emp{empno = E, empyear = Y},Acc) when Y < 2000 -> [E | Acc]; (_,Acc) -> Acc end, [], emp_tab).

结果是["052341","076324","535216","789789","989891"],如预期。使用手写匹配规范的等效表达式如下所示:

ets:select(emp_tab, [{#emp{empno = '$1', empyear = '$2', _='_'}, [{'<', '$2', 2000}], ['$1']}]).

这给出了相同的结果。[{'<', '$2', 2000}]在守卫部分,因此丢弃任何没有empyear('$2'在头上)小于2000的东西,就像foldr/3例子中的守卫一样。

我们用ets:fun2ms/1*

-include_lib("stdlib/include/ms_transform.hrl"). ets:select(emp_tab, ets:fun2ms( fun(#emp{empno = E, empyear = Y}) when Y < 2000 -> E end)).

例3

假设我们希望整个对象匹配而不是只有一个元素。一种替代方法是为记录的每个部分分配一个变量,并在函数的主体中再次构建变量,但以下内容更加容易:

ets:select(emp_tab, ets:fun2ms( fun(Obj = #emp{empno = E, empyear = Y}) when Y < 2000 -> Obj end)).

和普通的Erlang匹配一样,你可以使用“匹配内匹配”来将变量绑定到整个匹配对象,即a =。不幸的是,在被翻译为匹配规范的文本中,只允许在“顶层”,即匹配到达的整个对象被匹配到一个单独的变量中。如果您习惯于手工编写匹配规格,我们提到变量A只是简单地翻译为'$ _'。或者,伪函数object/0也返回整个匹配的对象,请参见部分Warnings and Restrictions

例4

这个例子涉及到函数的主体。假设以zero(0)开头的所有员工编号必须改为以one(1)开始,而我们要创建列表[{<Old empno>,<New empno>}]:

ets:select(emp_tab, ets:fun2ms( fun(#emp{empno = [$0 | Rest] }) -> {[$0|Rest],[$1|Rest]} end)).

该查询符合表类型ordered_set中部分绑定键的功能,因此不需要搜索整个表,只会查看包含以0开头的键的部分。

例5

函数可以有很多条款。假设我们想做以下工作:

  • 如果员工在1997之前开始工作,请返回元组。{inventory, <employee number>}...

  • 如果雇员在1997年或以后,但在2001之前,返回{rookie, <employee number>}...

  • 对于所有其他员工,{newbie, <employee number>}除了Smith标签以外的所有其他员工,除了标签以外的任何其他员工都会受到回报,并且这些标签guru也是针对其数字返回的内容:{guru, <employee number>}。这样做如下:ets:select(emp_tab,ets:fun2ms(fun(#emp {empno = E,surname =“Smith”}) - > {guru,E};(#emp {empno = E,empyear当Y <2001 - > {newbie,E};(#emp {empno = E,empyear);当Y <1997 - > {inventory,E};(#emp {empno = E,empyear = Y} = Y}) - >%1997 - 2001 {rookie,E} end))。结果如下:[{rookie,“011103”},{rookie,“041231”},{guru,“052341”},{guru,“076324”},{newbie,“122334”},{rookie, “535216”},{inventory,“789789”},{newbie,“963721”},{rookie,“989891”}]有用的BIFs你还能做什么?一个简单的答案是:请参阅文档match specifications在ERTS用户指南。但是,下面简要概述了当乐趣被翻译为匹配规范时可以使用的最有用的“内置函数” ets:fun2ms/1。调用比匹配规范允许的功能是不可能的。没有“通常的”Erlang代码可以通过翻译的乐趣来执行ets:fun2ms/1。乐趣仅限于匹配规格的力量,这是不幸的,但与执行速度ets:select/2相比,必须支付的价格ets:foldl/foldr。乐趣的头是头匹配(或不匹配)一个参数,一个对象我们从中选择的表格。该对象总是一个变量(可以_)或一个元组,因为ETS,Dets和Mnesia表都包括这个。返回的匹配规格ets:fun2ms/1可以与dets:select/2和 mnesia:select/2和with一起使用ets:select/2。=头部的使用允许(并鼓励)在顶层。警卫部分可以包含Erlang的任何警戒表达。以下是BIF和表达式的列表:

  • 型式检验:is_atomis_floatis_integeris_listis_numberis_pidis_portis_referenceis_tupleis_binaryis_functionis_record

  • 布尔运算符:notandorandalsoorelse

  • 关系运算符:>,> =,<,= <,= = =,==,= / =,/ =

  • 算术:+-*divrem

  • 位运算符:bandborbxorbnotbslbsr

  • 警卫BIF:abs,,,element,,,hd,,,length,,,node,,,round,,,size,,,tl,,,trunc,,,self与“手写”匹配规范的事实相反,is_record警卫的工作与普通的Erlang代码一样。分号%28;%29在警卫中是允许的,结果是%28,正如预期的%29一个“匹配规格子句”,每个分号分隔的部分警卫。语义与Erlang语义相同。乐趣的主体被用来构造结果值。在从表中选择时,通常在这里构造一个合适的项,使用普通的Erlang项构造,比如元组括号、列表括号和头中匹配的变量,可能偶尔会有常量。这里也允许在警卫中使用任何表达式,但是除了object和bindings%28进一步下降%29,它分别返回整个匹配的对象和所有已知的变量绑定。大dbg匹配规范的变体有一个命令式的方法来匹配规范体,ETS方言却没有。有趣的身体ets:fun2ms/1返回没有副作用的结果。作为匹配%28=%29在匹配规范的正文中是不允许%28的,因为性能原因%29唯一剩下的东西,或多或少,是术语结构。DBG示例本节描述了由dbg:fun2ms/1...使用解析转换的相同原因适用于dbg甚至可能更多,因为在跟踪%28时使用Erlang代码进行过滤并不是一个好主意,除非跟踪到文件%29。这个概念类似于ets:fun2ms/1但是,您通常直接从shell%28使用它,这也可以通过ets:fun2ms/129%。下面是要跟踪的示例模块:-module(toy). -export([start/1, store/2, retrieve/1]). start(Args) -> toy_table = ets:new(toy_table, Args). store(Key, Value) -> ets:insert(toy_table, {Key,Value}). retrieve(Key) -> [{Key, Value}] = ets:lookup(toy_table, Key), Value.在模型测试期间,第一个测试结果是{badmatch,16}在{toy,start,1}为什么?我们怀疑ets:new/2调用,因为我们在返回值上很难匹配,但只需要特定的new/2打电话给toy_table作为第一个参数。因此,我们在节点上启动一个默认的跟踪器:1> dbg:tracer(). {ok,<0.88.0>}我们打开所有进程的调用跟踪,我们希望创建一个非常严格的跟踪模式,因此不需要只调用几个进程%28--通常不是%29:2> dbg:p(all,call). {ok,[{matched,nonode@nohost,25}]}我们指定过滤器,我们想查看类似的调用ets:new(toy_table, <something>)*3> dbg:tp(ets,new,dbg:fun2ms(fun([toy_table,_]) -> true end)). {ok,[{matched,nonode@nohost,1},{saved,1}]}正如我们所看到的,dbg:fun2ms/1将单个列表作为参数,而不是单个元组。该列表将参数列表与跟踪函数匹配。也可以使用单个变量。乐趣的身体以一种更迫切的方式来表达,如果有趣的头部为28,守卫为29%,则需要采取的行动。true在这里返回,只是因为一个有趣的身体不能是空的。返回值被丢弃。在测试期间收到以下跟踪输出:(<0.86.0>) call ets:new(toy_table, [ordered_set]) 假设我们还没有发现问题,想看看ets:new/2退货。我们使用的跟踪模式略有不同:4> dbg:tp(ets,new,dbg:fun2ms(fun([toy_table,_]) -> return_trace() end)).在测试期间收到以下跟踪输出:(<0.86.0>) call ets:new(toy_table,[ordered_set]) (<0.86.0>) returned from ets:new/2 -> 24 打电话给return_trace在函数返回时生成跟踪消息。它只适用于特定的函数调用,触发匹配规范%28并匹配匹配规范%29的头/护卫。到目前为止,这是dbg匹配规格。现在测试失败了{badmatch,24}因为原子toy_table与未命名表返回的数字不匹配。因此,发现了问题,表将被命名,测试程序提供的参数不包括named_table.我们重写开始函数:start(Args) -> toy_table = ets:new(toy_table, [named_table|Args]).在打开相同的跟踪后,将收到以下跟踪输出:(<0.86.0>) call ets:new(toy_table,[named_table,ordered_set]) (<0.86.0>) returned from ets:new/2 -> toy_table 假设模块现在通过了所有测试并进入系统。过了一会儿,我们发现了那张桌子toy_table在系统运行的时候增长,并且有许多元素以原子为键。我们只期望整数键,系统的其他部分也是如此,但显然不是整个系统。我们打开调用跟踪,并尝试查看以原子为关键的对模块的调用:1> dbg:tracer(). {ok,<0.88.0>} 2> dbg:p(all,call). {ok,[{matched,nonode@nohost,25}]} 3> dbg:tpl(toy,store,dbg:fun2ms(fun([A,_]) when is_atom(A) -> true end)). {ok,[{matched,nonode@nohost,1},{saved,1}]}我们用dbg:tpl/3为了确保捕捉本地调用%28,假设模块从较小的版本开始增长,并且我们不确定这个原子插入是否在本地完成%29。如果有疑问,请始终使用本地呼叫跟踪。假设在以这种方式进行跟踪时不会发生任何事情。函数从未用这些参数调用。我们得出的结论是,其他人(%28)正在执行其他模块%29的操作,并意识到我们必须跟踪ets:insert/2并希望看到调用函数。调用函数可以使用匹配规范函数检索。caller.要将其输入跟踪消息,则需要使用Match规范函数。message一定要用。过滤器调用看起来像%28,查找ets:insert/229%:4> dbg:tpl(ets,insert,dbg:fun2ms(fun([toy_table,{A,_}]) when is_atom(A) -> message(caller()) end)). {ok,[{matched,nonode@nohost,1},{saved,2}]}调用者现在显示在跟踪输出的“附加消息”部分中,稍后显示如下内容:(<0.86.0>) call ets:insert(toy_table,{garbage,can}) {evil_mod,evil_fun,2}) 你已经意识到这个功能evil_fun在...evil_mod模块,具有独立性2引起了所有这些麻烦。此示例说明了匹配规范中最常用的调用。dbg.另一个更深奥的电话被列出并解释在Match specifications in Erlang在ERTS用户%27s指南中,因为它们超出了本说明的范围。警告和限制以下警告和限制适用于ets:fun2ms/1和dbg:fun2ms/1...警告若要使用触发转换的伪函数,请确保包含头文件。ms_transform.hrl在源代码中。如果不这样做,可能会导致运行时错误,而不是编译时间,因为表达式可以作为一个普通的Erlang程序有效,无需翻译。警告乐趣必须从字面上说是在参数列表中构造到伪函数中。不能先将乐趣绑定到变量,然后再传递给ets:fun2ms/1或dbg:fun2ms/1例如,ets:fun2ms(fun(A) -> A end)起作用,但不起作用F = fun(A) -> A end, ets:fun2ms(F)如果包含头,则后者将导致编译时错误,否则将导致运行时错误。许多限制适用于转换为匹配规范的乐趣。简单地说:你不能在乐趣中使用任何你不能在匹配规范中使用的东西。这意味着,除其他外,以下限制适用于娱乐本身:

  • 不能调用用Erlang编写的函数,也不能调用本地函数、全局函数或真正的Funs。

  • 作为函数调用编写的所有内容都被转换为对内置函数的匹配规范调用,因此调用is_list(X)翻译成{'is_list', '$1'}%28'$1'只是一个例子,编号可以更改%29。如果尝试调用一个不匹配的内置规范函数,则会导致错误。

  • 出现在乐趣头上的变量按出现的顺序被匹配规范变量替换,因此片段fun{A,B,C})被替换为{'$1', '$2', '$3'}等等。这样的变量在匹配规范中的每次出现都会以相同的方式被匹配规范变量替换,这样就可以让乐趣fun{A,B}) when is_atom(A) -> B end翻译成[{{'$1','$2'},[{is_atom,'$1'}],['$2']}]...

  • 不包含在头中的变量从环境中导入并生成匹配规范。const表情。shell中的示例: 1>X=25.25 2>ETS:当A>X->B结束%29时,fun2ms%28乐趣%28{A,B}%29。 [{{%27$1%27,%27$2%27},{%27>%27,%27$1%27,{const,25},,,%27$2%27}]

  • 与...匹配=不能在身体中使用。它只能用在高层的头上的乐趣。再次从shell中获取示例:

1>ETS:fun2ms%28娱乐%28{A,B-C}=D%29当A>B->D结束%29时。[{%27$1%27,%27$2%27%27$3%27}{%27>%27,%27$1%27,%27$2%27},,,%27元[医]%27}[2]>ETS:fun2ms%28乐趣%28{A,B-C当A>B->D结束%29时=D}%29。错误:头部匹配的乐趣%28%27=头%29中的%27不能转换为匹配[医]规范{错误,转换[医]错误}3>ETS:fun2ms%28FY%28{A,B-C}%29当A>B->D=B-C,D端%29。错误:与身体匹配的乐趣%28%27=%27在身体%29是非法的匹配[医]规范{错误,转换[医]错误}

所有变量都绑定在匹配规范的头上,因此转换器不能允许多个绑定。在顶层执行匹配时的特殊情况使变量绑定到'$_'在结果匹配规范中。它允许更自然地访问整个匹配的对象。伪函数object()可以替代使用,请参阅下面。

下列短语的翻译是平等的:

ETS:Fun2ms%28Fan%28{a,[医]}=A%29->A结束%29。ETS:Fun2ms%28Fan%28{a,[医]}%29->对象%28%29结束%29.

  • 特殊匹配规格变量'$_'和'$*'可以通过伪函数访问object()28%'$_'29%和bindings()28%'$*'29%。例如,您可以翻译以下内容ets:match_object/2打电话给ets:select/2呼叫: ETS:匹配[医]对象%28 Table,{%27$1%27,test,%27 2%27}%29。 这与: ETS:选择%28Table,ETS:fun2ms%28fun%28{A,test,B}%29->Object%28%29结束%29%29。 在这种简单的情况下,前者的表达式在可读性方面可能更好。 大ets:select/2在生成的代码中,Call的概念如下所示: ETS:选择%28表, [{{%27$1%27,test,%27$2%27},[],%27元[医]%27})%29。 在有趣的头部的顶层匹配可以是一种更自然的访问方式。'$_',见上文。

  • 术语结构/文字被翻译成有效的匹配规范所需的内容。通过这种方式,元组被制成匹配规范元组结构%28a,包含元组%29的单元素元组,并且在从环境导入变量时使用常量表达式。记录也被转换成简单的元组构造、元素调用等等。防护试验is_record/2使用内置到匹配规范中的三个参数版本转换为匹配规范代码,因此is_record(A,t)翻译成{is_record,'$1',t,5}如果记录类型的记录大小t是5。

  • 语言结构,如case,,,if,和catch不允许出现在匹配规范中的。

  • IF头文件ms_transform.hrl不包括在内,也不翻译乐趣,这可能导致运行时错误%28取决于在纯Erlang上下文中乐趣是否有效%29。

在使用etsdbg:fun2ms/1在编译的代码中。

  • 如果触发转换的伪函数是ets:fun2ms/1,乐趣的头必须包含一个变量或一个元组。如果伪函数是dbg:fun2ms/1,乐趣之首必须包含一个变量或一个列表。

函数到匹配规范的转换是在编译时完成的,因此使用这些伪函数不会影响运行时性能。

有关匹配规范的详细信息,请参阅Match specifications in Erlang在ERTS用户%27s指南中。

出口

格式[医]错误%28错误%29->字符

类型

获取模块中其他函数返回的错误代码,并创建错误的文本描述。

解析[医]转换%28格式,选项%29->Forms 2

类型

选项列表,必需但不使用。

在编译时实现转换。此函数由编译器调用,以便在头文件时执行源代码转换。ms_transform.hrl包含在源代码中。

有关如何使用此解析转换的信息,请参阅etsdbg:fun2ms/1...

有关匹配规范的说明,请参见Match Specification in Erlang在ERTS用户%27s指南中。

变换[医]从[医]shell%28 Dialect,子句,边界环境%29->术语%28%29

类型

shell环境中的变量绑定列表。

fun2ms/1函数从shell中调用。在本例中,抽象形式用于Erlangshell%29解析的单个乐趣%28。所有导入的变量都将在作为BoundEnvironment结果是一个术语,规范化的,也就是说,不是抽象的格式。

© 2010–2017 Ericsson AB

根据ApacheLicense,版本2.0获得许可。