[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