[Erlang 0006] Erlang中的record与宏 中我们提到过Record是一个编译时的功能,在Erlang VM中并没有专门的数据类型.在线上解决问题有时候会遇到要在shell中使用record,那么就有两个选择:1.在shell中构造record定义,如果能构造record有了record的定义编写ets:match的匹配模式就方便多了; 2.直接使用record对应的tuple结构;

方法一 使用rd命令

Eshell V5.9 (abort with ^G)
1> rd(film ,{ director, actor, type, name,imdb}).
film
2> F =#film{}.
#film{director = undefined,actor = undefined,
type = undefined,name = undefined,imdb = undefined}
3> F#film.type.
undefined
4> F#film.type=23.
* 1: illegal pattern
5> F2 =F#film{type=23}.
#film{director = undefined,actor = undefined,type = 23,
name = undefined,imdb = undefined}



方法二使用rr命令
rr命令可以加载模块中的record定义,我们把record放在一个模块中:

-module(records).
-record(book,{name, id, item01, item02, item03, item04, item05, item06, item07, item08, item09, item10 } ).
-record(film ,{ director, actor, type, name,imdb}).

注意:rr方法是支持通配符的,比如rr("*")


我们在shell中编译并尝试加载其中的record:

Eshell V5.9 (abort with ^G)
1> c(records).
records.erl:2: Warning: record book is unused
records.erl:19: Warning: record film is unused
{ok,records}
2> rr(records).
[book,film]
3> F =#film{}.
#film{director = undefined,actor = undefined,
type = undefined,name = undefined,imdb = undefined}
4>

 

方法三 最方便的方案 user_default

上面两种方法一种是手工定义,一种是手工加载定义,有更理想的方案么?有!
仔细阅读一下Erlang shell的文档,我们可以看到下面这段: http://www.erlang.org/documentation/doc-5.4.12/lib/stdlib-1.13.11/doc/html/shell.html

If a command (local function call) is not recognized by the shell, an attempt is first made to find the function in the module user_default, where customized local commands can be placed. If found, then the function is evaluated. Otherwise, an attempt is made to evaluate the function in the module shell_default. The module user_default must be explicitly loaded.

There is some support for reading and printing records in the shell. During compilation record expressions are translated to tuple expressions. In runtime it is not known whether a tuple actually represents a record. Nor are the record definitions used by compiler available at runtime. So in order to read the record syntax and print tuples as records when possible, record definitions have to be maintained by the shell itself. The shell commands for reading, defining, forgetting, listing, and printing records are described below. Note that each job has its own set of record definitions. To facilitate matters record definitions in the modules shell_default and user_default (if loaded) are read each time a new job is started.

我们就使用user_default来解决这个问题!!!我们准备一下测试的文件:

%% File: records.hrl
-record(book,{ name, id, item01, item02, item03, item04, item05, item06, item07, item08, item09, item10 } ).
-record(film ,{ director, actor, type, name,imdb}).
-record(foo,{ id, name,bar}).

%% File: user_default.erl
-module(user_default).
-include("records.hrl").
-compile(export_all).

get_meta() ->
user_default:module_info().

get_timestamp() ->
{M, S, _} = erlang:now(),
M * 1000000 + S.

编译一下user_default.erl,编译之后我们启动Erlang Shell,可以看到不仅record已经加载了,而且user_default中定义的方法也可以直接访问.

Eshell V5.9 (abort with ^G)
1> rl(). %% 在shell中可以使用rl()查看已经定义的record.
-record(book,{name, id, item01, item02, item03, item04, item05, item06, item07, item08, item09, item10 } ).
-record(film,{director,actor,type,name,imdb}).
-record(foo,{id,name,bar}).
ok
2> get_timestamp().
1325308014
3> F=foo#{}.
* 1: syntax error before: '{'
3> F=#foo{}.
#foo{id = undefined,name = undefined,bar = undefined}
4>

 

补遗:

在centos环境,在user_default.beam所在的目录启动erl 使用rl()查看,record定义没有加载,只返回了ok

我怀疑是user_default的加载时机有问题,就修改了~/.erlang 文件,添加了加载逻辑 code:load_abs("/data/op_server/user_default").
这样修改之后,还是没有效果,然后我怀疑是不是~/.erlang文件没有执行;于是我故意把里面的代码改错,再启动erl报错,这样可以确认这个文件的内容确实执行了

立涛的回答:
code:load_abs("..../user_default").
这一行删除也没有关系。
只要HOME目录下有user_default.beam

 

 成立涛  19:53:36
编译的时候加上erlc +debug_info user_default.erl
然后把user_default.beam放到当前目录就ok了。
我看了下代码 :) 

 

 

方法四 不定义直接使用tuple
[Erlang 0006] Erlang中的record与宏 我们说过record在Erlang的内部实现是还是一个tuple.在shell中我们可以使用等效的tuple.
4> F2 =#film{director=2012}.
#film{director = 2012,actor = undefined,type = undefined,
name = undefined,imdb = undefined}
5> F2 == {film , 2012, undefined, undefined, undefined, undefined} .
true

方法五 Match Specifications And Records (Dynamically!)
这是trapexit上提供的一个解决方案,稍微绕一点,主要是{ok,Tree}=epp:parse_file("myheader.hrl",["./"],[])解析头文件进行预处理,然后生成reocrd的元数据module文件,再利用工具类来动态生成match specification;

预处理模块的代码
 1 %%%-------------------------------------------------------------------
2 %%% File : record_util.erl
3 %%% Author : Gordon Guthrie gordon@hypernumbers.com
4 %%% Description : utilities for manipulating records
5 %%%
6 %%% Created : 2 Sep 2008 by Gordon Guthrie
7 %%%-------------------------------------------------------------------
8 -module(make_ms_util).
9
10 -include("myheader.hrl").
11
12 -export([make/0]).
13
14 -define(MODULENAME,"ms_util2").
15
16 make() ->
17 {ok,Tree}=epp:parse_file("myheader.hrl",["./"],[]),
18 Src=make_src(Tree),
19 ok=file:write_file(?MODULENAME++".erl",list_to_binary(Src)).
20
21 make_src(Tree) -> make_src(Tree,[]).
22
23 make_src([],Acc) -> make_src2(Acc,[],[]);
24 make_src([{attribute,_,record,Record}|T],Acc) -> make_src(T,[Record|Acc]);
25 make_src([_H|T],Acc) -> make_src(T,Acc).
26
27 make_src2([],Acc1,Acc2) -> top_and_tail(Acc1,Acc2);
28 make_src2([H|T],Acc1,Acc2) -> {NewAcc1,NewAcc2}=expand_rec(H),
29 make_src2(T,[NewAcc1|Acc1],[NewAcc2|Acc2]).
30
31 expand_rec({Name,Def}) -> expand_fields(Name,Def,1,[]).
32
33 expand_fields(Name,[],N,Acc) -> {mk2(Name,N-1),lists:reverse([mk(Name)|Acc])};
34 expand_fields(Name,[{record_field,_,{atom,_,F},_}|T],N,Acc) ->
35 expand_fields(Name,T,N+1,[mk(Name,F,N)|Acc]);
36 expand_fields(Name,[{record_field,_,{atom,_,F}}|T],N,Acc) ->
37 expand_fields(Name,T,N+1,[mk(Name,F,N)|Acc]);
38 expand_fields(Name,[H|T],N,Acc) -> expand_fields(Name,T,N+1,Acc).
39
40 %% mk2/1 builds the no of fields fns
41 mk2(Name,N) -> "no_of_fields("++atom_to_list(Name)++") -> "++
42 integer_to_list(N)++";\n".
43
44 %% mk/1 builds an error line
45 mk(Name) -> "get_index("++atom_to_list(Name)++",F) -> "++
46 "exit({error,\"Record: "++atom_to_list(Name)++
47 " has no field called \"++atom_to_list(F)});\n".
48
49 mk(Name,Field,N) ->
50 "get_index("++atom_to_list(Name)++","++
51 atom_to_list(Field)++")-> "++integer_to_list(N)++";\n".
52
53 top_and_tail(Acc1,Acc2)->
54 Top="%% This module automatically generated - do not edit\n"++
55 "\n"++
56 "%%% This module provides utilities for use in building\n"++
57 "%%% match specifications from records\n"++
58 "\n"++
59 "-module("++?MODULENAME++").\n"++
60 "\n"++
61 "-export([get_index/2,no_of_fields/1]).\n"++
62 "\n",
63 Tail1="no_of_fields(Other) -> exit({error,\"Invalid Record Name: \""++
64 "++Other}).\n\n\n",
65 Tail2="get_index(Record,_Field) -> exit({error,\""++
66 "Invalid Record Name: \"++Record}).\n",
67 Top++lists:flatten(Acc1)++Tail1++lists:flatten(Acc2)++Tail2.

详情点击这里: http://www.trapexit.org/Match_Specifications_And_Records_%28Dynamically%21%29


一个简单的问题,只要愿意去想,答案不止一个;