[erlang-questions] Finding inconsistencies in record definitions and pretty printing records
Joe Armstrong
erlang@REDACTED
Tue Oct 14 17:31:02 CEST 2008
Hi,
This is a method for ensuring the consistency of records and for
providing record field information at run-time.
Long-time readers of this list will recognize this as a long-standing
problem.
(what's the problem? if you change a record definition and
forget to recompile everything you will get fried - and
record definitions get forgotten at run time)
(Short summary of how it works)
- compile record definitions into the modules
(uses a simple parse transform)
- hack the error_handler so that every time a new module
is loaded the record definitions in the module are sent
to a global server that remembers all record definitions
- make a global sever to check the consistency of the record
definitions
- start things off the right way ...
(The long version)
Here's what I do:
1) make a parse transform that compiles all local and included
record definitions into a file.
If you include the line:
-compile({parse_transform, jalib_records}).
In your code then an extra function called
module_records() -> ...
will be included into the compiled code for your module.
2) Change error_handler.erl
The old code:
ensure_loaded(Module) ->
Self = self(),
case whereis(code_server) of
%% Perhaps double fault should be detected in code:ensure_loaded/1
%% instead, since this error handler cannot know whether the
%% code server can resolve the problem or not.
%% An {error, Reason} return from there would crash the code server and
%% bring down the node.
Self ->
Error = "The code server called the unloaded module `" ++
atom_to_list(Module) ++ "'",
halt(Error);
Pid when is_pid(Pid) ->
code:ensure_loaded(Module);
_ ->
init:ensure_loaded(Module)
end.
Is changed to:
ensure_loaded(Module) ->
Self = self(),
case whereis(code_server) of
%% Perhaps double fault should be detected in code:ensure_loaded/1
%% instead, since this error handler cannot know whether the
%% code server can resolve the problem or not.
%% An {error, Reason} return from there would crash the code server and
%% bring down the node.
Self ->
Error = "The code server called the unloaded module `" ++
atom_to_list(Module) ++ "'",
halt(Error);
Pid when is_pid(Pid) ->
Val = code:ensure_loaded(Module),
run_on_load(Module), %% **** NEW STUFF
Val;
_ ->
init:ensure_loaded(Module)
end.
Where
run_on_load(Mod) ->
case erlang:function_exported(Mod, module_records, 0) of
true ->
Records = Mod:module_records(),
%% assume a registered process
%% jalib_records that keeps track of all records
(catch jalib_records ! {loaded, Mod, Records});
false ->
void
end.
3) jalib_records.erl also has a server that keeps track of
the records. So I have to start this somehow.
I added these lines to my ${HOME}/.erlang
(catch jalib_records:start()).
code:unstick_mod(error_handler).
code:load_abs("/home/joe/code/jalib/error_handler").
io:format("new error handler loaded~n").
Now we're ready to test
0) compile error_handler.erl and jalib_records.erl
and put in your path.
a) Here's a module with some records
-module(rec_test1).
-compile(export_all).
-compile({parse_transform, jalib_records}).
-record(foo, {a=1,b=2,c=[]}).
-record(bar, {x=2,y=23}).
foo() ->
{#foo{}, #bar{}}.
test() ->
jalib_records:print_record(#foo{a=hello}).
b) and rec_test2.erl
-module(rec_test2).
-compile(export_all).
-compile({parse_transform, jalib_records}).
-record(foo, {a=1,b=2,c=[]}).
-record(bar, {x=2,y=23,z=6}).
foo() ->
{#foo{}, #bar{}}.
test() ->
jalib_records:print_record(#foo{a=hello}).
c) Compile both:
erlc rec_test1.erl rec_test2.erl
d) run in the shell
1> rec_test1:test().
"#foo{a = hello,b = 2,c = []}"
It works
2> rec_test2:test().
*** Record definition error:
Record:bar
Old Mod:rec_test1 definition:[{x,2},{y,23}]
New Mod:rec_test2 definition:[{x,2},{y,23},{z,6}]
"#foo{a = hello,b = 2,c = []}"
Yes - we detect that the module definition of bar is inconsistent.
This is a quick and dirty prototype - which shows that the method works.
For a production version some improvements are necessary:
- make it work for incremental code loading
(it only works the first time you load a module)
- improve error messages (ie make uniform with OTP standards)
- improve pretty printer
Have fun
/Joe Armstrong
-------------- next part --------------
A non-text attachment was scrubbed...
Name: jalib_records.erl
Type: application/octet-stream
Size: 3613 bytes
Desc: not available
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20081014/02a6b61d/attachment.obj>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: error_handler.erl
Type: application/octet-stream
Size: 4462 bytes
Desc: not available
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20081014/02a6b61d/attachment-0001.obj>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: rec_test1.erl
Type: application/octet-stream
Size: 235 bytes
Desc: not available
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20081014/02a6b61d/attachment-0002.obj>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: rec_test2.erl
Type: application/octet-stream
Size: 239 bytes
Desc: not available
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20081014/02a6b61d/attachment-0003.obj>
More information about the erlang-questions
mailing list