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