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:
i think that person.erl should export new/0, not new_person/0.
Thanks! fixed.
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?
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 :)
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.
Post a Comment