Thursday, August 17, 2006

Smerl Demo: Easy Function-Based Record Access in Erlang

A few people have told me that they thought Smerl was neat, but they didn't really know what it could be used for. So, I thought I'd share a short demo app that goes a little further than the code snippets in my last few postings about Smerl.



I've heard some people complain about Erlang' record access syntax. I agree with them that the notation Var#RecType.fieldname is not very pleasant to look at sometimes. With Smerl, we can make things better.



Here's a short module I wrote, called func_recs, which generates getters and setters for all records in a given source file:




-module(func_recs).
-export([generate/1]).

generate(Filename) ->
{ok, Forms} = epp:parse_file(Filename, [], []),
lists:foreach(
fun({attribute, _Line, record, {RecName, RecFields}}) ->
case smerl:for_module(RecName) of
{ok, C1} ->
process_module(C1, RecFields);
_Err ->
process_module(smerl:new(RecName), RecFields)
end;
(_Other) -> undefined
end, Forms).

process_module(MetaCtx, RecFields) ->
{_, C2} = lists:foldl(
fun({record_field, _Line,
{atom, _Line2, FieldName}},
{Idx, MetaCtx1}) ->
{Idx+1, process_field(MetaCtx1,
FieldName, Idx)};
({record_field, _Line,
{atom, _Line2, FieldName}, _Def},
{Idx, MetaCtx1}) ->
{Idx+1, process_field(MetaCtx1,
FieldName, Idx)}
end, {2, MetaCtx}, RecFields),
smerl:compile(C2).

process_field(MetaCtx, FieldName, Idx) ->
L = 999,
Getter = {function,L,FieldName,1,
[{clause,L,
[{var,L,'Obj'}],
[],
[{call,L,
{atom,L,element},
[{integer,L,Idx},{var,L,'Obj'}]}]}]},

{ok, MetaCtx1} = smerl:add_func(MetaCtx, Getter),
Setter =
{function,L,
FieldName,
2,
[{clause,1,
[{var,L,'Obj'},{var,L,'Val'}],
[],
[{call,L,
{atom,L,setelement},
[{integer,L,Idx},{var,L,'Obj'},
{var,L,'Val'}]}]}]},
{ok, MetaCtx2} = smerl:add_func(MetaCtx1, Setter),
MetaCtx2.


If the code path contains a module that has the same name as a record name in the source file, Smerl adds a getter and setter to the module for each field in the record. Otherwise, Smerl creates a new module purely in runtime.



Using func_recs is easy. Let's say we have the following source file called person.erl:




-module(person).
-export([new/0]).

-record(person, {name, city}).

new() ->
#person{}.


We can now write the following code:




func_recs:generate("person.erl"),
P = person:new(),
P1 = person:name(P, "Yannick"),
P2 = person:city(P1, "Barcelona"),
io:format("~w is from ~w", [person:name(P2), person:city(P2)])


This example doesn't exactly show you how to build a Google Killer in Erlang :) -- but I hope it illustrates some of the fun things you can do with Erlang metaprogramming using Smerl.



New tip (8/18/06): Are you looking an easy way of knowing what the abstract form for a function is? Fire up the erlang shell and type




io:parse_erl_form(". ").


This will give you a one-line mini shell, in which you can type your function and get back its abstract form representation.

5 comments:

bengt said...

i think that person.erl should export new/0, not new_person/0.

Yariv said...

Thanks! fixed.

Anders said...

While You are at it could You also add something like
new/1, that takes a list of {FieldName,Value} and initializes
the record with those values?

Yariv said...

Yes, of course! It wouldn't be too hard, either. To make it extra special, your meta-constructor would also implement the default field values for Erlang records if those values aren't set by the user. Maybe you can write that tutorial :)

Yariv said...

Read my latest entry on Smerl. I don't really like appending code snippets and I think I found a good way around either forms or code snippets.