<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-1893218858608219953</id><updated>2011-10-08T23:43:49.503+10:00</updated><category term='slope_one'/><category term='pare'/><category term='emp1'/><category term='erlang'/><category term='gotcha'/><category term='collaborative_filtering'/><category term='emp2'/><category term='atomiser'/><title type='text'>This is me</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://chlorophil.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://chlorophil.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Philip Robinson</name><uri>http://www.blogger.com/profile/17605642659657207381</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>25</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-1893218858608219953.post-2042706619324991488</id><published>2008-06-10T21:37:00.006+10:00</published><updated>2008-06-10T23:26:06.062+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='emp2'/><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><title type='text'>"Dynamic" module generation with compile-time macros</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_6zQp6LtHMTo/SE6ApnDOyKI/AAAAAAAAAD8/YyLZ_gO9N3k/s1600-h/kelphorse.PNG"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;" src="http://bp0.blogger.com/_6zQp6LtHMTo/SE6ApnDOyKI/AAAAAAAAAD8/YyLZ_gO9N3k/s320/kelphorse.PNG" border="0" alt=""id="BLOGGER_PHOTO_ID_5210243271259768994" /&gt;&lt;/a&gt;&lt;br /&gt;Over on the Erlang Questions mailing list, Jacob Perkins asked:&lt;br /&gt;&lt;br /&gt;"I have a few files that are basically lists of words. What I want to do is generate a module whose functions will return each list of words. So if I have two files named "adjectives" and "nouns", then the generated module (let's call it 'grammar') should have two functions, adjectives() and nouns(), that return their respective list of words.&lt;br /&gt;&lt;br /&gt;How can I do this?"&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;If I had some method of running Erlang code at compile-time - and I do - then I could write a program to slurp in those adjective and noun files and generate the appropriate functions.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;adjectives.txt:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;this&lt;br /&gt;that&lt;br /&gt;these&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;nouns.txt:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;apple&lt;br /&gt;banana&lt;br /&gt;carrot&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;grammar.erl:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-module(grammar).&lt;br /&gt;-export([nouns/0, adjectives/0]).&lt;br /&gt;-compile({parse_transform, emp2}).&lt;br /&gt;&lt;br /&gt;-macro({grammar_macro, generate, [nouns]}).&lt;br /&gt;-macro({grammar_macro, generate, [adjectives]}).&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;grammar_macro.erl:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-module(grammar_macro).&lt;br /&gt;-export([generate/1]).&lt;br /&gt;&lt;br /&gt;generate(NameFun) -&gt;&lt;br /&gt;    {ok,Fd} = file:open(atom_to_list(NameFun) ++ ".txt", [read]),&lt;br /&gt;    generate(Fd, NameFun, []).&lt;br /&gt;&lt;br /&gt;generate(Fd, NameFun, Words) -&gt;&lt;br /&gt;    case io:get_line(Fd, "") of&lt;br /&gt;        eof -&gt;&lt;br /&gt;            ok=file:close(Fd),&lt;br /&gt;            io_lib:format("~w() -&gt; ~w.~n", [NameFun, lists:reverse(Words)]);&lt;br /&gt;        Word -&gt; generate(Fd, NameFun, [Word | Words])&lt;br /&gt;        end.&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And in the Erlang shell:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;1&gt; c(grammar_macro), c(grammar).&lt;br /&gt;{ok,grammar}&lt;br /&gt;2&gt; grammar:nouns().&lt;br /&gt;["apple\n","banana\n","carrot\n"]&lt;br /&gt;3&gt; grammar:adjectives().&lt;br /&gt;["this\n","that\n","these\n"]&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1893218858608219953-2042706619324991488?l=chlorophil.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chlorophil.blogspot.com/feeds/2042706619324991488/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://chlorophil.blogspot.com/2008/06/dynamic-module-generation-with-compile.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/2042706619324991488'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/2042706619324991488'/><link rel='alternate' type='text/html' href='http://chlorophil.blogspot.com/2008/06/dynamic-module-generation-with-compile.html' title='&quot;Dynamic&quot; module generation with compile-time macros'/><author><name>Philip Robinson</name><uri>http://www.blogger.com/profile/17605642659657207381</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp0.blogger.com/_6zQp6LtHMTo/SE6ApnDOyKI/AAAAAAAAAD8/YyLZ_gO9N3k/s72-c/kelphorse.PNG' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1893218858608219953.post-4323450579543210024</id><published>2007-11-30T09:35:00.001+10:00</published><updated>2007-12-03T08:29:03.777+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pare'/><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><title type='text'>PARE - PARallel Execution in Erlang</title><content type='html'>&lt;a href="http://bp0.blogger.com/_6zQp6LtHMTo/R09NS6PAIJI/AAAAAAAAAD0/TyYMaEbbr4k/s1600-h/Teacup.png"&gt;&lt;img id="BLOGGER_PHOTO_ID_5138410687118188690" style="margin: 0px 0px 10px 10px; float: right;" alt="" src="http://bp0.blogger.com/_6zQp6LtHMTo/R09NS6PAIJI/AAAAAAAAAD0/TyYMaEbbr4k/s320/Teacup.png" border="0" /&gt;&lt;/a&gt;Update 3/12/2007: Attempt to restore all the missing vertical bars from the code...&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Don &lt;a href="http://cgi.cse.unsw.edu.au/%7Edons/blog/2007/11/29#smoking"&gt;echoed&lt;/a&gt; a recent sentiment of mine: "Importantly, and unlike say, Erlang or Concurrent Haskell, we don't have to do manual thread creation, synchronisation or communication -- the compiler does all that for us!"&lt;br /&gt;&lt;br /&gt;Consider this arbitrary (and rather pointless) piece of Erlang code:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;   A = a(),&lt;br /&gt;   b(),&lt;br /&gt;   c().&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;If I wanted to run the above expressions in parallel (and let us assume that in this case I knew what I was doing, however improbable that might seem), I would normally have to risk RSI with something like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;   PidThis = self(),&lt;br /&gt;   PidA = spawn_link(fun() -&gt; PidThis ! {self(), a()} end),&lt;br /&gt;   spawn_link(fun() -&gt; b() end),&lt;br /&gt;   PidC = spawn_link(fun() -&gt; PidThis ! {self(), c()} end),&lt;br /&gt;   A = receive {PidA, ResultA} -&gt; ResultA end,&lt;br /&gt;   receive {PidC, ResultC} -&gt; ResultC end.&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Ouch.&lt;br /&gt;&lt;br /&gt;It would be much nicer if we could just tell the compiler that we want the next 'N' expressions to be executed in parallel and have the compiler handle all the rest of the boilerplate. Like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;   parallel_execution_3,&lt;br /&gt;   A = a(),&lt;br /&gt;   b(),&lt;br /&gt;   c().&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Rather fortuitously for this blog entry, that is exactly what the PARE parse transformation module does:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;% PARE: PARallel Execution&lt;br /&gt;%&lt;br /&gt;% Caveats:&lt;br /&gt;% Atoms starting with 'parallel_execution_' are consumed by this parse_transform,&lt;br /&gt;% and variables starting with 'parallel_execution_' will be created.&lt;br /&gt;% A process dictionary counter (key: 'parallel_execution_var_id') will be used&lt;br /&gt;% while compiling.&lt;br /&gt;% Change the definition of 'PREFIX' and 'VAR_ID' if these are unsuitable for your&lt;br /&gt;% codebase.&lt;br /&gt;%&lt;br /&gt;% Use:&lt;br /&gt;% A sequence of expressions beginning with an atom of the form&lt;br /&gt;% 'parallel_execution_N' (where N is an integer) will be parallelised by this&lt;br /&gt;% parse transformation.  The next 'N' expressions (at the same level as the&lt;br /&gt;% triggering atom) will be converted into a series of spawn/receive expressions,&lt;br /&gt;% and the triggering atom will be removed from the code.&lt;br /&gt;%&lt;br /&gt;% Return-value order will be preserved: the last expression in a list of&lt;br /&gt;% expressions will always be the value returned by that sequence of expressions.&lt;br /&gt;%&lt;br /&gt;% Future:&lt;br /&gt;% Use different triggering atom prefixes for spawn vs spawn_link?&lt;br /&gt;&lt;br /&gt;-module(pare).&lt;br /&gt;-author("Philip Robinson").&lt;br /&gt;-vsn('1.0').&lt;br /&gt;-export([parse_transform/2]).&lt;br /&gt;&lt;br /&gt;-define(PREFIX, "parallel_execution_").&lt;br /&gt;-define(VAR_ID, parallel_execution_var_id).&lt;br /&gt;&lt;br /&gt;parse_transform(ASTIn, _Options) -&gt;&lt;br /&gt;   put(?VAR_ID, 0), ASTOut = ast(ASTIn, []), erase(?VAR_ID), ASTOut.&lt;br /&gt;&lt;br /&gt;% PARALLELISE_AST/2&lt;br /&gt;ast([{function,Line,NameFun,Arity,ClausesIn} | ASTIn], ASTOut) -&gt;&lt;br /&gt;   ast(ASTIn, [{function,Line,NameFun,Arity,clause(ClausesIn, [])} | ASTOut]);&lt;br /&gt;ast([Node | ASTIn], ASTOut) -&gt; ast(ASTIn, [Node | ASTOut]);&lt;br /&gt;ast([], ASTOut) -&gt; lists:reverse(ASTOut).&lt;br /&gt;&lt;br /&gt;% PARALLELISE_CLAUSES/2&lt;br /&gt;clause([{clause,Line,Pattern,Guards,ExprsIn} | ClausesIn], ClausesOut) -&gt;&lt;br /&gt;   clause(ClausesIn, [{clause,Line,Pattern,Guards,expr(ExprsIn, [])}&lt;/code&gt;&lt;code&gt; | &lt;/code&gt;&lt;code&gt;ClausesOut]);&lt;br /&gt;clause([], ClausesOut) -&gt; lists:reverse(ClausesOut).&lt;br /&gt;&lt;br /&gt;% PARALLELISE_EXPRS/2 - Searching for a trigger atom.&lt;br /&gt;expr([{atom,_,Atom}=Expr&lt;/code&gt;&lt;code&gt; | &lt;/code&gt;&lt;code&gt;ExprsIn], ExprsOut) -&gt;&lt;br /&gt;   AtomStr = atom_to_list(Atom),&lt;br /&gt;   case lists:prefix(?PREFIX, AtomStr) of&lt;br /&gt;       false -&gt; expr(ExprsIn, [Expr&lt;/code&gt;&lt;code&gt; | &lt;/code&gt;&lt;code&gt;ExprsOut]);&lt;br /&gt;       true -&gt;&lt;br /&gt;           N = list_to_integer(element(2, lists:split(length(?PREFIX), AtomStr))),&lt;br /&gt;           PidThis = new_var(),&lt;br /&gt;           Line = element(2, hd(ExprsIn)),&lt;br /&gt;           {RParallelExprs, Rest} = expr(PidThis, ExprsIn, N, [], []),&lt;br /&gt;           ExprPidThis = [{match,Line,{var,Line,PidThis},&lt;br /&gt;               {call,Line,{atom,Line,self},[]}}],&lt;br /&gt;           expr(Rest, RParallelExprs ++ ExprPidThis ++ ExprsOut)&lt;br /&gt;       end;&lt;br /&gt;expr([{block,Line,Body}&lt;/code&gt;&lt;code&gt; | &lt;/code&gt;&lt;code&gt;ExprsIn], ExprsOut) -&gt;&lt;br /&gt;   expr(ExprsIn,[{block,Line,expr(Body, [])}&lt;/code&gt;&lt;code&gt; | &lt;/code&gt;&lt;code&gt;ExprsOut]);&lt;br /&gt;expr([{'case',Line,Condition,Clauses}&lt;/code&gt;&lt;code&gt; | &lt;/code&gt;&lt;code&gt;ExprsIn], ExprsOut) -&gt;&lt;br /&gt;   expr(ExprsIn,[{'case',Line,Condition,clause(Clauses,[])}&lt;/code&gt;&lt;code&gt; | &lt;/code&gt;&lt;code&gt;ExprsOut]);&lt;br /&gt;expr([{'if',Line,Clauses}&lt;/code&gt;&lt;code&gt; | &lt;/code&gt;&lt;code&gt;ExprsIn], ExprsOut) -&gt;&lt;br /&gt;   expr(ExprsIn,[{'if',Line,clause(Clauses,[])}&lt;/code&gt;&lt;code&gt; | &lt;/code&gt;&lt;code&gt;ExprsOut]);&lt;br /&gt;expr([{'try',Line,Body,CaseClauses,CatchClauses,After}&lt;/code&gt;&lt;code&gt; | &lt;/code&gt;&lt;code&gt;ExprsIn], ExprsOut) -&gt;&lt;br /&gt;   expr(ExprsIn,[{'try',Line,expr(Body,[]), clause(CaseClauses,[]),&lt;br /&gt;       clause(CatchClauses,[]), expr(After,[])}&lt;/code&gt;&lt;code&gt; | &lt;/code&gt;&lt;code&gt;ExprsOut]);&lt;br /&gt;expr([Expr&lt;/code&gt;&lt;code&gt; | &lt;/code&gt;&lt;code&gt;ExprsIn], ExprsOut) -&gt; expr(ExprsIn, [Expr&lt;/code&gt;&lt;code&gt; | &lt;/code&gt;&lt;code&gt;ExprsOut]);&lt;br /&gt;expr([], ExprsOut) -&gt; lists:reverse(ExprsOut).&lt;br /&gt;&lt;br /&gt;% PARALLELISE_EXPRS/5 - Trigger atom has been found, parallelise the following 'N' expressions.&lt;br /&gt;% Build up a list of expressions to spawn and receive.&lt;br /&gt;expr(_PidThis, ExprsIn, 0, SpawnExprs, ReceiveExprs) -&gt;&lt;br /&gt;   {ReceiveExprs ++ SpawnExprs, ExprsIn};&lt;br /&gt;% Match expression:&lt;br /&gt;% Spawn RHS, match receive value to original LHS.&lt;br /&gt;expr(PidThis, [{match,Line,LHS,RHS}&lt;/code&gt;&lt;code&gt; | &lt;/code&gt;&lt;code&gt;ExprsIn], N, SpawnExprs, ReceiveExprs) -&gt;&lt;br /&gt;   VarPid = {var,Line,new_var()},&lt;br /&gt;   VarReceive = {var,Line,new_var()},&lt;br /&gt;   VarReason = {var,Line,new_var()},&lt;br /&gt;   Spawn = {match,Line,VarPid,{call,Line,{atom,Line,spawn_link},&lt;br /&gt;       [{'fun',Line,{clauses,[{clause,Line,[],[],[{op,Line,'!',{var,Line,PidThis},&lt;br /&gt;       {tuple,Line,[{call,Line,{atom,Line,self},[]},RHS]}}]}]}}]}},&lt;br /&gt;   Receive = {match,Line,LHS,{'receive',Line,[&lt;br /&gt;       {clause,Line,[{tuple,Line,[VarPid,VarReceive]}], [], [VarReceive]},&lt;br /&gt;       {clause,Line,[{tuple,Line,[{atom,Line,'EXIT'},VarReason]}], [],&lt;br /&gt;           [{call,Line,{atom,Line,exit},[VarReason]}]}]}},&lt;br /&gt;   expr(PidThis, ExprsIn, N - 1, [Spawn&lt;/code&gt;&lt;code&gt; | &lt;/code&gt;&lt;code&gt;SpawnExprs], [Receive | ReceiveExprs]);&lt;br /&gt;% Last expression in parallel block and not a match expression:&lt;br /&gt;% Spawn expression, capture return value as last return from expression sequence.&lt;br /&gt;expr(PidThis, [Expr&lt;/code&gt;&lt;code&gt; | &lt;/code&gt;&lt;code&gt;ExprsIn], 1, SpawnExprs, ReceiveExprs) -&gt;&lt;br /&gt;   Line = element(2, Expr),&lt;br /&gt;   VarPid = {var,Line,new_var()},&lt;br /&gt;   VarReceive = {var,Line,new_var()},&lt;br /&gt;   VarReason = {var,Line,new_var()},&lt;br /&gt;   Spawn = {match,Line,VarPid,{call,Line,{atom,Line,spawn_link},&lt;br /&gt;       [{'fun',Line,{clauses,[{clause,Line,[],[],[{op,Line,'!',{var,Line,PidThis},&lt;br /&gt;       {tuple,Line,[{call,Line,{atom,Line,self},[]},Expr]}}]}]}}]}},&lt;br /&gt;   Receive = {'receive',Line,[&lt;br /&gt;       {clause,Line,[{tuple,Line,[VarPid,VarReceive]}], [], [VarReceive]},&lt;br /&gt;       {clause,Line,[{tuple,Line,[{atom,Line,'EXIT'},VarReason]}], [],&lt;br /&gt;           [{call,Line,{atom,Line,exit},[VarReason]}]}]},&lt;br /&gt;   expr(PidThis, ExprsIn, 0, [Spawn&lt;/code&gt;&lt;code&gt; | &lt;/code&gt;&lt;code&gt;SpawnExprs], [Receive&lt;/code&gt;&lt;code&gt; | &lt;/code&gt;&lt;code&gt;ReceiveExprs]);&lt;br /&gt;% Non-match expression:&lt;br /&gt;% Spawn expression, do not wait for a return message.&lt;br /&gt;expr(PidThis, [Expr | ExprsIn], N, SpawnExprs, ReceiveExprs) -&gt;&lt;br /&gt;   Line = element(2, Expr),&lt;br /&gt;   Spawn = {call,Line,{atom,Line,spawn},&lt;br /&gt;       [{'fun',Line,{clauses,[{clause,Line,[],[],[Expr]}]}}]},&lt;br /&gt;   expr(PidThis, ExprsIn, N - 1, [Spawn&lt;/code&gt;&lt;code&gt; | &lt;/code&gt;&lt;code&gt;SpawnExprs], ReceiveExprs).&lt;br /&gt;&lt;br /&gt;% NEW_VAR/0 - Return the next internal PARE variable.&lt;br /&gt;new_var() -&gt; list_to_atom(?PREFIX ++ integer_to_list(put(?VAR_ID, get(?VAR_ID) + 1))).&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Here is an Erlang version of Don's 'fib' module, using PARE:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;-module(fib).&lt;br /&gt;-export([main/0]).&lt;br /&gt;-compile({parse_transform, pare}).&lt;br /&gt;&lt;br /&gt;fib(0) -&gt; 0;&lt;br /&gt;fib(1) -&gt; 1;&lt;br /&gt;fib(N) -&gt;&lt;br /&gt;   parallel_execution_2,&lt;br /&gt;   A = fib(N-1),&lt;br /&gt;   B = fib(N-2),&lt;br /&gt;   A + B.&lt;br /&gt;&lt;br /&gt;main() -&gt;&lt;br /&gt;   [io:format("n=~B =&gt; ~B~n", [X, fib(X)]) || X &lt;- lists:seq(0, 35)],&lt;br /&gt;   ok.&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Postscript:&lt;br /&gt;&lt;br /&gt;A handy thing to have when developing parse-transformations is a 'showast' module:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;-module(showast).&lt;br /&gt;-export([parse_transform/2]).&lt;br /&gt;&lt;br /&gt;parse_transform(AST, _Options) -&gt;&lt;br /&gt;   io:format("AST:~n~p~n", [AST]),&lt;br /&gt;   AST.&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Include it twice in your testing code to get snapshots of the test module's AST before and after your parse_transform has had a go at it:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;-module(test).&lt;br /&gt;-export([test/0]).&lt;br /&gt;-compile([{parse_transform, showast},&lt;br /&gt;   {parse_transform, pare}, {parse_transform, showast}]).&lt;br /&gt;&lt;br /&gt;test() -&gt; ok.&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Alternatively, you could just compile the test module with the 'P' compiler option &lt;code&gt;c(test, ['P']).&lt;/code&gt; to produce a code listing (in the file "test.P") after the parse-transform has been applied.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Post-Postscript:&lt;br /&gt;&lt;br /&gt;Setting up a macro or two can save a lot of typing with PARE:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;-define(P2, parallel_execution_2).&lt;br /&gt;&lt;br /&gt;test() -&gt;&lt;br /&gt;   ?P2,&lt;br /&gt;   io:format("1~n"),&lt;br /&gt;   io:format("2~n").&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Or you could change PARE to look for a different triggering atom prefix. Be my guest!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1893218858608219953-4323450579543210024?l=chlorophil.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chlorophil.blogspot.com/feeds/4323450579543210024/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://chlorophil.blogspot.com/2007/11/pare-parallel-execution-in-erlang.html#comment-form' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/4323450579543210024'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/4323450579543210024'/><link rel='alternate' type='text/html' href='http://chlorophil.blogspot.com/2007/11/pare-parallel-execution-in-erlang.html' title='PARE - PARallel Execution in Erlang'/><author><name>Philip Robinson</name><uri>http://www.blogger.com/profile/17605642659657207381</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp0.blogger.com/_6zQp6LtHMTo/R09NS6PAIJI/AAAAAAAAAD0/TyYMaEbbr4k/s72-c/Teacup.png' height='72' width='72'/><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1893218858608219953.post-2523104254096015939</id><published>2007-07-08T03:19:00.000+10:00</published><updated>2007-07-28T21:15:23.009+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><category scheme='http://www.blogger.com/atom/ns#' term='gotcha'/><title type='text'>Erlang and the Very Large Binary</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_6zQp6LtHMTo/Ro_Z4qOJxvI/AAAAAAAAADs/0AtfHuz6RUs/s1600-h/HalfOnion06.png"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://bp0.blogger.com/_6zQp6LtHMTo/Ro_Z4qOJxvI/AAAAAAAAADs/0AtfHuz6RUs/s320/HalfOnion06.png" alt="" id="BLOGGER_PHOTO_ID_5084522071754131186" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Update 28/07/2007:&lt;/span&gt; The issue with pattern-matching on very large binaries has been resolved in &lt;a href="http://erlang.org/download/otp_src_R11B-5.readme"&gt;R11B5&lt;/a&gt;, so the workaround below is no longer needed.  Nothing to see here folks, move along... many thanks to the Erlang OTP team for clearing this up.&lt;br /&gt;&lt;br /&gt;The following post has been kept purely for posterity.&lt;br /&gt;&lt;br /&gt;(Also, see Per's comment where a better workaround than mine is suggested.)&lt;br /&gt;&lt;br /&gt;&lt;div style="text-align: center;"&gt;----------&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;I have these &lt;a href="http://chlorophil.blogspot.com/2007/06/erlang-binary-map.html"&gt;binary files I created from the Netflix data&lt;/a&gt;.  Some of them are quite large, so for peace of mind I had to do a quick check to see whether Erlang could handle binaries of that size.&lt;br /&gt;&lt;br /&gt;It turns out that Erlang can indeed handle some &lt;a href="http://erlang.org/doc/efficiency_guide/advanced.html#7.2"&gt;reasonably large binary sizes&lt;/a&gt;.  Sort of.  There was certainly no problem with loading a 300MB binary into RAM.  Accessing the elements of this binary, however, proved to be somewhat of a problem.&lt;br /&gt;&lt;br /&gt;I had written a simple helper function to manage retrieving elements from memory- or file-based binaries[1]:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;bin_get(BytesOffset, BytesToRead, Fd={file_descriptor,prim_file,_}) -&gt;&lt;br /&gt;   {ok,Element} = file:pread(Fd, BytesOffset, BytesToRead),&lt;br /&gt;   Element;&lt;br /&gt;bin_get(BytesOffset, BytesToRead, Bin) -&gt;&lt;br /&gt;   &lt;&lt;_:bytesoffset/binary, Element:BytesToRead/binary&gt;&gt; = Bin,&lt;br /&gt;   Element.&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;For relatively small &lt;code&gt;BytesOffset&lt;/code&gt; values everything worked as expected.  But as soon as I tried an offset of 134,217,728 bytes or higher I received a &lt;code&gt;badmatch&lt;/code&gt; error from &lt;code&gt;bin_get/3&lt;/code&gt;... but only for memory-based binaries.  Opening a file descriptor to the same binary and retrieving the same offset value worked just fine, if a bit slower.&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;span style="font-weight: bold;"&gt;There appears to be a maximum element size limit of 2^27 - 1 for binary pattern matching.&lt;/span&gt;[2]&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;There was a simple workaround for this limit - all I needed to do was have a few extra clauses in &lt;code&gt;bin_get/3&lt;/code&gt; and insert multiple anonymous elements into the binary pattern match where needed.  Since my largest binary is just a bit over 300MB I could get away with three clauses:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;bin_get(BytesOffset, BytesToRead, Bin) when BytesOffset =&lt; 134217727 -&gt;&lt;br /&gt;   &lt;&lt;_:BytesOfset/binary, Element:BytesToRead/binary&gt;&gt; = Bin,&lt;br /&gt;   Element;&lt;br /&gt;bin_get(BytesOffset, BytesToRead, Bin) when BytesOffset =&lt; 268435454 -&gt;&lt;br /&gt;   BytesOffset2 = BytesOffset - 134217727,&lt;br /&gt;   &lt;&lt;_:134217727/binary, _:BytesOffset2/binary, Element:BytesToRead/binary&gt;&gt; = Bin,&lt;br /&gt;   Element;&lt;br /&gt;bin_get(BytesOffset, BytesToRead, Bin) -&gt;&lt;br /&gt;   BytesOffset2 = BytesOffset - 268435454,&lt;br /&gt;   &lt;&lt;_:134217727/binary, _:134217727/binary, _:BytesOffset2/binary, Element:BytesToRead/binary&gt;&gt; = Bin,&lt;br /&gt;   Element.&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I was happy to let a call for an offset greater than 402,653,181 to produce a runtime &lt;code&gt;badmatch&lt;/code&gt; error, but not so happy to discover that the code above produced a compile error:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;beam/beam_load.c(1551): Error loading function test:bin_get/3: op bs_skip_bits2 f x w u u: no specific operation found&lt;br /&gt;{error,badfile}&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;After judicious use of the 'comment out lines of code and recompile the program' debugging technique, I determined that the Erlang compiler really did not like having the two initial anonymous elements in that last binary pattern match.  Even turning the underscores into named (but ignored) variables gave the same result.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The solution to the problem raised by my solution to the initial problem was to go recursive:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;-define(MAX_BIN_ELEM_SIZE, 134217727).&lt;br /&gt;bin_get(BytesOffset, BytesToRead, Bin) when BytesOffset =&lt; ?MAX_BIN_ELEM_SIZE -&gt;&lt;br /&gt;   &lt;&lt;_:BytesOffset/binary, Element:BytesToRead/binary&gt;&gt; = Bin,&lt;br /&gt;   Element;&lt;br /&gt;bin_get(BytesOffset, BytesToRead, &lt;&lt;_:?MAX_BIN_ELEM_SIZE/binary,Bin/binary&gt;&gt;) -&gt;&lt;br /&gt;   bin_get(BytesOffset - ?MAX_BIN_ELEM_SIZE, BytesToRead, Bin).&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;In other words, keep discarding the first 'magic number' bytes from the binary and subtract the magic number from the offset until our offset is equal to or less than the magic number, then access the element in the binary in the usual manner.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;I am not particularly happy with the need for this hack, but the end result lets me use some large in-memory binaries instead of constantly seeking and reading from disk.  (If this last attempt hadn't worked then I was going to try breaking the large binaries into 100-MB chunks, and that would have been a &lt;span style="font-style: italic;"&gt;much&lt;/span&gt; uglier workaround.)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;[1] Depending on how much RAM the machine I was running the code on had, I set certain read-only binary files to be either loaded straight into memory or to just have a file handle opened for them.  &lt;code&gt;bin_get/3&lt;/code&gt; was written to abstract that difference away from the rest of the code.  I would have gotten away with it, too, if it weren't for those pesky errors.&lt;br /&gt;&lt;br /&gt;[2] This would be consistent with using a 32-bit word to store the size value (with 4 of those bits used for a type identifier and 1 bit for a sign indicator).  I would expect the element limit on a 64-bit architecture to be somewhat larger, and I wouldn't have noticed this problem with the size of the binaries I am currently using.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1893218858608219953-2523104254096015939?l=chlorophil.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chlorophil.blogspot.com/feeds/2523104254096015939/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://chlorophil.blogspot.com/2007/07/erlang-and-very-large-binary.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/2523104254096015939'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/2523104254096015939'/><link rel='alternate' type='text/html' href='http://chlorophil.blogspot.com/2007/07/erlang-and-very-large-binary.html' title='Erlang and the Very Large Binary'/><author><name>Philip Robinson</name><uri>http://www.blogger.com/profile/17605642659657207381</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp0.blogger.com/_6zQp6LtHMTo/Ro_Z4qOJxvI/AAAAAAAAADs/0AtfHuz6RUs/s72-c/HalfOnion06.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1893218858608219953.post-5132485084358160305</id><published>2007-06-28T21:14:00.000+10:00</published><updated>2007-06-30T23:29:12.965+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><title type='text'>Erlang Binary Map</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_6zQp6LtHMTo/RoOaPqOJxtI/AAAAAAAAADc/v4O89pbzA4M/s1600-h/SubdivideSphere4.bmp"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://bp0.blogger.com/_6zQp6LtHMTo/RoOaPqOJxtI/AAAAAAAAADc/v4O89pbzA4M/s320/SubdivideSphere4.bmp" alt="" id="BLOGGER_PHOTO_ID_5081074398426416850" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;(Updated to clean up some hypertext errors in the code examples.)&lt;br /&gt;&lt;br /&gt;The &lt;a href="http://www.netflixprize.com/"&gt;Netflix Challenge&lt;/a&gt; &lt;a href="http://www.netflixprize.com/download"&gt;dataset&lt;/a&gt; is very big, and there is not much RAM in my laptop at all.&lt;br /&gt;&lt;br /&gt;To fit as much as possible of the rating data into memory, I converted the data into a bunch of Erlang binaries.  Erlang binaries are 'just' contiguous blocks of bytes, so they do not incur the overhead of list cons cells or dictionary indexing.  They are fast, but pretty simple, and using them to store data like C arrays means that you have to write your own code to manage access to them.  (At least you will get a runtime error if you try to access past the end of a binary, which is a major step up from C.)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;While wandering through the wasteland that is my code I happened to notice that one section did not smell quite right.  This code was taking a chunk of that binary data, converting the chunk into a list of elements, and then mapping a function over this list of elements in order to return a list of function results.  Creating that intermediate list seemed like a bit of unnecessary overhead - I would much rather iterate directly across the binary itself and save all of that extra list consing - but I could not find any &lt;code&gt;binary:map&lt;/code&gt;-like function anywhere.&lt;br /&gt;&lt;br /&gt;So I wrote my own.&lt;br /&gt;&lt;br /&gt;It's not very big.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The original code had used a &lt;code&gt;list_from_bin&lt;/code&gt; function to turn a binary into a list of elements:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;% Convert a binary into a list of unsigned integer elements.&lt;br /&gt;list_from_bin(Bin, BytesPerElem) -&gt;&lt;br /&gt;   list_from_bin(Bin, BytesPerElem, BytesPerElem * 8, size(Bin), []).&lt;br /&gt;&lt;br /&gt;list_from_bin(_Bin, _BytesPerElem, _BitsPerElem, 0, Results) -&gt; Results;&lt;br /&gt;list_from_bin(Bin, BytesPerElem, BitsPerElem, BytesOffset, Results) -&gt;&lt;br /&gt;   BytesDiscard = BytesOffset - BytesPerElem,&lt;br /&gt;   &lt;&lt;_:BytesDiscard/binary,Element:BitsPerElem/unsigned-integer,_/binary&gt;&gt; = Bin,&lt;br /&gt;   list_from_bin(Bin, BytesPerElem, BitsPerElem, BytesDiscard, [Element|Results]).&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And &lt;code&gt;list_from_bin&lt;/code&gt; was used in this manner (with a trivial "&lt;code&gt;2 * Elem&lt;/code&gt;" function):&lt;br /&gt;&lt;br /&gt;&lt;code&gt;[2 * N || N &lt;- list_from_bin(&lt;&lt;1,2,3,4&gt;&gt;, 1)].&lt;/code&gt;&lt;br /&gt;-&gt; [2,4,6,8]&lt;br /&gt;&lt;br /&gt;&lt;code&gt;[2 * N || N &lt;- list_from_bin(&lt;&lt;1,2,3,4&gt;&gt;, 2)].&lt;/code&gt;&lt;br /&gt;-&gt; [516,1544]&lt;br /&gt;&lt;br /&gt;Note that &lt;code&gt;list_from_bin&lt;/code&gt; iterates from the end of the binary backwards down to the beginning of the binary, so the list it builds is in the same order as the original binary and does not need reversing.&lt;br /&gt;&lt;br /&gt;(If all of my elements were one byte long then I could have just used the standard Erlang &lt;code&gt;binary_to_list/1&lt;/code&gt; function, but sometimes the elements I used were two or three bytes in length.  I should have probably included an initial clause of "&lt;code&gt;list_from_bin(Bin, 1) -&gt; list_from_binary(Bin);&lt;/code&gt;", but didn't think of it at the time.)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The new &lt;code&gt;map_bin&lt;/code&gt; function maps a function over the elements in a binary, and returns a &lt;em&gt;list&lt;/em&gt; of the function's results:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;map_bin(Fun, Bin, BytesPerElem) -&gt;&lt;br /&gt;   map_bin(Fun, Bin, BytesPerElem, 0, size(Bin) div BytesPerElem, []).&lt;br /&gt;&lt;br /&gt;map_bin(_Fun, _Bin, _BytesPerElem, _BytesDiscard, 0, Results) -&gt; lists:reverse(Results);&lt;br /&gt;map_bin(Fun, Bin, BytesPerElem, BytesDiscard, CountElemRemain, Results) -&gt;&lt;br /&gt;   &lt;&lt;_:BytesDiscard/binary,Elem:BytesPerElem/binary,_/binary&gt;&gt; = Bin,&lt;br /&gt;   map_bin(Fun, Bin, BytesPerElem, BytesDiscard + BytesPerElem, CountElemRemain - 1, [Fun(Elem)|Results]).&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The main function (&lt;code&gt;map_bin/3&lt;/code&gt;) takes these arguments:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;Fun&lt;/code&gt;: A function that takes a single binary element as input.  May return any value.&lt;/li&gt;&lt;li&gt;&lt;code&gt;Bin&lt;/code&gt;: The original binary data block.  Its &lt;code&gt;size&lt;/code&gt; should be an exact multiple of &lt;code&gt;BytesPerElem&lt;/code&gt;.  If the &lt;code&gt;size&lt;/code&gt; of the binary is not an exact multiple of the &lt;code&gt;BytesPerElem&lt;/code&gt; value then any excess bytes at the end of the binary are discarded.&lt;/li&gt;&lt;li&gt;&lt;code&gt;BytesPerElem&lt;/code&gt;: The number of bytes taken up by each element in the binary.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;The helper function (&lt;code&gt;map_bin/6&lt;/code&gt;) takes three additional arguments:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;BytesDiscard&lt;/code&gt;: The number of bytes to skip at the beginning of the binary, for the current iteration.&lt;/li&gt;&lt;li&gt;&lt;code&gt;CountElemRemain&lt;/code&gt;: The number of elements remaining to process.&lt;/li&gt;&lt;li&gt;&lt;code&gt;Results&lt;/code&gt;: An accumulated, reversed list of the function's results.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;The &lt;code&gt;BytesDiscard&lt;/code&gt; argument was added to avoid having to recalculate the number of bytes to skip for every iteration (with, for example, something like "&lt;code&gt;BytesDiscard = Offset * BytesPerElem&lt;/code&gt;").  I am not sure if this was a good decision or if it reeks too much of premature optimisation.  Old C habits die hard.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;CountElemRemain&lt;/code&gt; starts at the number of elements to process and decrements each iteration so the terminating condition can be written simply as &lt;code&gt;0&lt;/code&gt;, rather than having to have a "&lt;code&gt;when Offset &gt; CountElems&lt;/code&gt;" guard on the function clause.&lt;br /&gt;&lt;br /&gt;And here is &lt;code&gt;map_bin&lt;/code&gt; in action:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;map_bin(fun(&lt;&lt; Elem:8&gt;&gt;) -&gt; 2 * Elem end, &lt;&lt;1,2,3,4&gt;&gt;, 1).&lt;/code&gt;&lt;br /&gt;-&gt; [2,4,6,8]&lt;br /&gt;&lt;br /&gt;&lt;code&gt;map_bin(fun(&lt;&lt; Elem:16&gt;&gt;) -&gt; 2 * Elem end, &lt;&lt;1,2,3,4&gt;&gt;, 2).&lt;/code&gt;&lt;br /&gt;-&gt; [516,1544]&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Now with the new &lt;code&gt;map_bin&lt;/code&gt; function my code can skip the creation of an intermediate list, and, quite entirely by accident, is actually more flexible than before.  The original code always produced lists of unsigned integers from the binaries; the new code can be used to operate on multiple elements.  For example:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;map_bin(fun(&lt;&lt; Elem1:8,Elem2:16&gt;&gt;) -&gt; Elem1 + Elem2 end, &lt;&lt;1,2,3,4,5,6&gt;&gt;, 3).&lt;/code&gt;&lt;br /&gt;-&gt; [516,1290]&lt;br /&gt;&lt;br /&gt;It's just not &lt;span style="font-style: italic;"&gt;quite&lt;/span&gt; as nice to look at as a good list comprehension.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Performance&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The &lt;code&gt;map_bin&lt;/code&gt; function is all well and good, but the real question we all want answered is... does using this new code actually make our program run any faster?&lt;br /&gt;&lt;br /&gt;Well, according to my &lt;span style="font-weight:bold;"&gt;very&lt;/span&gt; informal use of &lt;code&gt;now/0&lt;/code&gt; and &lt;code&gt;timer:now_diff/2&lt;/code&gt;, with large binaries and a trivial "x2" function for each element, &lt;code&gt;map_bin&lt;/code&gt; seems to be around 11% faster than using &lt;code&gt;list_from_bin&lt;/code&gt;.  That's... not too bad.  But we could go faster with multiple processes, I think.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Parallelism&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Really, what is the point of using Erlang if we don't &lt;code&gt;spawn&lt;/code&gt; a few hundred processes for every trivial piece of code?&lt;br /&gt;&lt;br /&gt;Luckily for my fingers it is only a minor modification to make a parallel version of &lt;code&gt;map_bin&lt;/code&gt;:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;pmap_bin(Fun, Bin, BytesPerElem) -&gt;&lt;br /&gt;   pmap_bin(Fun, Bin, BytesPerElem, 0, size(Bin) div BytesPerElem, []).&lt;br /&gt;&lt;br /&gt;pmap_bin(_Fun, _Bin, _BytesPerElem, _BytesDiscard, 0, Pids) -&gt;&lt;br /&gt;   [receive {done, Pid, Result} -&gt; Result end || Pid &lt;- lists:reverse(Pids)];&lt;br /&gt;pmap_bin(Fun, Bin, BytesPerElem, BytesDiscard, CountElemRemain, Pids) -&gt;&lt;br /&gt;   PidThis = self(),&lt;br /&gt;   &lt;&lt;_:BytesDiscard/binary,Elem:BytesPerElem/binary,_/binary&gt;&gt; = Bin,&lt;br /&gt;   pmap_bin(Fun, Bin, BytesPerElem, BytesDiscard + BytesPerElem, CountElemRemain - 1,&lt;br /&gt;       [spawn(fun() -&gt; PidThis ! {done, self(), Fun(Elem)} end)|Pids]).&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Sadly, I cannot comment much on the speed difference of this conversion because I do not (yet) have access to a multi-core machine.  A routine like this is probably best avoided for a single-CPU system as the overhead of spawning many processes would be wasted, but it should perform better on multiple-CPU systems.  (Feel free to try it out and let me know how it goes!)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;There is still room for improvement in this function, if we wanted to take it further.  We may not want to spawn a separate process for each element, for instance, but rather split the original binary into N chunks and spawn a process to apply a function to each chunk.  Also, we might want to expand on the parallelism and include other nodes into the mix, to spread the calculations across multiple machines.&lt;br /&gt;&lt;br /&gt;Something for another day.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:78%;"&gt;And now someone is going to tell me that binary comprehensions have been available in the standard Erlang distribution for a while, and I just haven't been paying enough attention to the Erlang mailing-list announcements.&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1893218858608219953-5132485084358160305?l=chlorophil.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chlorophil.blogspot.com/feeds/5132485084358160305/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://chlorophil.blogspot.com/2007/06/erlang-binary-map.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/5132485084358160305'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/5132485084358160305'/><link rel='alternate' type='text/html' href='http://chlorophil.blogspot.com/2007/06/erlang-binary-map.html' title='Erlang Binary Map'/><author><name>Philip Robinson</name><uri>http://www.blogger.com/profile/17605642659657207381</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp0.blogger.com/_6zQp6LtHMTo/RoOaPqOJxtI/AAAAAAAAADc/v4O89pbzA4M/s72-c/SubdivideSphere4.bmp' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1893218858608219953.post-440793804211725344</id><published>2007-06-22T13:28:00.000+10:00</published><updated>2007-06-22T13:47:40.182+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><category scheme='http://www.blogger.com/atom/ns#' term='gotcha'/><title type='text'>Erlang Macro Oddness</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_6zQp6LtHMTo/RntCTbit3BI/AAAAAAAAADU/grrwczgjlcU/s1600-h/SubdivideSphere3.bmp"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;" src="http://bp3.blogger.com/_6zQp6LtHMTo/RntCTbit3BI/AAAAAAAAADU/grrwczgjlcU/s320/SubdivideSphere3.bmp" border="0" alt=""id="BLOGGER_PHOTO_ID_5078725906368683026" /&gt;&lt;/a&gt;&lt;br /&gt;I found a little oddity with Erlang macros while I was writing version 2 of the &lt;a href="http://chlorophil.blogspot.com/2007/06/collaborative-filtering-weighted-slope_20.html"&gt;Weighted Slope One&lt;/a&gt; algorithm.  It seems that passing a multi-statement anonymous function as a parameter into a macro confuses the compiler.&lt;br /&gt;&lt;br /&gt;For example, this code works:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;-module(macro_oddness).&lt;br /&gt;-export([start/0]).&lt;br /&gt;-define(A_MACRO(FunAnon), apply(FunAnon, [])).&lt;br /&gt;&lt;br /&gt;start() -&gt;&lt;br /&gt;    ?A_MACRO(&lt;br /&gt;        fun() -&gt;&lt;br /&gt;            io:format("Single-element function works fine.~n")&lt;br /&gt;            end).&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;But this code produces a compile-time error:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;-module(macro_oddness).&lt;br /&gt;-export([start/0]).&lt;br /&gt;-define(A_MACRO(FunAnon), apply(FunAnon, [])).&lt;br /&gt;&lt;br /&gt;start() -&gt; ?A_MACRO(&lt;br /&gt;        fun() -&gt;&lt;br /&gt;            io:format("Multiple-element function "),&lt;br /&gt;            io:format("does not compile.~n")&lt;br /&gt;            end).&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;An "argument mismatch for macro ''A_MACRO''" error, to be precise.&lt;br /&gt;&lt;br /&gt;Interestingly, multiple statements in a begin..end block seem to be okay:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;-module(macro_oddness).&lt;br /&gt;-export([start/0]).&lt;br /&gt;-define(A_MACRO(FunAnon), apply(FunAnon, [])).&lt;br /&gt;&lt;br /&gt;start() -&gt; ?A_MACRO(&lt;br /&gt;        fun() -&gt;&lt;br /&gt;            begin&lt;br /&gt;                io:format("Multiple-element function "),&lt;br /&gt;                io:format("with a begin..end block is okay.~n")&lt;br /&gt;                end&lt;br /&gt;            end).&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Something to keep an eye out for.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1893218858608219953-440793804211725344?l=chlorophil.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chlorophil.blogspot.com/feeds/440793804211725344/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://chlorophil.blogspot.com/2007/06/erlang-macro-oddness.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/440793804211725344'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/440793804211725344'/><link rel='alternate' type='text/html' href='http://chlorophil.blogspot.com/2007/06/erlang-macro-oddness.html' title='Erlang Macro Oddness'/><author><name>Philip Robinson</name><uri>http://www.blogger.com/profile/17605642659657207381</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp3.blogger.com/_6zQp6LtHMTo/RntCTbit3BI/AAAAAAAAADU/grrwczgjlcU/s72-c/SubdivideSphere3.bmp' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1893218858608219953.post-6635847185465253867</id><published>2007-06-20T22:30:00.000+10:00</published><updated>2007-06-20T22:46:26.100+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='slope_one'/><category scheme='http://www.blogger.com/atom/ns#' term='collaborative_filtering'/><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><title type='text'>Collaborative Filtering: Weighted Slope One in Erlang (v2)</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp2.blogger.com/_6zQp6LtHMTo/Rnkefrit3AI/AAAAAAAAADM/0WIo7Urb1kw/s1600-h/wave2.png"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;" src="http://bp2.blogger.com/_6zQp6LtHMTo/Rnkefrit3AI/AAAAAAAAADM/0WIo7Urb1kw/s320/wave2.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5078123584450059266" /&gt;&lt;/a&gt;&lt;br /&gt;Okay, so my initial Weighted Slope One Erlang translation wasn't very Erlang-ish... not a single &lt;code&gt;spawn&lt;/code&gt; in sight and side-effects galore.  Yuck.&lt;br /&gt;&lt;br /&gt;I've ripped out ETS and replaced it with dictionaries, and modified the &lt;code&gt;build_matrix&lt;/code&gt; loops to palm off the real work to &lt;code&gt;spawn&lt;/code&gt;ed processes rather than do it themselves.  &lt;br /&gt;&lt;br /&gt;The main change was with the &lt;code&gt;build_matric&lt;/code&gt; function.  A long-running process is &lt;code&gt;spawn&lt;/code&gt;ed for every item in the system (the 'X' item in the {x, y} difference/frequency matrix), and short-term user rating processes are &lt;code&gt;spawn&lt;/code&gt;ed to send off difference and frequency information to the relevant item and wait for confirmation that the message was received.  Once all of the data has been sent the item processes are asked to return their individual dictionaries to the main process, which then merges them into one big dictionary.&lt;br /&gt;&lt;br /&gt;The item processes die naturally after they return their dictionaries, and the user rating processes only live long enough to ensure that their data is received by the relevant item process.&lt;br /&gt;&lt;br /&gt;The only other significant changes to the program were to make the &lt;code&gt;predict&lt;/code&gt; function use a dictionary rather than an ETS table.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The concurrent bits of this program use this idiom[*]:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;process_flag(trap_exit, true),&lt;br /&gt;[receive&lt;br /&gt;    {'EXIT', Pid, normal} -&gt; ok;&lt;br /&gt;    {'EXIT', Pid, Reason} -&gt; exit(self(), Reason)&lt;br /&gt;    end&lt;br /&gt;    || Pid &lt;- [spawn_link(fun() -&gt; &lt;span style="font-weight:bold;"&gt;&gt;&gt;do something&lt;&lt;&lt;/span&gt; end)&lt;br /&gt;        || &lt;span style="font-weight:bold;"&gt;&gt;&gt;value/s &lt;- list of value/s&lt;&lt;&lt;/span&gt;]]&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;In other words: &lt;code&gt;spawn&lt;/code&gt; a process to do something for every element in the list of values, and wait for each of those processes to signal that they have finished normally.  (I used a substitution macro to avoid typing in all that boilerplate.)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;An unfortunate consequence of this modification is that the code is about 50% larger than the earlier Erlang version:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;%% slopeone2.erl&lt;br /&gt;% Philip Robinson&lt;br /&gt;% A parallel implementation of the weighted slope one&lt;br /&gt;% algorithm in Erlang for item-based collaborative&lt;br /&gt;% filtering.&lt;br /&gt;% Based on the same algorithm in Java by Daniel Lemire and Marco Ponzi:&lt;br /&gt;% http://www.daniel-lemire.com/fr/documents/publications/SlopeOne.java&lt;br /&gt;&lt;br /&gt;-module(slopeone2).&lt;br /&gt;-export([start/0]).&lt;br /&gt;&lt;br /&gt;-define(SPAWN_AND_WAIT(Block, Data),&lt;br /&gt;    process_flag(trap_exit, true),&lt;br /&gt;    [receive&lt;br /&gt;        {'EXIT', Pid, normal} -&gt; ok;&lt;br /&gt;        {'EXIT', Pid, Reason} -&gt; exit(self(), Reason)&lt;br /&gt;        end&lt;br /&gt;        || Pid &lt;- [spawn_link(fun() -&gt; Block end)&lt;br /&gt;            || Data]]).&lt;br /&gt;&lt;br /&gt;start() -&gt;&lt;br /&gt;    % The rating database: A list of users, each containing a list of {item, rating} elements.&lt;br /&gt;    Items = [{item1,"candy"}, {item2,"dog"}, {item3,"cat"}, {item4,"war"}, {item5,"strange food"}],&lt;br /&gt;    DataRating = [{user1, "Bob",       [{item1,1.0}, {item2,0.5}, {item4,0.1}]},&lt;br /&gt;                  {user2, "Jane",      [{item1,1.0}, {item3,0.5}, {item4,0.2}]},&lt;br /&gt;                  {user3, "Jo",        [{item1,0.9}, {item2,0.4}, {item3,0.5}, {item4,0.1}]},&lt;br /&gt;                  {user4, "StrangeJo", [{item1,0.1}, {item4,1.0}, {item5,0.4}]}],&lt;br /&gt;    % The difference &amp; frequency database: a dictionary of {{item X, itemY}, {diff, freq}}.&lt;br /&gt;    % Create the predictor engine.&lt;br /&gt;    DiffFreq = build_matrix(Items, DataRating),&lt;br /&gt;    io:format("Here's the data I have accumulated...~n"),&lt;br /&gt;    print_data(Items, DataRating, DiffFreq),&lt;br /&gt;    io:format("Ok, now we predict...~n"),&lt;br /&gt;    TestRatings1 = [{item5,0.4}],&lt;br /&gt;    io:format("Inputting...~n"),&lt;br /&gt;    print_user_ratings(Items, TestRatings1),&lt;br /&gt;    io:format("Getting...~n"),&lt;br /&gt;    print_user_ratings(Items, predict(Items, TestRatings1, DiffFreq)),&lt;br /&gt;    TestRatings2 = [{item4,0.2}|TestRatings1],&lt;br /&gt;    io:format("Inputting...~n"),&lt;br /&gt;    print_user_ratings(Items, TestRatings2),&lt;br /&gt;    io:format("Getting...~n"),&lt;br /&gt;    print_user_ratings(Items, predict(Items, TestRatings2, DiffFreq)),&lt;br /&gt;    ok.&lt;br /&gt;&lt;br /&gt;% Based on existing data, and using weights, try to predict all missing ratings.&lt;br /&gt;% The trick to make this more scalable is to consider only difference entries&lt;br /&gt;% having a large (&gt; 1) frequency entry.&lt;br /&gt;% Precondition: ItemRatings is sorted as per lists:sort/1 (to re-merge actual ratings).&lt;br /&gt;predict(Items, ItemRatings, DiffFreq) -&gt;&lt;br /&gt;    % Gather the sum of the rating differences and frequencies for all available items given the known item ratings.&lt;br /&gt;    RatingsPredicted = lists:foldl(&lt;br /&gt;        fun({IdItemX, IdItemY, RatingX}, Dict) -&gt;&lt;br /&gt;            case dict:find({IdItemY, IdItemX}, DiffFreq) of&lt;br /&gt;                error -&gt; Dict;&lt;br /&gt;                {ok, {Diff, DFreq}} -&gt;&lt;br /&gt;                    dict:update(IdItemY,&lt;br /&gt;                        fun({Pred, PFreq}) -&gt; {Pred + ((RatingX + Diff) * DFreq), PFreq + DFreq} end,&lt;br /&gt;                        {(RatingX + Diff) * DFreq, DFreq},&lt;br /&gt;                        Dict)&lt;br /&gt;                end&lt;br /&gt;            end,&lt;br /&gt;        dict:new(),&lt;br /&gt;        [{IdItemX, IdItemY, RatingX}&lt;br /&gt;            || {IdItemX, RatingX} &lt;- ItemRatings,&lt;br /&gt;                {IdItemY, _} &lt;- Items]),&lt;br /&gt;    % Put the original (actual) ratings back into our prediction list.&lt;br /&gt;    RatingsPredictedAndActual = lists:foldl(&lt;br /&gt;        fun({Item,Rating}, Ratings) -&gt; dict:store(Item, {Rating, 1}, Ratings) end,&lt;br /&gt;        RatingsPredicted, ItemRatings),&lt;br /&gt;    % Divide the total rating difference by the frequency and return as a list.&lt;br /&gt;    [{Item, Rating / Freq} || {Item, {Rating, Freq}} &lt;- dict:to_list(RatingsPredictedAndActual)].&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;print_data(Items, DataRating, DiffFreq) -&gt;&lt;br /&gt;    [begin io:format("~s~n", [Name]), print_user_ratings(Items, ItemRatings) end&lt;br /&gt;        || {_Id, Name, ItemRatings} &lt;- DataRating],&lt;br /&gt;    [print_item_diffs(Items, Item, DiffFreq) || Item &lt;- Items],&lt;br /&gt;    io:format("~n").&lt;br /&gt;&lt;br /&gt;print_user_ratings(Items, ItemRatings) -&gt;&lt;br /&gt;    [begin {value, {Item, NameItem}} = lists:keysearch(Item, 1, Items),&lt;br /&gt;        io:format(" ~12s --&gt; ~4.2f~n", [NameItem, Rating]) end&lt;br /&gt;        || {Item, Rating} &lt;- lists:sort(ItemRatings)].&lt;br /&gt;&lt;br /&gt;print_item_diffs(Items, {ItemX, Name}, DiffFreq) -&gt;&lt;br /&gt;    io:format("~n~12s:", [Name]),&lt;br /&gt;    [case dict:find({ItemX, ItemY}, DiffFreq) of&lt;br /&gt;        error -&gt; io:format("          ");&lt;br /&gt;        {ok, {Diff, Freq}} -&gt; io:format("  ~6.3f ~1B", [Diff, Freq])&lt;br /&gt;        end || {ItemY, _} &lt;- Items].&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;% Long-running itemX process to manage a dictionary of {{itemX, itemY}, {Diff, Freq}} entries.&lt;br /&gt;dict_itemX(IdItemX, DictDiffFreq) -&gt;&lt;br /&gt;    receive&lt;br /&gt;        {data, PidSender, IdItemY, DiffNew} -&gt;&lt;br /&gt;            PidSender ! {done, self(), IdItemY},&lt;br /&gt;            dict_itemX(IdItemX,&lt;br /&gt;                dict:update({IdItemX, IdItemY},&lt;br /&gt;                    fun({DiffOld, FreqOld}) -&gt; {DiffOld + DiffNew, FreqOld + 1} end,&lt;br /&gt;                    {DiffNew, 1}, DictDiffFreq));&lt;br /&gt;        {results, PidParent} -&gt; PidParent ! {results, self(), DictDiffFreq}&lt;br /&gt;        end.&lt;br /&gt;&lt;br /&gt;% Build a matrix (dictionary) of {{itemX, itemY}, Difference, Frequency} entries.&lt;br /&gt;build_matrix(Items, DataRating) -&gt;&lt;br /&gt;    PidThis = self(),&lt;br /&gt;    % Create a bunch of long-running processes to manage itemX data.&lt;br /&gt;    PidItems = dict:from_list(&lt;br /&gt;        [{IdItemX, spawn(fun() -&gt; dict_itemX(IdItemX, dict:new()) end)} || {IdItemX, _NameItem} &lt;- Items]),&lt;br /&gt;    % Spawn a short-term process for each user's ratings and wait until they are all done.&lt;br /&gt;    ?SPAWN_AND_WAIT(process_user_ratings(PidItems, Ratings), {_, _, Ratings} &lt;- DataRating),&lt;br /&gt;    % Retrieve and merge all the item process dictionaries, then divide all differences by their frequency.&lt;br /&gt;    dict:map(&lt;br /&gt;        fun(_Key, {Diff, Freq}) -&gt; {Diff / Freq, Freq} end,&lt;br /&gt;        dict:fold(&lt;br /&gt;            fun(_IdItemX, PidItemX, DictIn) -&gt;&lt;br /&gt;                PidItemX ! {results, PidThis},&lt;br /&gt;                receive&lt;br /&gt;                    {results, PidItemX, DiffFreq} -&gt;&lt;br /&gt;                        dict:merge(fun(_, _, _) -&gt; io:format("Key collision~n") end, DictIn, DiffFreq)&lt;br /&gt;                    end&lt;br /&gt;                end,&lt;br /&gt;            dict:new(), PidItems)).&lt;br /&gt;&lt;br /&gt;process_user_ratings(PidItems, Ratings) -&gt;&lt;br /&gt;    % Spawn a short-term process for each itemX rating and wait until they are all done.&lt;br /&gt;    ?SPAWN_AND_WAIT(process_user_itemX_ratings(dict:fetch(IdItemX, PidItems), RatingX, Ratings),&lt;br /&gt;                        {IdItemX, RatingX} &lt;- Ratings).&lt;br /&gt;&lt;br /&gt;process_user_itemX_ratings(PidItemX, RatingX, Ratings) -&gt;&lt;br /&gt;    % Spawn a process for each itemY rating that sends a message to the long-running itemX process,&lt;br /&gt;    % waits for confirmation that its message has been processed, then signals that it is done.&lt;br /&gt;    ?SPAWN_AND_WAIT(begin&lt;br /&gt;            PidItemX ! {data, self(), IdItemY, RatingX - RatingY},&lt;br /&gt;            receive {done, PidItemX, IdItemY} -&gt; ok end&lt;br /&gt;        end, {IdItemY, RatingY} &lt;- Ratings).&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;[*] If trapping exits is not your cup of tea then you could use plain old &lt;code&gt;spawn&lt;/code&gt; like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;PidThis = self(),&lt;br /&gt;[receive {done, Pid} -&gt; ok end&lt;br /&gt;    || Pid &lt;- [spawn(fun() -&gt;&lt;br /&gt;                    &lt;span style="font-weight:bold;"&gt;&gt;&gt;do something&lt;&lt;&lt;/span&gt;,&lt;br /&gt;                    PidThis ! {done, self()}&lt;br /&gt;                end)&lt;br /&gt;        || &lt;span style="font-weight:bold;"&gt;&gt;&gt;value/s &lt;- list of value/s&lt;&lt;&lt;/span&gt;]]&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I had used this code until I thought of using &lt;code&gt;spawn_link&lt;/code&gt;.  I am not sure which version would be considered better by Erlang Gurus, but my personal preference leans towards &lt;code&gt;spawn_link&lt;/code&gt;.  &lt;code&gt;Spawn_link&lt;/code&gt; seems easier to extend to handle multiple Erlang nodes, and if I was concerned about setting my main process to trap all exits then I could simply &lt;code&gt;spawn&lt;/code&gt; a single process to manage all of the other &lt;code&gt;spawn_link&lt;/code&gt;ing.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1893218858608219953-6635847185465253867?l=chlorophil.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chlorophil.blogspot.com/feeds/6635847185465253867/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://chlorophil.blogspot.com/2007/06/collaborative-filtering-weighted-slope_20.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/6635847185465253867'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/6635847185465253867'/><link rel='alternate' type='text/html' href='http://chlorophil.blogspot.com/2007/06/collaborative-filtering-weighted-slope_20.html' title='Collaborative Filtering: Weighted Slope One in Erlang (v2)'/><author><name>Philip Robinson</name><uri>http://www.blogger.com/profile/17605642659657207381</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp2.blogger.com/_6zQp6LtHMTo/Rnkefrit3AI/AAAAAAAAADM/0WIo7Urb1kw/s72-c/wave2.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1893218858608219953.post-5769530097979046160</id><published>2007-06-19T01:00:00.000+10:00</published><updated>2007-06-19T01:32:20.718+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='slope_one'/><category scheme='http://www.blogger.com/atom/ns#' term='collaborative_filtering'/><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><title type='text'>Collaborative Filtering: Weighted Slope One in Erlang</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp2.blogger.com/_6zQp6LtHMTo/RnakRbit2_I/AAAAAAAAADE/45dS-70tKa8/s1600-h/wave.png"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;" src="http://bp2.blogger.com/_6zQp6LtHMTo/RnakRbit2_I/AAAAAAAAADE/45dS-70tKa8/s320/wave.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5077426249264913394" /&gt;&lt;/a&gt;&lt;br /&gt;I have been toying with the &lt;a href="http://netflixprize.com/"&gt;Netflix Challenge&lt;/a&gt; for a while now.  It's fascinating stuff.&lt;br /&gt;&lt;br /&gt;I knew nothing about collaborative filtering when I started this project, but that it pretty normal for my coding hobbies.  (Hey, why would I start something new if I already knew how to do it?)&lt;br /&gt;&lt;br /&gt;During my standard "collect and absorb everything" phase I ran across an article on Wikipedia that described the &lt;a href="http://en.wikipedia.org/wiki/Slope_One"&gt;Slope One algorithm&lt;/a&gt;.  This article had links to various implementations of the algorithm, including a &lt;a href="http://www.daniel-lemire.com/fr/documents/publications/SlopeOne.java"&gt;standalone Java program by Daniel Lemire&lt;/a&gt;.  More information on the Weighted Slope One algorithm may be found on &lt;a href="http://www.daniel-lemire.com/fr/abstracts/SDM2005.html"&gt;Daniel's site&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I am not using Slope One in my current Netflix algorithm attempt, but I translated Daniel's Java code into Erlang as a learning exercise anyway:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;%% slopeone.erl&lt;br /&gt;% Philip Robinson&lt;br /&gt;% A simple implementation of the weighted slope one&lt;br /&gt;% algorithm in Erlang for item-based collaborative&lt;br /&gt;% filtering.&lt;br /&gt;% Based on the same algorithm in Java by Daniel Lemire and Marco Ponzi:&lt;br /&gt;% http://www.daniel-lemire.com/fr/documents/publications/SlopeOne.java&lt;br /&gt;&lt;br /&gt;-module(slopeone).&lt;br /&gt;-export([start/0]).&lt;br /&gt;&lt;br /&gt;start() -&gt;&lt;br /&gt;    % The rating database: A list of users, each containing a list of {item, rating} elements.&lt;br /&gt;    Items = [{item1,"candy"}, {item2,"dog"}, {item3,"cat"}, {item4,"war"}, {item5,"strange food"}],&lt;br /&gt;    DataRating = [{user1, "Bob",       [{item1,1.0}, {item2,0.5}, {item4,0.1}]},&lt;br /&gt;                  {user2, "Jane",      [{item1,1.0}, {item3,0.5}, {item4,0.2}]},&lt;br /&gt;                  {user3, "Jo",        [{item1,0.9}, {item2,0.4}, {item3,0.5}, {item4,0.1}]},&lt;br /&gt;                  {user4, "StrangeJo", [{item1,0.1}, {item4,1.0}, {item5,0.4}]}],&lt;br /&gt;    % The difference &amp; frequency database: an ETS table of {{item X, itemY}, diff, freq}.&lt;br /&gt;    ets:new(diff_freq, [private, set, named_table]),&lt;br /&gt;    % Create the predictor engine.&lt;br /&gt;    build_matrix(DataRating),&lt;br /&gt;    io:format("Here's the data I have accumulated...~n"),&lt;br /&gt;    print_data(Items, DataRating),&lt;br /&gt;    io:format("Ok, now we predict...~n"),&lt;br /&gt;    TestRatings1 = [{item5,0.4}],&lt;br /&gt;    io:format("Inputting...~n"),&lt;br /&gt;    print_user_ratings(Items, TestRatings1),&lt;br /&gt;    io:format("Getting...~n"),&lt;br /&gt;    print_user_ratings(Items, predict(Items, TestRatings1)),&lt;br /&gt;    TestRatings2 = [{item4,0.2}|TestRatings1],&lt;br /&gt;    io:format("Inputting...~n"),&lt;br /&gt;    print_user_ratings(Items, TestRatings2),&lt;br /&gt;    io:format("Getting...~n"),&lt;br /&gt;    print_user_ratings(Items, predict(Items, TestRatings2)),&lt;br /&gt;    ets:delete(diff_freq).&lt;br /&gt;&lt;br /&gt;% Based on existing data, and using weights, try to predict all missing ratings.&lt;br /&gt;% The trick to make this more scalable is to consider only diff_freq entries&lt;br /&gt;% having a large (&gt; 1) frequency entry.&lt;br /&gt;predict(Items, ItemRatings) -&gt;&lt;br /&gt;    PredFreq = ets:new(pred_freq, []),&lt;br /&gt;    [ets:insert(PredFreq, {Item, 0.0, 0}) || {Item, _} &lt;- Items],&lt;br /&gt;    [[case {ets:match(diff_freq, {{ItemY, ItemX}, '$1', '$2'}),&lt;br /&gt;            ets:match(PredFreq, {ItemY, '$1', '$2'})} of&lt;br /&gt;        {[], _} -&gt; ok;&lt;br /&gt;        {[[Diff, DFreq]], [[Pred, PFreq]]} -&gt;&lt;br /&gt;            ets:insert(PredFreq, {ItemY, Pred + ((RatingX + Diff) * DFreq), PFreq + DFreq})&lt;br /&gt;        end || {ItemY, _} &lt;- Items] || {ItemX, RatingX} &lt;- ItemRatings],&lt;br /&gt;    ets:match_delete(PredFreq, {'_', '_', 0}), % Remove all zero-frequency predictions.&lt;br /&gt;    [ets:insert(PredFreq, {Item, Rating, 1}) || {Item, Rating} &lt;- ItemRatings], % Re-insert actual ratings.&lt;br /&gt;    Results = [{Item, Rating / Freq} || {Item, Rating, Freq} &lt;- ets:tab2list(PredFreq)],&lt;br /&gt;    ets:delete(PredFreq),&lt;br /&gt;    Results.&lt;br /&gt;&lt;br /&gt;print_data(Items, DataRating) -&gt;&lt;br /&gt;    [begin io:format("~s~n", [Name]),&lt;br /&gt;        print_user_ratings(Items, ItemRatings) end&lt;br /&gt;        || {_Id, Name, ItemRatings} &lt;- DataRating],&lt;br /&gt;    [print_item_diffs(Items, Item) || Item &lt;- Items],&lt;br /&gt;    io:format("~n").&lt;br /&gt;&lt;br /&gt;print_user_ratings(Items, ItemRatings) -&gt;&lt;br /&gt;    [begin {value, {Item, NameItem}} = lists:keysearch(Item, 1, Items),&lt;br /&gt;        io:format(" ~12s --&gt; ~4.2f~n", [NameItem, Rating]) end&lt;br /&gt;        || {Item, Rating} &lt;- lists:sort(ItemRatings)].&lt;br /&gt;&lt;br /&gt;print_item_diffs(Items, {Item, Name}) -&gt;&lt;br /&gt;    io:format("~n~12s:", [Name]),&lt;br /&gt;    [case ets:match(diff_freq, {{Item, Id}, '$1', '$2'}) of&lt;br /&gt;        [] -&gt; io:format("          ");&lt;br /&gt;        [[Diff, Freq]] -&gt; io:format("  ~6.3f ~1B", [Diff, Freq])&lt;br /&gt;        end || {Id, _} &lt;- Items].&lt;br /&gt;&lt;br /&gt;% Build a matrix (ETS table) of {{itemX, itemY}, Difference, Frequency} entries.&lt;br /&gt;build_matrix(DataRating) -&gt;&lt;br /&gt;    % Gather the sum difference and the total count (frequency).&lt;br /&gt;    [[[case ets:lookup(diff_freq, {ItemX, ItemY}) of&lt;br /&gt;        [] -&gt; ets:insert(diff_freq, {{ItemX, ItemY}, RatingX - RatingY, 1});&lt;br /&gt;        [{Key, Diff, Freq}] -&gt; ets:insert(diff_freq, {Key, Diff + RatingX - RatingY, Freq + 1})&lt;br /&gt;        end || {ItemY, RatingY} &lt;- ItemRatings]&lt;br /&gt;            || {ItemX, RatingX} &lt;- ItemRatings]&lt;br /&gt;            || {_, _, ItemRatings} &lt;- DataRating],&lt;br /&gt;    % Divide sum of difference by frequency to get mean difference.&lt;br /&gt;    DivideFun = fun({Key, Diff, Freq}, _) -&gt; ets:insert(diff_freq, {Key, Diff / Freq, Freq}) end,&lt;br /&gt;    ets:foldl(DivideFun, undefined, diff_freq).&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Some musings:&lt;br /&gt;&lt;br /&gt;* I do not really consider this code to be a typical "Erlang-style" program.  In particular, I am making substantial use of side-effects via ETS tables; comparisons between this code and the Java original will probably not be representative of comparisons between Erlang and Java programs in general.&lt;br /&gt;&lt;br /&gt;* I may have gone a little overboard with the use of list comprehensions.  I did not really like LCs when first exposed to them and it seems that I am overcompensating for that now.&lt;br /&gt;&lt;br /&gt;* While some functions are obviously shorter in this Erlang version (compare &lt;code&gt;build_matrix&lt;/code&gt; and &lt;code&gt;buildDiffMatrix&lt;/code&gt;, for example), I am not convinced that they are necessarily clearer in Erlang than in Java.  At least one advantage of smaller functions is that I can fit more of them on my 8.9" screen, but if that was my main concern then I would be programming in something even less verbose.&lt;br /&gt;&lt;br /&gt;* While I haven't delved into this much, I suspect that using ETS has made this program harder to parallelise effectively.  While the loops could easily be converted to use a parallel map algorithm, all CRUD activity still has to go through a single ETS process bottleneck.  One possible way of utilising multiple CPUs might be to spawn a bunch of processes to manage individual items, send each of these messages regarding specific ratings, and then convert the results into a dictionary via a list comprehension of &lt;code&gt;receive&lt;/code&gt; statements and &lt;code&gt;dict:from_list/1&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;* I did run the Dialyzer over the code but I have not even considered optimising it for performance.  It &lt;span style="font-style:italic;"&gt;will&lt;/span&gt; be slower than the Java version. :-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1893218858608219953-5769530097979046160?l=chlorophil.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chlorophil.blogspot.com/feeds/5769530097979046160/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://chlorophil.blogspot.com/2007/06/collaborative-filtering-weighted-slope.html#comment-form' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/5769530097979046160'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/5769530097979046160'/><link rel='alternate' type='text/html' href='http://chlorophil.blogspot.com/2007/06/collaborative-filtering-weighted-slope.html' title='Collaborative Filtering: Weighted Slope One in Erlang'/><author><name>Philip Robinson</name><uri>http://www.blogger.com/profile/17605642659657207381</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp2.blogger.com/_6zQp6LtHMTo/RnakRbit2_I/AAAAAAAAADE/45dS-70tKa8/s72-c/wave.png' height='72' width='72'/><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1893218858608219953.post-8468169041901984487</id><published>2007-04-22T20:54:00.000+10:00</published><updated>2007-04-22T22:20:10.562+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='emp2'/><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><title type='text'>Erlang Macro Processor (v2), Part V</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_6zQp6LtHMTo/Ris_WkGm_VI/AAAAAAAAAC8/3tr4VPEa5Y4/s1600-h/CoralCrabLobsterRockSmall.png"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://bp1.blogger.com/_6zQp6LtHMTo/Ris_WkGm_VI/AAAAAAAAAC8/3tr4VPEa5Y4/s320/CoralCrabLobsterRockSmall.png" alt="" id="BLOGGER_PHOTO_ID_5056204663534583122" border="0" /&gt;&lt;/a&gt;The final step for EMP2 is to expand any remote macro function calls and insert the results back into the AST.&lt;br /&gt;&lt;br /&gt;Naively we would just follow the same pattern as the &lt;code&gt;macro&lt;/code&gt; attribute expansion that we have just added:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;node_parse(Node={call,Line,{remote,_,{atom,_,Mod},{atom,_,Fun}},Args}, Mods) -&gt;&lt;br /&gt;    case lists:member(Mod, Mods) of&lt;br /&gt;        true -&gt;&lt;br /&gt;            ast_from_results(lists:flatten([apply(Mod,Fun,Args)|" "]), Line, []);&lt;br /&gt;        false -&gt; setelement(4,Node,node_parse(Args, Mods))&lt;br /&gt;        end;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;But if we do, we find that there are three(!) problems with this approach.&lt;br /&gt;&lt;br /&gt;Firstly, &lt;code&gt;ast_from_results&lt;/code&gt; is currently using &lt;code&gt;erl_parse:parse_form&lt;/code&gt; to turn the textual macro results into an AST.  This only works for complete Erlang forms (function definitions) and not for, say, a set of three Erlang expressions to be inserted into a function.  We can fix this by using &lt;code&gt;erl_parse:parse_exprs&lt;/code&gt; instead, but we will also have to append a full-stop and space to the result string (instead of just a space) to get it to work properly.&lt;br /&gt;&lt;br /&gt;Secondly, the arguments for the function call are all in AST format with tuples and line numbers everywhere.  We cannot just apply the function directly to these arguments; we need to convert them back to something more usable.&lt;br /&gt;&lt;br /&gt;Finally, we may receive more than one Erlang expression from the macro.  To fit these back into the space of a single node we have to wrap them in a &lt;code&gt;block&lt;/code&gt; expression.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;To tackle the first issue we need to update &lt;code&gt;ast_from_results&lt;/code&gt; a little:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;ast_from_results(FunParse, ResultsString, LineStart, ASTResults) -&gt;&lt;br /&gt;    case remove_leading_whitespace(ResultsString) of&lt;br /&gt;        "" -&gt; lists:flatten(lists:reverse(ASTResults));&lt;br /&gt;        String -&gt;&lt;br /&gt;            {done,{ok,Tokens,LineEnd},StringRest} =&lt;br /&gt;                erl_scan:tokens([], String, LineStart),&lt;br /&gt;            {ok, AST} = erl_parse:FunParse(Tokens),&lt;br /&gt;            ast_from_results(FunParse, StringRest, LineEnd, [AST|ASTResults])&lt;br /&gt;        end.&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;As an aside, you might like to have a closer look at that &lt;code&gt;erl_parse:FunParse&lt;/code&gt; call.&lt;br /&gt;&lt;br /&gt;Yes, instead of hard-coding a function call or adding an extra &lt;code&gt;if&lt;/code&gt; statement, we are calling the &lt;code&gt;erl_parse&lt;/code&gt; function via a variable whose value we will not know until run-time&lt;span style="font-weight: bold;"&gt;[1]&lt;/span&gt;.  Doesn't thinking about that just make you go all tingly inside?  No?  Me neither.  Of course.&lt;br /&gt;&lt;br /&gt;We can now use &lt;code&gt;ast_from_results&lt;/code&gt; for &lt;code&gt;erl_parse:parse_form&lt;/code&gt; and &lt;code&gt;erl_parse:parse_exprs&lt;/code&gt; situations with only a single additional "erl_parse function" argument.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;For the second issue I am going to use a cheap and nasty hack.  Because we are not (yet) supporting anything fancier than literal terms in the argument list, we can get away with this little bit of trickery to convert the arguments into something usable by our call to &lt;code&gt;apply&lt;/code&gt;:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;ArgsLiteral = [Value || {_Type,_Line,Value} &lt;- Args]. &lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The third issue is also very easily fixed by wrapping the call to &lt;code&gt;ast_from_results&lt;/code&gt; in a &lt;code&gt;block&lt;/code&gt; expression tuple.  We should only do this if there is more than one node in the results list:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;node_parse(Node={call,Line,{remote,_,{atom,_,Mod},{atom,_,Fun}},Args}, Mods) -&gt;&lt;br /&gt;    case lists:member(Mod, Mods) of&lt;br /&gt;        true -&gt;&lt;br /&gt;            ArgsLiteral = [Value || {_Type,_Line,Value} &lt;- Args],&lt;br /&gt;            Results = lists:flatten([apply(Mod,Fun,ArgsLiteral)|". "]),&lt;br /&gt;            case length(Results) of&lt;br /&gt;                1 -&gt; hd(Results);&lt;br /&gt;                _ -&gt; {block,Line,ast_from_results(parse_exprs, Results, Line, [])}&lt;br /&gt;                end;&lt;br /&gt;        false -&gt; setelement(4,Node,node_parse(Args, Mods))&lt;br /&gt;        end;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Oh, and of course we need to update the other &lt;code&gt;node_parse&lt;/code&gt; function clause to include the new argument to &lt;code&gt;ast_from_results&lt;/code&gt;:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;node_parse({attribute,Line,macro,{Mod,Fun,Args}}, _Mods) -&gt;&lt;br /&gt;    ast_from_results(parse_form, lists:flatten([apply(Mod,Fun,Args)|" "]), Line, []);&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And with any luck we are done.  Let's try it out on our &lt;a href="http://chlorophil.blogspot.com/2007/04/erlang-macro-processor-v2-part-i.html"&gt;example code&lt;/a&gt;.&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;1&gt; CL = fun(F) -&gt; c(F), l(F) end.&lt;br /&gt;#Fun&lt;br /&gt;2&gt; CL(emp2), CL(example_macro), CL(example).&lt;br /&gt;{module, example}&lt;br /&gt;3&gt; [example:lookup(N) || N &lt;- lists:seq(0, 3)]. [0,2,4,6] 4&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Yep.  EMP2 is done.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The full listing:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;-module(emp2).&lt;br /&gt;-author("Philip Robinson").&lt;br /&gt;-vsn('1.0').&lt;br /&gt;-export([parse_transform/2]).&lt;br /&gt;&lt;br /&gt;parse_transform(AST, _Options) -&gt;&lt;br /&gt;    Mods = lists:flatten([Mods || {attribute,_Line,macro_modules,Mods} &lt;- AST]),&lt;br /&gt;    lists:flatten([node_parse(Node, Mods) || Node &lt;- AST]).&lt;br /&gt;node_parse({attribute,Line,macro,{Mod,Fun,Args}}, _Mods) -&gt;&lt;br /&gt;    ast_from_results(parse_form, lists:flatten([apply(Mod,Fun,Args)|" "]), Line, []);&lt;br /&gt;node_parse(Node={call,Line,{remote,_,{atom,_,Mod},{atom,_,Fun}},Args}, Mods) -&gt;&lt;br /&gt;    case lists:member(Mod, Mods) of&lt;br /&gt;        true -&gt;&lt;br /&gt;            ArgsLiteral = [Value || {_Type,_Line,Value} &lt;- Args],&lt;br /&gt;            Results = lists:flatten([apply(Mod,Fun,ArgsLiteral)|". "]),&lt;br /&gt;            case length(Results) of&lt;br /&gt;                1 -&gt; hd(Results);&lt;br /&gt;                _ -&gt; {block,Line,ast_from_results(parse_exprs,Results,Line,[])}&lt;br /&gt;                end;&lt;br /&gt;        false -&gt; setelement(4,Node,node_parse(Args, Mods))&lt;br /&gt;        end;&lt;br /&gt;node_parse(Node, Mods) when is_list(Node) -&gt;&lt;br /&gt;    [node_parse(Element, Mods) || Element &lt;- Node];&lt;br /&gt;node_parse(Node, Mods) when is_tuple(Node) -&gt;&lt;br /&gt;    list_to_tuple([node_parse(Element, Mods) || Element &lt;- tuple_to_list(Node)]);&lt;br /&gt;node_parse(Node, _Mods) -&gt; Node.&lt;br /&gt;&lt;br /&gt;args_from_ast(AST) -&gt; [Value || {_Type,_Line,Value} &lt;- AST].&lt;br /&gt;&lt;br /&gt;ast_from_results(FunParse, ResultsString, LineStart, ASTResults) -&gt;&lt;br /&gt;    case remove_leading_whitespace(ResultsString) of&lt;br /&gt;        "" -&gt; lists:flatten(lists:reverse(ASTResults));&lt;br /&gt;        String -&gt;&lt;br /&gt;            {done,{ok,Tokens,LineEnd},StringRest} =&lt;br /&gt;                erl_scan:tokens([], String, LineStart),&lt;br /&gt;            {ok, AST} = erl_parse:FunParse(Tokens),&lt;br /&gt;            ast_from_results(FunParse, StringRest, LineEnd, [AST|ASTResults])&lt;br /&gt;        end.&lt;br /&gt;&lt;br /&gt;remove_leading_whitespace([9 |String]) -&gt; remove_leading_whitespace(String);&lt;br /&gt;remove_leading_whitespace([10|String]) -&gt; remove_leading_whitespace(String);&lt;br /&gt;remove_leading_whitespace([32|String]) -&gt; remove_leading_whitespace(String);&lt;br /&gt;remove_leading_whitespace(    String ) -&gt; String.&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;EMP2: Entirely painful compile-time macros for functions and expressions, in 45 lines of obscure, uncommented, and unreadable Erlang code.&lt;br /&gt;&lt;span style="font-size:85%;"&gt;(If you think that this code is bad, wait until you see EMP3.)&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;[1]&lt;/span&gt; Run-time for EMP2 is, of course, compile-time for the module that we are using EMP2 to transform.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1893218858608219953-8468169041901984487?l=chlorophil.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chlorophil.blogspot.com/feeds/8468169041901984487/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://chlorophil.blogspot.com/2007/04/erlang-macro-processor-v2-part-v.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/8468169041901984487'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/8468169041901984487'/><link rel='alternate' type='text/html' href='http://chlorophil.blogspot.com/2007/04/erlang-macro-processor-v2-part-v.html' title='Erlang Macro Processor (v2), Part V'/><author><name>Philip Robinson</name><uri>http://www.blogger.com/profile/17605642659657207381</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp1.blogger.com/_6zQp6LtHMTo/Ris_WkGm_VI/AAAAAAAAAC8/3tr4VPEa5Y4/s72-c/CoralCrabLobsterRockSmall.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1893218858608219953.post-7762903372832489352</id><published>2007-04-22T14:41:00.000+10:00</published><updated>2007-04-22T14:56:54.043+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='emp2'/><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><title type='text'>Erlang Macro Processor (v2), Part IV</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_6zQp6LtHMTo/RirnuUGm_UI/AAAAAAAAAC0/-MapHnBlMR4/s1600-h/GeckoFlySmall.png"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://bp3.blogger.com/_6zQp6LtHMTo/RirnuUGm_UI/AAAAAAAAAC0/-MapHnBlMR4/s320/GeckoFlySmall.png" alt="" id="BLOGGER_PHOTO_ID_5056108314533231938" border="0" /&gt;&lt;/a&gt;Okay, now we are getting somewhere.  Time to expand some macros!&lt;br /&gt;&lt;br /&gt;To begin with we will start with something easy, like duplicating EMP1's functionality.  We already have code from EMP1 to expand the &lt;code&gt;-macro&lt;/code&gt; attribute entries, but unfortunately we cannot just cut-and-paste the EMP1 code into EMP2; our AST-walking is slightly different and we need to adjust &lt;code&gt;ast_reversed_results&lt;/code&gt;:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;ast_from_results(ResultsString, LineStart, ASTResults) -&gt;&lt;br /&gt;   case remove_leading_whitespace(ResultsString) of&lt;br /&gt;       "" -&gt; lists:reverse(ASTResults);&lt;br /&gt;       String -&gt;&lt;br /&gt;           {done,{ok,Tokens,LineEnd},StringRest} =&lt;br /&gt;               erl_scan:tokens([], String, LineStart),&lt;br /&gt;           {ok, AST} = erl_parse:parse_form(Tokens),&lt;br /&gt;           ast_from_results(StringRest, LineEnd, [AST|ASTResults])&lt;br /&gt;       end.&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;We change the &lt;code&gt;-macro&lt;/code&gt; clause for &lt;code&gt;node_parse&lt;/code&gt; to call the new function:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;node_parse({attribute,Line,macro,{Mod,Fun,Args}}, _Mods) -&gt;&lt;br /&gt;   ast_from_results(lists:flatten([apply(Mod,Fun,Args)|" "]), Line, []);&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;And that obscene &lt;code&gt;remove_leading_whitespace&lt;/code&gt; function has returned:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;remove_leading_whitespace([9 |String]) -&gt; remove_leading_whitespace(String);&lt;br /&gt;remove_leading_whitespace([10|String]) -&gt; remove_leading_whitespace(String);&lt;br /&gt;remove_leading_whitespace([32|String]) -&gt; remove_leading_whitespace(String);&lt;br /&gt;remove_leading_whitespace(    String ) -&gt; String.&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;The only difference between &lt;code&gt;ast_from_results&lt;/code&gt; and &lt;code&gt;ast_reversed_results&lt;/code&gt; is that &lt;code&gt;ast_from_results&lt;/code&gt; keeps the resulting AST in the same order as the input ResultsString argument (it kindly reverses its already-reversed results for us before passing them back).&lt;br /&gt;&lt;br /&gt;Unlike EMP1, EMP2 does NOT want to receive the results of the expanded AST in reversed order.  We are not following the "build a list in reverse and then reverse the result" model for our AST (which works just fine for traversing the top level only), but rather using a recursive descent model for AST parsing.  In this situation we need to keep the results in the order that they appear.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Now we have the EMP2 module reproducing the functionality of EMP1, and at only a few more lines of code.  The only thing left to do is identify macro function calls, apply them, and insert the parsed results into the AST in place of the original call.&lt;br /&gt;&lt;br /&gt;Ha!&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;For remote function calls we have two situations to handle:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;The remote function call is to a macro, and&lt;/li&gt;&lt;li&gt;The remote function call is &lt;span style="font-style: italic;"&gt;not&lt;/span&gt; to a macro.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;The easier case is when the remote function call is &lt;span style="font-style: italic;"&gt;not&lt;/span&gt; to a macro function.  We pretty much just want the default tuple node function to run on the node, but we cannot (easily) get there because this more-specific function clause will have intercepted the node before the default code gets a chance to run on it.&lt;br /&gt;&lt;br /&gt;We could encapsulate the common default code in another function (or a substitution macro), but for simplicity's sake I will just build the required node in place with the &lt;code&gt;setelement&lt;/code&gt; function.  It is not a large amount of code:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;node_parse(Node={call,Line,{remote,_,{atom,_,Mod},{atom,_,Fun}},Args}, Mods) -&gt;&lt;br /&gt;   case lists:member(Mod, Mods) of&lt;br /&gt;       true -&gt;&lt;br /&gt;           io:format("Function-level macro call: ~w~n", [Node]),&lt;br /&gt;           Node;&lt;br /&gt;       false -&gt; setelement(4,Node,node_parse(Args, Mods))&lt;br /&gt;       end;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Next up: The final installment - expanding remote macro function calls.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1893218858608219953-7762903372832489352?l=chlorophil.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chlorophil.blogspot.com/feeds/7762903372832489352/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://chlorophil.blogspot.com/2007/04/erlang-macro-processor-v2-part-iv.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/7762903372832489352'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/7762903372832489352'/><link rel='alternate' type='text/html' href='http://chlorophil.blogspot.com/2007/04/erlang-macro-processor-v2-part-iv.html' title='Erlang Macro Processor (v2), Part IV'/><author><name>Philip Robinson</name><uri>http://www.blogger.com/profile/17605642659657207381</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp3.blogger.com/_6zQp6LtHMTo/RirnuUGm_UI/AAAAAAAAAC0/-MapHnBlMR4/s72-c/GeckoFlySmall.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1893218858608219953.post-3554824493070788919</id><published>2007-04-22T09:24:00.000+10:00</published><updated>2007-04-22T09:35:21.739+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='emp2'/><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><title type='text'>Erlang Macro Processor (v2), Part III</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_6zQp6LtHMTo/RiqdbkGm_TI/AAAAAAAAACs/NEn-HWNRTr8/s1600-h/PalmTreesLagoonSmall.png"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;" src="http://bp0.blogger.com/_6zQp6LtHMTo/RiqdbkGm_TI/AAAAAAAAACs/NEn-HWNRTr8/s320/PalmTreesLagoonSmall.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5056026628550229298" /&gt;&lt;/a&gt;&lt;br /&gt;The top level of the AST is a list of nodes, rather than a node in its own right, so we might write our first attempt at an[other] AST walker like this:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;parse_transform(AST, _Options) -&gt;&lt;br /&gt;    Mods = lists:flatten([Mods || {attribute,_Line,macro_modules,Mods} &lt;- AST]),&lt;br /&gt;    lists:flatten([node_parse(Node, Mods) || Node &lt;- AST]).&lt;br /&gt;&lt;br /&gt;node_parse(Node, _Mods) -&gt; Node.&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;The &lt;code&gt;parse_transform&lt;/code&gt; function calls &lt;code&gt;node_parse&lt;/code&gt; on each top-level node in the AST.  It calls &lt;code&gt;lists:flatten&lt;/code&gt; on the result because - as we already know - the EMP1-variety of top-level macro expansion may return more than one function definition from a single macro call.  These definitions all need to be at the same "height" as the others, so the resulting deep list of nodes needs to be flattened.&lt;br /&gt;&lt;br /&gt;These two functions together will traverse the top level of the AST but not examine any sub-nodes.  To do that we need to split the atom... er, node tuples, and parse each element in sequence:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;node_parse(Node, Mods) when is_tuple(Node) -&gt;&lt;br /&gt;    list_to_tuple([node_parse(Element, Mods) || Element &lt;- tuple_to_list(Node)]).&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Now if we were to compile and run this on our example.erl file we would get a big fat error... it turns out that not every element in a node tuple is actually another node tuple (but we already knew that, too).  Some of the elements are lists, and some of them are atoms or integers.  A few extra clauses should take care of these conditions:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;node_parse(Node, Mods) when is_list(Node) -&gt;&lt;br /&gt;    [node_parse(Element, Mods) || Element &lt;- Node];&lt;br /&gt;node_parse(Node, _Mods) -&gt; Node.&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Here is the whole module in one piece:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;-module(emp2).&lt;br /&gt;-export([parse_transform/2]).&lt;br /&gt;&lt;br /&gt;parse_transform(AST, _Options) -&gt;&lt;br /&gt;    Mods = lists:flatten([Mods || {attribute,_Line,macro_modules,Mods} &lt;- AST]),&lt;br /&gt;    lists:flatten([node_parse(Node, Mods) || Node &lt;- AST]).&lt;br /&gt;&lt;br /&gt;node_parse(Node, Mods) when is_list(Node) -&gt;&lt;br /&gt;    [node_parse(Element, Mods) || Element &lt;- Node];&lt;br /&gt;node_parse(Node, Mods) when is_tuple(Node) -&gt;&lt;br /&gt;    [Type,Line|ListElements] = tuple_to_list(Node),&lt;br /&gt;    Results = [node_parse(Element, Mods) || Element &lt;- ListElements],&lt;br /&gt;    list_to_tuple([Type,Line|Results]);&lt;br /&gt;node_parse(Node, _Mods) -&gt; Node.&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;And that is all we need to generically walk the entire AST.&lt;br /&gt;&lt;br /&gt;Trapping the specific nodes we want to macro-expand is also rather trivial.  We need to catch &lt;code&gt;macro&lt;/code&gt; module attributes and remote function calls, and to do that we just add two new clauses to the &lt;code&gt;node_parse&lt;/code&gt; function:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;node_parse(Node={attribute,Line,macro,{Mod,Fun,Args}}, _Mods) -&gt;&lt;br /&gt;    io:format("Line ~B: EMP1-style macro attribute found.~n", [Line]),&lt;br /&gt;    % Do macro-expansion of attribute's Mod, Fun, and Args values.&lt;br /&gt;    Node;&lt;br /&gt;node_parse(Node={call,Line,{remote,L,{atom,_,Mod},{atom,_,Fun}},Args}, Mods) -&gt;&lt;br /&gt;    io:format("Line ~B: EMP2-style remote function call found.~n", [Line]),&lt;br /&gt;    % Test whether the remote call is to a macro module.&lt;br /&gt;    % If so, expand it.  Otherwise traverse node as usual.&lt;br /&gt;    setelement(4, Node, node_parse(Args, Mods));&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Next up: Expanding the macros.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1893218858608219953-3554824493070788919?l=chlorophil.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chlorophil.blogspot.com/feeds/3554824493070788919/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://chlorophil.blogspot.com/2007/04/erlang-macro-processor-v2-part-iii.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/3554824493070788919'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/3554824493070788919'/><link rel='alternate' type='text/html' href='http://chlorophil.blogspot.com/2007/04/erlang-macro-processor-v2-part-iii.html' title='Erlang Macro Processor (v2), Part III'/><author><name>Philip Robinson</name><uri>http://www.blogger.com/profile/17605642659657207381</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp0.blogger.com/_6zQp6LtHMTo/RiqdbkGm_TI/AAAAAAAAACs/NEn-HWNRTr8/s72-c/PalmTreesLagoonSmall.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1893218858608219953.post-1665519967388231702</id><published>2007-04-21T15:04:00.000+10:00</published><updated>2007-04-21T21:29:02.252+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='emp2'/><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><title type='text'>Erlang Macro Processor (v2), Part II</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_6zQp6LtHMTo/RimhyEGm_SI/AAAAAAAAACk/wiANDYvWW3g/s1600-h/FishingBootSmall.png"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://bp1.blogger.com/_6zQp6LtHMTo/RimhyEGm_SI/AAAAAAAAACk/wiANDYvWW3g/s320/FishingBootSmall.png" alt="" id="BLOGGER_PHOTO_ID_5055749938167086370" border="0" /&gt;&lt;/a&gt;You know we need to do it eventually, so let's get the boring "find &lt;code&gt;-macro_modules&lt;/code&gt; attributes and store their values" bit out of the way so we can move on to some more interesting stuff.  Here we go:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;-module(emp2).&lt;br /&gt;-export([parse_transform/2]).&lt;br /&gt;&lt;br /&gt;parse_transform(AST, _Options) -&gt;&lt;br /&gt;    Mods = lists:flatten([Mods || {attribute,_Line,macro_modules,Mods} &lt;- AST]),&lt;br /&gt;    io:format("Macro Modules: ~p~n", [Mods]),&lt;br /&gt;    AST.&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Ah, whoops.  One of those &lt;a href="http://www.erlang.org/doc/doc-5.5.4/doc/reference_manual/expressions.html#6.22"&gt;list comprehension&lt;/a&gt; thingies seems to have slipped into the parse_transform function.  To get rid of it we just have to change that line into something like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;    Mods = lists:flatten(lists:map(&lt;br /&gt;        fun ({attribute,_Line,macro_modules,Mods}) -&gt; Mods;&lt;br /&gt;            (_Node) -&gt; []&lt;br /&gt;            end,&lt;br /&gt;        AST)),&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Hmmm.  On second thoughts, maybe we should keep the list comprehension.&lt;br /&gt;&lt;br /&gt;I believe that list comprehensions are a relatively new feature in Erlang so you may not see too many of them in existing code, but they really are worth learning.  (Erlang is in good company: Python and Haskell have list comprehensions too.)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Back from that tangent and to the program at hand, we see that the macro module names are being stored in an ordinary list.  I expect that only a few macro modules (probably only one at most) will be specified in any given module, and looking for an element in a one-element list is pretty quick, so we should not be needing the indexing overhead of a dictionary.   I also don't particularly mind if someone specifies a macro module more than once, or if a specified macro module is never used.  (If we were &lt;span style="font-style: italic;"&gt;really&lt;/span&gt; &lt;span&gt;concerned&lt;/span&gt; about duplicate macro module names then we could use one of the &lt;code&gt;list&lt;/code&gt; module functions to easily remove them.)&lt;br /&gt;&lt;br /&gt;We could also roll the gathering of the &lt;code&gt;macro_modules&lt;/code&gt; attributes up into the AST-walking code, but conceptually it is nicer to keep it up here and out of the way.  Also, as this code only traverses the very top level of the AST it should be quite quick.  Pattern-matching one entry per module attribute and function definition is not a computationally expensive task.&lt;br /&gt;&lt;br /&gt;Right, the boring stuff is done; let's get into parsing that AST.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;As I briefly mused at the bottom of a previous &lt;a href="http://chlorophil.blogspot.com/2007/04/atomiser-part-iv.html"&gt;Atomiser&lt;/a&gt; post:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;Rather than consisting of a bunch of pattern matching clauses, the walk_ast function could be made "smarter" by transforming the given node tuple into a list, and applying some rules-based logic to the elements of that list (from the third element onwards).&lt;/blockquote&gt;&lt;br /&gt;I reckon we could give this a go and see where we end up.  (Either it will work and we have learned something new, or it won't work and we will have learned something new, so it is a win-win situation either way.)&lt;br /&gt;&lt;br /&gt;You might recall that the Atomiser &lt;code&gt;walk_ast&lt;/code&gt; function had a clause for each node type.  This was a great way for me to implement the Atomiser because I got to see the AST nodes that made up my programs, but in the end it has turned out to be a pretty ugly function.&lt;br /&gt;&lt;br /&gt;Here are a few lines of the &lt;code&gt;walk_ast&lt;/code&gt; function as a quick refresher (the substitution macro actually makes the code nicer than it could be):&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;?WALK_AST({call,_Line,_Fun,Args}, Args); % Handles local and module calls.&lt;br /&gt;?WALK_AST({'case',_Line,Test,Clauses}, [Test|Clauses]);&lt;br /&gt;?WALK_AST({'catch',_Line,Expr}, Expr);&lt;br /&gt;?WALK_AST({char,_Line,_Char}, []);&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;And those clauses go on (and on!) for about forty different node types...&lt;br /&gt;&lt;br /&gt;I would much rather only have specific clause for handling each node that we are interested in, and use some generic code to handle the rest.  But if we want to create some rules to manage these nodes generically then we had better find some patterns in all of that mess.&lt;br /&gt;&lt;br /&gt;...&lt;br /&gt;&lt;br /&gt;The first (and blindingly obvious) thing to notice about the nodes is that - without exception - they are all tuples.  (I know, I know: I am a genius.  Applause is not strictly necessary.  Really.  Oh, all right then, a little bit of applause is okay, if you insist.)&lt;br /&gt;&lt;br /&gt;Two of these tuple nodes are not quite the same as the others: {error, Details} and {warning, Details}.  In all of the other nodes the first element is the node type and the second element is the line number of the source file that the node appears in.  After that there are a variable number of elements (possibly none) with node-specific meanings.&lt;br /&gt;&lt;br /&gt;We are interested in catching &lt;code&gt;-macro&lt;/code&gt; attributes (so EMP2 can do the work of EMP1) as well as remote function call nodes that are calling a macro function.  Everything else is irrelevant, except that we want to recursively descend into sub-nodes to keep searching for other remote macro function calls.&lt;br /&gt;&lt;br /&gt;If we take a closer look at the elements of nodes we will note that the element is always either a list, a tuple, or atomic (i.e.: an atom or an integer).  These elements might have a special meaning to the compiler (depending on their location in the current node tuple) but to us they are just potential sub-nodes.  If the node does not match an attribute or remote function call pattern then the elements have no meaning to EMP2 and we can treat them as homogenous lumps of node matter.&lt;br /&gt;&lt;br /&gt;Of the additional elements in a node (if any), they are either&lt;br /&gt;&lt;ul&gt;&lt;li&gt;a list, which we can parse as its own sub-AST,&lt;/li&gt;&lt;li&gt;a tuple, which we can parse as another node, or&lt;/li&gt;&lt;li&gt;atomic (or integer), which we can pass back as-is.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;I think that all of these notes are probably enough to get us started coding.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1893218858608219953-1665519967388231702?l=chlorophil.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chlorophil.blogspot.com/feeds/1665519967388231702/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://chlorophil.blogspot.com/2007/04/erlang-macro-processor-v2-part-ii.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/1665519967388231702'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/1665519967388231702'/><link rel='alternate' type='text/html' href='http://chlorophil.blogspot.com/2007/04/erlang-macro-processor-v2-part-ii.html' title='Erlang Macro Processor (v2), Part II'/><author><name>Philip Robinson</name><uri>http://www.blogger.com/profile/17605642659657207381</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp1.blogger.com/_6zQp6LtHMTo/RimhyEGm_SI/AAAAAAAAACk/wiANDYvWW3g/s72-c/FishingBootSmall.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1893218858608219953.post-3211430900325373283</id><published>2007-04-19T22:50:00.000+10:00</published><updated>2007-04-20T11:23:40.599+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='emp2'/><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><title type='text'>Erlang Macro Processor (v2), Part I</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_6zQp6LtHMTo/Ridl_kGm_QI/AAAAAAAAACU/dDsAOTFAiW8/s1600-h/SunFlowerSmall.png"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://bp1.blogger.com/_6zQp6LtHMTo/Ridl_kGm_QI/AAAAAAAAACU/dDsAOTFAiW8/s320/SunFlowerSmall.png" alt="" id="BLOGGER_PHOTO_ID_5055121249444232450" border="0" /&gt;&lt;/a&gt;&lt;a href="http://chlorophil.blogspot.com/2007/04/erlang-macro-processor-v1-part-i.html"&gt;EMP1&lt;/a&gt; is all well and good, but it does have more than its fair share of idiosyncratic behaviour&lt;span style="font-weight: bold;"&gt;[1]&lt;/span&gt;:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;EMP1 can only be used to create full functions at the top level of a module.  This makes it a bit more difficult to use than strictly necessary, especially if we only want to generate a term to use within a function.&lt;/li&gt;&lt;li&gt;Arguments passed to the macro must be literal values - no function calls allowed!&lt;/li&gt;&lt;li&gt;Macros must be defined in a separate module, which must be compiled before the macro-using module is compiled.&lt;/li&gt;&lt;/ul&gt;Quite frankly that first point bugs the hell out of me.  I really should not have to write a macro that returns an &lt;span style="font-style: italic;"&gt;entire&lt;/span&gt; function definition if I only need to generate a small portion of a function.&lt;br /&gt;&lt;br /&gt;Today we will begin to tackle this issue with EMP2, but before we dive straight into the parse_transform code I would like to spend a few moments updating our example code.  The rewrite will make the example_macro.erl and example.erl modules use the as-yet-unwritten EMP2 module functionality.   I probably won't explicitly show it in these posts, but the compile errors I get from running EMP2 over example.erl will have a big influence over the direction that its development takes.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;We will still need a separate macro module, but the macro function will only generate the lookup table itself rather than return a whole function definition:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;-module(example_macro).&lt;br /&gt;-export([lookup_binary/1]).&lt;br /&gt;&lt;br /&gt;lookup_binary(Size) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[[$,,FirstVal]|NumberString] = lists:map(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun(Offset) -&gt; io_lib:format(",~B", [Offset * 2]) end,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lists:seq(0, Size - 1)),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"&lt;&lt;" ++ [FirstVal] ++ NumberString ++ "&gt;&gt;".&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;We have lost the code that produces the whole function and only kept our lookup binary creation function, which also seems to have picked up a jaunty little Size argument from somewhere.   As before, each element's value is twice its offset (modulo 256: we are only storing bytes after all).&lt;br /&gt;&lt;br /&gt;To check that the new macro code works correctly:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;1&gt; CL = fun(F) -&gt; c(F), l(F) end.&lt;br /&gt;#Fun&lt;br /&gt;2&gt; CL(example_macro).&lt;br /&gt;{module,example_macro}&lt;br /&gt;3&gt; M = fun(R) -&gt; io:format("~s~n", [lists:flatten(R)]) end.&lt;br /&gt;#Fun&lt;br /&gt;4&gt; M(example_macro:lookup_binary(4)).&lt;br /&gt;&lt;&lt;0,2,4,6&gt;&gt;&lt;br /&gt;ok&lt;br /&gt;5&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;And we also have to rewrite the module that calls this lookup macro:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;-module(example).&lt;br /&gt;-export([lookup/1]).&lt;br /&gt;&lt;br /&gt;-compile({parse_transform, emp2}).&lt;br /&gt;-macro_modules([example_macro]).&lt;br /&gt;&lt;br /&gt;lookup(Offset) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;&lt;_:offset/binary,value:8/integer,_/binary&gt;&gt; =&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;example_macro:lookup_binary(4),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Value.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;This does look a lot nicer than the EMP1 version.  Only the snippet of code that needs to be dynamically generated is in the macro module; the rest of the code is in the standard module where it belongs, and the macro call is in a much more appropriate place - inside the function that uses it - than lurking within a module attribute.&lt;br /&gt;&lt;br /&gt;With EMP1 we had to peek inside another module to see that a lookup/1 function was being generated, but here we can see that fact already in front of us.  We can even guess that a binary term will be created just from the context around the macro call.&lt;br /&gt;&lt;br /&gt;Note that 'emp1' has changed to 'emp2' in the parse_transform compiler directive, and that we need a new 'macro_modules' module attribute to tell EMP2 which remote function calls are to be expanded at compile-time.&lt;br /&gt;&lt;br /&gt;Once we have written EMP2 and compiled all the modules,we should be able to run the lookup function and receive the same results as we did before:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;1&gt; lists:map(fun(N) -&gt; example:lookup(N) end, lists:seq(0, 3)).&lt;br /&gt;[0,2,4,6]&lt;br /&gt;2&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;We shall see.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;[1]&lt;/span&gt; And I cannot have all that competition floating around out there, you know.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1893218858608219953-3211430900325373283?l=chlorophil.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chlorophil.blogspot.com/feeds/3211430900325373283/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://chlorophil.blogspot.com/2007/04/erlang-macro-processor-v2-part-i.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/3211430900325373283'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/3211430900325373283'/><link rel='alternate' type='text/html' href='http://chlorophil.blogspot.com/2007/04/erlang-macro-processor-v2-part-i.html' title='Erlang Macro Processor (v2), Part I'/><author><name>Philip Robinson</name><uri>http://www.blogger.com/profile/17605642659657207381</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp1.blogger.com/_6zQp6LtHMTo/Ridl_kGm_QI/AAAAAAAAACU/dDsAOTFAiW8/s72-c/SunFlowerSmall.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1893218858608219953.post-2761118176189017349</id><published>2007-04-17T01:43:00.000+10:00</published><updated>2007-04-17T22:41:25.235+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><category scheme='http://www.blogger.com/atom/ns#' term='atomiser'/><title type='text'>The Atomiser, Redux</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_6zQp6LtHMTo/RiOR4WFNYYI/AAAAAAAAACM/vurFtgFX80o/s1600-h/At-omiser1.1Small.png"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;" src="http://bp3.blogger.com/_6zQp6LtHMTo/RiOR4WFNYYI/AAAAAAAAACM/vurFtgFX80o/s320/At-omiser1.1Small.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5054043604025958786" /&gt;&lt;/a&gt;I have received some great comments and suggestions regarding the Atomiser; as a result I have added a new (optional) feature to the module.  (Don't worry - The Atomiser may be new and improved, but is still 100% backwardly-compatible!)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;As usual, you may specify a list of globally-valid atoms:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;-atoms([atom1, atom2...]).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;You may now also specify function-specific atom lists in two ways.  The first method is to add a function name (only) to an atoms declaration entry.  The atoms specified will then be valid within all 'fun_name' functions, regardless of the arity of those function definitions:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;-atoms({fun_name, [atom1, atom2...]}).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;(Unfortunately we have to wrap this all information up in a single tuple: 'wild' module attributes can only contain one value.)&lt;br /&gt;&lt;br /&gt;To be even more specific you may add a function name &lt;span style="font-style:italic;"&gt;and&lt;/span&gt; an arity to an atoms declaration.  These atoms will then be valid within that specific 'fun_name/arity' function definition:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;-atoms({fun_name, arity, [atom1, atom2...]}).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Atoms declarations are cumulative: globally-valid atoms (if any) are included along with function and function/arity atoms when checking for valid atoms within a given function definition.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;You might notice that in the code below I have added a few new clauses into the walk_ast function.  I was a bit concerned that I may have missed some node types from the Erlang AST, so I cracked open the only reference I had seen of the &lt;a href="http://www.erlang.org/doc/doc-5.5.4/erts-5.5.4/doc/html/absform.html#4"&gt;Abstract Format&lt;/a&gt; and added a few more function clauses that I had initially overlooked.  I am pretty sure that just about everything is in there now, but feel free to disabuse me of that notion. :-)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Finally, I cleaned up the ?WALK_AST macro a little so that it no longer requires a list of ASTs to process: it now works directly off a single AST.  Removing embedded lists has simplified the use of this macro quite considerably.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The new Atomiser Module:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;-module(atomiser).&lt;br /&gt;-author("Philip Robinson").&lt;br /&gt;-vsn('1.1.1').&lt;br /&gt;-export([parse_transform/2]).&lt;br /&gt;%-compile({parse_transform, atomiser}). % Uncomment after initial compile.&lt;br /&gt;&lt;br /&gt;-atoms([base_dict_key,error, ok]). % Atoms used in four or more functions.&lt;br /&gt;-atoms({atoms_check, 5, [found]}).&lt;br /&gt;-atoms({atoms_unused_print, 1, [found]}).&lt;br /&gt;-atoms({key_more_general, 1, [function]}).&lt;br /&gt;-atoms({parse_transform, 2, [report_warnings,true]}).&lt;br /&gt;-atoms({walk_ast, 3, [atom, atoms, attribute, b_generate, bc, bin, bin_element,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;block, call, 'case', 'catch', char, clause, clauses, cons, eof, float,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'fun', function, generate, 'if', integer, lc, match, nil, op, 'query',&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'receive', record, record_field, string, 'try', tuple, var, warning]}).&lt;br /&gt;&lt;br /&gt;parse_transform(AST, Options) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;DictAtomsAll = dict:store(base_dict_key, dict:new(), dict:new()),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case lists:member(report_warnings, Options) of&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;true -&gt; atoms_unused_print(walk_ast(AST, base_dict_key, DictAtomsAll));&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_ -&gt; ok&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AST.&lt;br /&gt;&lt;br /&gt;dict_with_added_atoms(Line, AtomList, DictInitial) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AddAtom = fun(Atom, Dict) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case dict:find(Atom, Dict) of&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{ok,LineAlreadyDefined} -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;io:format(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"~s:~B Warning: atom '~w' already defined on line ~B.~n",&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[?FILE, Line, Atom, LineAlreadyDefined]),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Dict;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error -&gt; dict:store(Atom, Line, Dict)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lists:foldl(AddAtom, DictInitial, AtomList).&lt;br /&gt;&lt;br /&gt;atoms_from_attr(Line, Key, AtomList, Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Dict = case dict:find(Key, Atoms) of {ok,D} -&gt; D; error -&gt; dict:new() end,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dict:store(Key, dict_with_added_atoms(Line, AtomList, Dict), Atoms).&lt;br /&gt;&lt;br /&gt;atoms_check(Atom, Line, KeyDict, Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case dict:find(KeyDict, Atoms) of&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{ok,Dict} -&gt; atoms_check(Atom, Line, KeyDict, Dict, Atoms);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error -&gt; atoms_check(Atom, Line, key_more_general(KeyDict), Atoms)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end.&lt;br /&gt;&lt;br /&gt;atoms_check(Atom, Line, KeyDict, Dict, Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case dict:find(Atom, Dict) of&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{ok,found} -&gt; Atoms;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{ok,_LineDefinedOn} -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dict:store(KeyDict, dict:store(Atom,found,Dict), Atoms);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case KeyDict of&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;base_dict_key -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;io:format("~s:~B Warning: atom '~w' unexpected.~n",&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[?FILE, Line, Atom]),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Atoms;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_ -&gt; atoms_check(Atom, Line, key_more_general(KeyDict), Atoms)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end.&lt;br /&gt;&lt;br /&gt;key_more_general({function,Fun,_Arity}) -&gt; {function,Fun};&lt;br /&gt;key_more_general({function,_Fun}) -&gt; base_dict_key.&lt;br /&gt;&lt;br /&gt;atoms_unused_print(Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Filter = fun({_Atom,Line}) -&gt; Line =/= found end,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;DictsToList = fun({_DictKey,Dict}, UnusedAtoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;UnusedAtomsNew = lists:filter(Filter, dict:to_list(Dict)),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;UnusedAtomsNewSorted = lists:keysort(2, UnusedAtomsNew),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lists:keymerge(2, UnusedAtomsNewSorted, UnusedAtoms)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;UnusedAtoms = lists:foldl(DictsToList, [], dict:to_list(Atoms)),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;PrintUnusedAtom = fun({Atom,Line}) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;io:format("~s:~B Warning: atom '~w' unused.~n", [?FILE, Line, Atom])&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lists:foreach(PrintUnusedAtom, UnusedAtoms).&lt;br /&gt;&lt;br /&gt;-define(WALK_AST(PatternToMatch, ExpressionsToProcess),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast([PatternToMatch|ASTRest], Key, Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(ASTRest, Key, walk_ast(ExpressionsToProcess, Key, Atoms))).&lt;br /&gt;&lt;br /&gt;walk_ast([], _Key, Atoms) -&gt; Atoms;&lt;br /&gt;walk_ast([{atom,Line,Atom}|RestAST], Key, Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(RestAST, Key, atoms_check(Atom, Line, Key, Atoms));&lt;br /&gt;walk_ast([{attribute,Line,atoms,{Fun,Arity,AtomList}}|RestAST], Key, Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AtomsNew = atoms_from_attr(Line, {function,Fun,Arity}, AtomList, Atoms),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(RestAST, Key, AtomsNew);&lt;br /&gt;walk_ast([{attribute,Line,atoms,{Fun,AtomList}}|RestAST], Key, Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AtomsNew = atoms_from_attr(Line, {function,Fun}, AtomList, Atoms),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(RestAST, Key, AtomsNew);&lt;br /&gt;walk_ast([{attribute,Line,atoms,AtomList}|RestAST], Key, Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AtomsNew = atoms_from_attr(Line, base_dict_key, AtomList, Atoms),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(RestAST, Key, AtomsNew);&lt;br /&gt;?WALK_AST({attribute,_Line,_Name,_Value}, []); % Ignore all other attributes.&lt;br /&gt;?WALK_AST({b_generate,_Line,Pattern,Expression}, [Pattern, Expression]);&lt;br /&gt;?WALK_AST({bc,_Line,Head,Tail}, [Head|Tail]);&lt;br /&gt;?WALK_AST({bin,_Line,BinElements}, BinElements);&lt;br /&gt;?WALK_AST({bin_element,_Line,_Name,_Size,_Type}, []);&lt;br /&gt;?WALK_AST({block,_Line,Expr}, [Expr]);&lt;br /&gt;?WALK_AST({call,_Line,_Fun,Args}, Args); % Handles local and module calls.&lt;br /&gt;?WALK_AST({'case',_Line,Test,Clauses}, [Test|Clauses]);&lt;br /&gt;?WALK_AST({'catch',_Line,Expr}, Expr);&lt;br /&gt;?WALK_AST({char,_Line,_Char}, []);&lt;br /&gt;walk_ast([{clause,_Line,Pattern,Guards,Body}|RestAST], Key, Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AtomsGuard = lists:foldl(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun(ASTGuard, AtomsGuard) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(ASTGuard, Key, AtomsGuard)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(Pattern, Key, Atoms), Guards),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(ASTRest, Key, walk_ast(Body, Key, AtomsGuard));&lt;br /&gt;?WALK_AST({cons,_Line,Left,Right}, [Left,Right]);&lt;br /&gt;?WALK_AST({eof,_Line}, []);&lt;br /&gt;?WALK_AST({error,_Details}, []); % Ignore compiler errors.&lt;br /&gt;?WALK_AST({float,_Line,_Float}, []);&lt;br /&gt;?WALK_AST({'fun',_Line,{clauses,Clauses}}, Clauses);&lt;br /&gt;?WALK_AST({'fun',_Line,_ModuleFunArity}, []);&lt;br /&gt;walk_ast([{function,_Line,Fun,Arity,Clauses}|RestAST], Key, Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(RestAST, Key, walk_ast(Clauses, {function,Fun,Arity}, Atoms));&lt;br /&gt;?WALK_AST({generate,_Line,Pattern,Expression}, [Pattern, Expression]);&lt;br /&gt;?WALK_AST({'if',_Line,Clauses}, Clauses);&lt;br /&gt;?WALK_AST({integer,_Line,_Integer}, []);&lt;br /&gt;?WALK_AST({lc,_Line,Head,Tail}, [Head|Tail]);&lt;br /&gt;?WALK_AST({match,_Line,Pattern,Expression}, [Pattern, Expression]);&lt;br /&gt;?WALK_AST({nil,_Line}, []);&lt;br /&gt;?WALK_AST({op,_Line,_BinaryOp,Left,Right}, [Left, Right]);&lt;br /&gt;?WALK_AST({op,_Line,_UnaryOp,_Operand}, []);&lt;br /&gt;?WALK_AST({'query',_Line,ListComprehension}, [ListComprehension]);&lt;br /&gt;?WALK_AST({'receive',_Line,Clauses}, Clauses);&lt;br /&gt;?WALK_AST({'receive',_Line,Clauses1,_TimeAfter,Clauses2}, Clauses1 ++ Clauses2);&lt;br /&gt;?WALK_AST({record,_Line,_Record,Fields}, Fields);&lt;br /&gt;?WALK_AST({record_field,_Line,Field,Value}, [Field, Value]);&lt;br /&gt;?WALK_AST({record_field,_Line,_Variable,_Record,Field}, [Field]);&lt;br /&gt;?WALK_AST({string,_Line,_String}, []);&lt;br /&gt;?WALK_AST({'try',_Line,Block,CaseClauses,CatchClauses,AfterClauses},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[Block] ++ CaseClauses ++ CatchClauses ++ AfterClauses);&lt;br /&gt;?WALK_AST({tuple,_Line,Elements}, Elements);&lt;br /&gt;?WALK_AST({var,_Line,_Name}, []);&lt;br /&gt;?WALK_AST({warning,_Details}, []); % Ignore compiler warnings.&lt;br /&gt;walk_ast([Node|ASTRest], Key, Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;io:format("Unknown node: ~p~n", [Node]),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(ASTRest, Key, Atoms).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;PS: Does anyone know of an easy way to get Blogger to indent code properly?  I am getting a little tired of pasting loads of "&amp;amp;nbsp;" everywhere...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1893218858608219953-2761118176189017349?l=chlorophil.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chlorophil.blogspot.com/feeds/2761118176189017349/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://chlorophil.blogspot.com/2007/04/atomiser-redux_17.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/2761118176189017349'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/2761118176189017349'/><link rel='alternate' type='text/html' href='http://chlorophil.blogspot.com/2007/04/atomiser-redux_17.html' title='The Atomiser, Redux'/><author><name>Philip Robinson</name><uri>http://www.blogger.com/profile/17605642659657207381</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp3.blogger.com/_6zQp6LtHMTo/RiOR4WFNYYI/AAAAAAAAACM/vurFtgFX80o/s72-c/At-omiser1.1Small.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1893218858608219953.post-8333577788015325587</id><published>2007-04-15T22:23:00.000+10:00</published><updated>2007-04-16T00:49:37.126+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='emp1'/><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><title type='text'>"Dynamic" record access functions with EMP1</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_6zQp6LtHMTo/RiIuM2FNYXI/AAAAAAAAACE/juMaRw5fgHA/s1600-h/EMPProblemSmall.png"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://bp0.blogger.com/_6zQp6LtHMTo/RiIuM2FNYXI/AAAAAAAAACE/juMaRw5fgHA/s320/EMPProblemSmall.png" alt="" id="BLOGGER_PHOTO_ID_5053652530073788786" border="0" /&gt;&lt;/a&gt;Brian Olsen (over at &lt;a href="http://progexpr.blogspot.com/2007/04/simple-dynamic-record-access.html"&gt;Programming Experiments&lt;/a&gt;) wrote a small set of functions to make record accesses/updates in Erlang nicer.  Ayrnieu wrote a detailed response to this in a comment on &lt;a href="http://programming.reddit.com/info/1i147/comments/c1i1kd"&gt;Reddit&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Brian wanted to hide some of the (admittedly pretty ugly) syntax of Erlang records in a simple way. He used some run-time list-searching to find the position in the record tuple that a particular field name occurs at, and then located the desired value at that position.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Now that we have EMP1 working I thought that perhaps I might see how I would use this particular tool to solve the same problem.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;First of all we need to figure out what the functions we want should look like.  I think something like this would do nicely:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;recval(FieldName, Record) -&gt; Value.&lt;br /&gt;setrecval(FieldName, Record, Value) -&gt; Updated Record.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Of course under the covers &lt;span style="font-size:85%;"&gt;recval&lt;/span&gt; and &lt;span style="font-size:85%;"&gt;setrecval&lt;/span&gt; would examine the record given and work out which field to retrieve / update.&lt;br /&gt;&lt;br /&gt;Both Brian and Ayrneiu have this work done at run-time.  With EMP1 we can build the supporting functions at compile-time based on the record information (which is already known at compile-time).&lt;br /&gt;&lt;br /&gt;In detail, &lt;span style="font-size:85%;"&gt;recval&lt;/span&gt; and company would look something like this:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;recval(FieldName, Record) -&gt; recval(element(1, Record), FieldName, Record).&lt;br /&gt;recval(record1, field1, Record) -&gt; element(2, Record);&lt;br /&gt;recval(record1, field2, Record) -&gt; element(3, Record);&lt;br /&gt;recval(record2, field1, Record) -&gt; element(2, Record);&lt;br /&gt;...&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;...and similarly for the &lt;span style="font-size:85%;"&gt;setrecval&lt;/span&gt; versions.&lt;br /&gt;&lt;br /&gt;These functions can all be created at compile-time with EMP1, like this:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;-module(dyrec_macro).&lt;br /&gt;-export([recval_generate/1]).&lt;br /&gt;&lt;br /&gt;recval_field(NameRecord, NameField, Posn) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;io_lib:format("recval(~w, ~w, Record) -&gt; element(~B, Record)",&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[NameRecord, NameField, Posn]).&lt;br /&gt;&lt;br /&gt;setrecval_field(NameRecord, NameField, Posn) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;io_lib:format(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"setrecval(~w, ~w, Record, Value) -&gt; setelement(~B, Record, Value)",&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[NameRecord, NameField, Posn]).&lt;br /&gt;&lt;br /&gt;recval_record(RecordDetails) -&gt; recval_record(RecordDetails, 2, []).&lt;br /&gt;recval_record({_NameRecord, []}, _Posn, Text) -&gt; Text;&lt;br /&gt;recval_record({NameRecord, [NameField|NameFieldsRest]}, Posn, Text) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;recval_record({NameRecord, NameFieldsRest}, Posn + 1,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Text ++ "; " ++ recval_field(NameRecord, NameField, Posn)).&lt;br /&gt;&lt;br /&gt;setrecval_record(RecordDetails) -&gt; setrecval_record(RecordDetails, 2, []).&lt;br /&gt;setrecval_record({_NameRecord, []}, _Posn, Text) -&gt; Text;&lt;br /&gt;setrecval_record({NameRecord, [NameField|NameFieldsRest]}, Posn, Text) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setrecval_record({NameRecord, NameFieldsRest}, Posn + 1,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Text ++ "; " ++ setrecval_field(NameRecord, NameField, Posn)).&lt;br /&gt;&lt;br /&gt;recval_generate(ListRecordDetails) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[$;,32|CodeGet] = lists:flatten(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lists:map(fun(E) -&gt; recval_record(E) end, ListRecordDetails)),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[$;,32|CodeSet] = lists:flatten(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lists:map(fun(E) -&gt; setrecval_record(E) end, ListRecordDetails)),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"recval(Field, Record) -&gt; recval(element(1, Record), Field, Record). "&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"setrecval(Field, Record, Value) -&gt; "&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"setrecval(element(1, Record), Field, Record, Value). " ++&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;io_lib:format("~s. ~s.", [CodeGet, CodeSet]).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;And here is a test program:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;-module(dyrec_test).&lt;br /&gt;-export([start/0]).&lt;br /&gt;-compile({parse_transform, emp1}).&lt;br /&gt;&lt;br /&gt;-record(data1, {this, that}).&lt;br /&gt;-record(data2, {this, the_other}).&lt;br /&gt;&lt;br /&gt;-macro({dyrec_macro, recval_generate,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[[{data1, [this, that]}, {data2, [this, the_other]}]]}).&lt;br /&gt;&lt;br /&gt;start() -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;D1 = #data1{this=a, that=b},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;D2 = #data2{this=c, the_other=d},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;D3 = setrecval(this, D1, e),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;io:format("~p~n~p~n~p~n~p~n~p~n",&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[recval(this, D1), recval(that, D1),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;recval(this, D2), recval(the_other, D2),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;D3]).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;After compiling both of them, we can run this at the REPL:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;1&gt; dyrec_test:start().&lt;br /&gt;a&lt;br /&gt;b&lt;br /&gt;c&lt;br /&gt;d&lt;br /&gt;{data1,e,b}&lt;br /&gt;ok&lt;br /&gt;2&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Personally I would not use EMP1 for this (particular) purpose.  I do not mind Erlang's record syntax, but if I really did not want to use it I would rather build a parse-transformation (a la Yariv's recless module) to convert a different syntax into the record tuples Erlang uses behind the scenes.&lt;br /&gt;&lt;br /&gt;By layering function calls on top of record/tuple field accesses we destroy the ability of Erlang's compiler to convert the usual record syntax into direct tuple &lt;span style="font-size:85%;"&gt;element&lt;/span&gt; lookups at the point of reference.  With this approach the runtime now has to perform a pattern match on record and field names before finding the appropriate value.  (Possibly this overhead could be removed by the use of the compiler's 'inline' option, though.)&lt;br /&gt;&lt;br /&gt;So my verdict on this jaunt into using EMP1 for layering function calls on record accesses, is "certainly possible, but not necessarily practical".  Wait for EMP2 and use that instead. :-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1893218858608219953-8333577788015325587?l=chlorophil.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chlorophil.blogspot.com/feeds/8333577788015325587/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://chlorophil.blogspot.com/2007/04/dynamic-record-access-functions-with.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/8333577788015325587'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/8333577788015325587'/><link rel='alternate' type='text/html' href='http://chlorophil.blogspot.com/2007/04/dynamic-record-access-functions-with.html' title='&quot;Dynamic&quot; record access functions with EMP1'/><author><name>Philip Robinson</name><uri>http://www.blogger.com/profile/17605642659657207381</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp0.blogger.com/_6zQp6LtHMTo/RiIuM2FNYXI/AAAAAAAAACE/juMaRw5fgHA/s72-c/EMPProblemSmall.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1893218858608219953.post-845906212296200844</id><published>2007-04-13T00:40:00.000+10:00</published><updated>2007-04-16T00:51:20.734+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='emp1'/><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><title type='text'>Erlang Macro Processor (v1), Part IV</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_6zQp6LtHMTo/Rh5FS2FNYWI/AAAAAAAAAB8/OPipK6JlSFo/s1600-h/ParseTransformersMoreThanMeetsTheEyeSmall.png"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://bp0.blogger.com/_6zQp6LtHMTo/Rh5FS2FNYWI/AAAAAAAAAB8/OPipK6JlSFo/s320/ParseTransformersMoreThanMeetsTheEyeSmall.png" alt="" id="BLOGGER_PHOTO_ID_5052552022013600098" border="0" /&gt;&lt;/a&gt;We know what we want, we know how we want to use it, and so without further ado, here it is: the code for EMP1.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;-module(emp1).&lt;br /&gt;-author("Philip Robinson").&lt;br /&gt;-vsn('1.0').&lt;br /&gt;-export([parse_transform/2]).&lt;br /&gt;&lt;br /&gt;parse_transform(ASTIn, _Options) -&gt; walk_ast(ASTIn, []).&lt;br /&gt;&lt;br /&gt;walk_ast([], ASTOut) -&gt; lists:reverse(ASTOut);&lt;br /&gt;walk_ast([{attribute,Line,macro,{Mod,Fun,Args}}|RestASTIn], ASTOut) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ReversedResults =&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ast_reversed_results(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lists:flatten([apply(Mod,Fun,Args)|[" "]]),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Line, []),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(RestASTIn, ReversedResults ++ ASTOut);&lt;br /&gt;walk_ast([Node|ASTInRest], ASTOut) -&gt; walk_ast(ASTInRest, [Node|ASTOut]).&lt;br /&gt;&lt;br /&gt;ast_reversed_results(ResultsString, LineStart, ASTResults) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case string_trim_whitespace(ResultsString) of&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"" -&gt; ASTResults;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{done,{ok,Tokens,LineEnd},StringRest} =&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;erl_scan:tokens([], String, LineStart),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{ok, AST} = erl_parse:parse_form(Tokens),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ast_reversed_results(StringRest, LineEnd, [AST|ASTResults])&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end.&lt;br /&gt;&lt;br /&gt;string_trim_whitespace([ 9|String]) -&gt; string_trim_whitespace(String);&lt;br /&gt;string_trim_whitespace([10|String]) -&gt; string_trim_whitespace(String);&lt;br /&gt;string_trim_whitespace([32|String]) -&gt; string_trim_whitespace(String);&lt;br /&gt;string_trim_whitespace(    String ) -&gt; String.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;And that is it - 30 lines of code.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;No, really.   That is all there is.   I can take you through it in some detail, if you want.  Fasten your seat-belts.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;EMP1 In Detail&lt;br /&gt;&lt;br /&gt;I will start with the "mostest ugliest" piece of Erlang code I have ever written: the string_trim_whitespace function.&lt;br /&gt;&lt;br /&gt;This function returns the given string minus any leading tabs, carriage returns, or spaces.   I searched the Erlang documentation and the &lt;a href="http://www.trapexit.org/"&gt;Trap Exit&lt;/a&gt; website but I did not manage to find any built-in functions that achieved the same goal.   Four lines of code seems a bit excessive for what it actually does and I am sure there must be a nicer way of writing it.&lt;br /&gt;&lt;br /&gt;This function is actually a reasonably good example of Erlang pattern-matching and tail-recursion at work.   If the given string begins with a tab (ASCII 9), carriage return (ASCII 10), or a space (ASCII 32), then it will match one of the first three function clauses.   The first character will be dropped and the function recursively called with the rest of the string.&lt;br /&gt;&lt;br /&gt;If the string does &lt;span style="font-weight: bold;"&gt;not&lt;/span&gt; match any of those three function clauses then it must not have a tab, carriage return, or space at the beginning, so the given string is returned as-is.   This even works for the empty string.   (Technically it would also match any non-string argument - integer, float, tuple, or whatever - and just return the input given.)&lt;br /&gt;&lt;br /&gt;Even though the function uses recursion there is no danger of the stack blowing out no matter how large the string is.   Erlang (like most functional languages) has a neat trick of turning &lt;a href="http://en.wikipedia.org/wiki/Tail_recursion"&gt;tail recursion&lt;/a&gt; calls into a goto loop so the function executes in constant memory space.   Others have explained tail-recursion better than I can, so let's move on...&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Next on the list is the walk_ast function, which runs through the top level of the inbound AST and builds an outbound AST.   The outbound AST list is built in reverse order to take advantage of the cheap list 'cons' operation: it is very quick to add or remove an element at the beginning of a list but much more expensive to add or remove an element at the end of a list.   When the whole inbound AST has been processed (the argument matches the empty list) then the outbound AST is run through the &lt;span style="font-size:85%;"&gt;lists:reverse&lt;/span&gt; function to switch it back to the right-way-around order again.   If you are not yet familiar with this build-in-reverse-then-switch idiom, you soon will be. :-)&lt;br /&gt;&lt;br /&gt;There are only three function clauses in this walk_ast function: The final case where we reverse and return the new AST, processing a 'macro' module attribute, and everything else.&lt;br /&gt;&lt;br /&gt;The 'final' case I have covered above, and the 'everything else' case just passes the node straight to the outbound AST.   The magic of EMP1 happens in the macro module attribute clause.&lt;br /&gt;&lt;br /&gt;The walk_ast function looks for macro atributes of this form:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;-macro({Module, Function, Args}).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;When it finds a match it calls the module/function with the given args and captures the result, which should be a string representation of an Erlang function.   It adds this string to the beginning of a list containing a single space and then flattens the total result.&lt;br /&gt;&lt;br /&gt;A space is added to the end of the return string because &lt;span style="font-size:85%;"&gt;erl_scan:tokens&lt;/span&gt; has a problem parsing something like "42." - it cannot tell if this is the beginning of a floating-point number.   To avoid this I add a space to the end of the string; &lt;span style="font-size:85%;"&gt;erl_scan:tokens&lt;/span&gt; knows that "42. " is just the integer 42.&lt;br /&gt;&lt;br /&gt;The resulting string is also flattened because &lt;span style="font-size:85%;"&gt;io_lib:format&lt;/span&gt; does some funny things when you use &lt;span style="font-size:85%;"&gt;"~s"&lt;/span&gt; to embed a value string into a format string.  For example, &lt;span style="font-size:85%;"&gt;io_lib:format("ab~se", ["cd"])&lt;/span&gt; produces &lt;span style="font-size:85%;"&gt;[97,98,"cd",101]&lt;/span&gt; instead of an expected (in my opinion) &lt;span style="font-size:85%;"&gt;"abcde"&lt;/span&gt;.   This might be okay for printing, which I presume flattens its input as it goes, but this is a terrible format for erl_scan to tokenise.&lt;br /&gt;&lt;br /&gt;Once the macro's return string has been mutilated enough it is passed on to ast_reversed_results, for some further mangling.&lt;br /&gt;&lt;br /&gt;The ast_reversed_results function does pretty much all the heavy lifting for the module.  It takes in the current result string (a flattened text representation of one or more Erlang functions with a space at the end), the line the module attribute was declared, and the current AST list of processed results (in reversed order as per the functional programming idiom mentioned above).&lt;br /&gt;&lt;br /&gt;The very first thing this function does is to strip leading whitespace characters from the input string, and test that result against the empty string.&lt;br /&gt;&lt;br /&gt;For some reason &lt;span style="font-size:85%;"&gt;erl_scan:tokens&lt;/span&gt; returns a &lt;span style="font-size:85%;"&gt;{more, SomeWeirdStuff}&lt;/span&gt; tuple when it is handed a string of whitespace characters (and also when given the empty string).  I have no idea what I should do with this result so I strip the leading whitespace characters out and test for the empty string instead.&lt;br /&gt;&lt;br /&gt;If the stripped string is not empty then we want to tokenise and parse the first form (which should be a function definition), add the parsed results to the beginning of our AST list, and try again with the rest of the string (as it is possible to include more than one function definition in the macro return string).&lt;br /&gt;&lt;br /&gt;If the stripped string is empty then there is nothing left to process and we can return the (reversed) AST of result.  We keep these in reversed order because it is just pre-pended to the walk_ast's ASTOut, and it will all be re-reversed at the end.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Whew!&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;EMP1 Epilogue and Notes&lt;br /&gt;&lt;br /&gt;* An interesting 'feature' of EMP1 is that it may be used to create functions where the function name is programmatically generated.  I am not sure why you might choose to create a whole bunch of separate, named functions over, say, creating one function with multiple clauses triggered by an atom argument, but EMP1 certainly makes it possible.&lt;br /&gt;&lt;br /&gt;* I would recommend avoiding carriage returns in macro output strings.  It does not actually break anything, but it tends to obfuscate the stack trace output of any runtime exceptions thrown from the generated code.&lt;br /&gt;&lt;br /&gt;* One advantage of compile-time macros over run-time function-building techniques is that the usual compiler checks are run over the generated code.  (The macro-created code is actually there at compile-time rather than appearing later at run-time.)   I like to get my bug reports early, and if the compiler can complain then I don't need to wait for unit tests to raise an issue.&lt;br /&gt;&lt;br /&gt;Using compile-time macros also means that static code analysis tools such as the Dialyzer will include the generated functions in its analysis and report.&lt;br /&gt;&lt;br /&gt;There are, however, situations where not all of the information needed to create a function is available at compile-time.  If you find yourself in such a predicament you might want to check out &lt;a href="http://yarivsblog.com/"&gt;Yariv's&lt;/a&gt; smerl project, which makes it a lot easier to do runtime meta-programming.&lt;br /&gt;&lt;br /&gt;I might need to use smerl when I write EMP2.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1893218858608219953-845906212296200844?l=chlorophil.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chlorophil.blogspot.com/feeds/845906212296200844/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://chlorophil.blogspot.com/2007/04/erlang-macro-processor-v1-part-iv.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/845906212296200844'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/845906212296200844'/><link rel='alternate' type='text/html' href='http://chlorophil.blogspot.com/2007/04/erlang-macro-processor-v1-part-iv.html' title='Erlang Macro Processor (v1), Part IV'/><author><name>Philip Robinson</name><uri>http://www.blogger.com/profile/17605642659657207381</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp0.blogger.com/_6zQp6LtHMTo/Rh5FS2FNYWI/AAAAAAAAAB8/OPipK6JlSFo/s72-c/ParseTransformersMoreThanMeetsTheEyeSmall.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1893218858608219953.post-1312617857737476596</id><published>2007-04-12T22:30:00.000+10:00</published><updated>2007-04-18T10:46:34.300+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='emp1'/><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><title type='text'>Erlang Macro Processor (v1), Part III</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_6zQp6LtHMTo/Rh44cmFNYVI/AAAAAAAAAB0/SMs0ZFLy-rw/s1600-h/HowDoIUseItSmall.png"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://bp3.blogger.com/_6zQp6LtHMTo/Rh44cmFNYVI/AAAAAAAAAB0/SMs0ZFLy-rw/s320/HowDoIUseItSmall.png" alt="" id="BLOGGER_PHOTO_ID_5052537895866163538" border="0" /&gt;&lt;/a&gt;&lt;span style="font-size:100%;"&gt;EMP1 in Action&lt;br /&gt;(How will I use this thing?)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;A macro function must already be compiled before EMP1 can call it, so I will probably have to create an "example_macro.erl" module for each "example.erl" module, as needed.&lt;br /&gt;&lt;br /&gt;The macro modules will simply export functions that return strings.  Ideally the returned strings will be valid Erlang functions... if they are not valid Erlang functions then we will get to watch the pretty fireworks from the compiler. :-)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;To generate a lookup function I might have a macro module something like this:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;-module(example_macro).&lt;br /&gt;-export([lookup_generate/0]).&lt;br /&gt;&lt;br /&gt;lookup_generate() -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;io_lib:format(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"lookup(Offset) -&gt; "&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"&lt;&lt;_:offset/binary,Value:8/integer,_/binary&gt;&gt; = &lt;&lt;~s&gt;&gt;, "&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"Value.",&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[lookup_table()]).&lt;br /&gt;&lt;br /&gt;lookup_table() -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[[$,,FirstVal]|NumberString] =&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lists:map(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun(Offset) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;io_lib:format(",~B", [Offset * 2])&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lists:seq(0, 3)),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lists:flatten([FirstVal|NumberString]).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;In this example the lookup table generated has only four elements; each element's value in the table is just its offset multiplied by two.&lt;br /&gt;&lt;br /&gt;If we were to compile and run &lt;span style="font-size:85%;"&gt;example_macro:lookup_generate()&lt;/span&gt; directly we would see a whole string of numbers... to actually see what was produced in a readable format we will want to do something like this:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;1&gt; io:format("~s~n", [lists:flatten(example_macro:lookup_generate())]).&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;...actually, we probably want to do this instead, to save typing:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;1&gt; M = fun(R) -&gt; io:format("~s~n", [lists:flatten(R)]) end.&lt;br /&gt;#Fun&lt;br /&gt;2&gt; M(example_macro:lookup_generate()).&lt;br /&gt;lookup(Offset) -&gt; &lt;&lt;_offset/binary,value:8/integer,_/binary&gt;&gt; = &lt;&lt;0,2,4,6&gt;&gt;, Value.&lt;br /&gt;ok&lt;br /&gt;3&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Hey, that even looks like a valid function... not bad as a proof of concept, but I really hope no-one uses a macro for a table that small!&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Here is the module that I want this lookup function to be created in:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;-module(example).&lt;br /&gt;-compile({parse_transform, emp1}).&lt;br /&gt;-export([lookup/1]).&lt;br /&gt;&lt;br /&gt;-macro({example_macro, lookup_generate, []}).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Note that the export directive is referring to a function that does not actually exist in the source file.  This function will be generated at compile-time by EMP1 calling example_macro:lookup_generate() and inserting the parsed results into the compiled code.&lt;br /&gt;&lt;br /&gt;We cannot compile this file just yet because we have not implemented EMP1, but hopefully we will soon be able to do this:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;1&gt; lists:map(fun(N) -&gt; example:lookup(N) end, lists:seq(0, 3)).&lt;br /&gt;[0,2,4,6]&lt;br /&gt;2&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Hopefully...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1893218858608219953-1312617857737476596?l=chlorophil.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chlorophil.blogspot.com/feeds/1312617857737476596/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://chlorophil.blogspot.com/2007/04/erlang-macro-processor-v1-part-iii.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/1312617857737476596'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/1312617857737476596'/><link rel='alternate' type='text/html' href='http://chlorophil.blogspot.com/2007/04/erlang-macro-processor-v1-part-iii.html' title='Erlang Macro Processor (v1), Part III'/><author><name>Philip Robinson</name><uri>http://www.blogger.com/profile/17605642659657207381</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp3.blogger.com/_6zQp6LtHMTo/Rh44cmFNYVI/AAAAAAAAAB0/SMs0ZFLy-rw/s72-c/HowDoIUseItSmall.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1893218858608219953.post-1614800760234562791</id><published>2007-04-12T22:16:00.000+10:00</published><updated>2007-04-12T22:29:29.896+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='emp1'/><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><title type='text'>Erlang Macro Processor (v1), Part II</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_6zQp6LtHMTo/Rh4ji2FNYUI/AAAAAAAAABs/4-c0GYGKmPw/s1600-h/FeaturesSmall.png"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://bp0.blogger.com/_6zQp6LtHMTo/Rh4ji2FNYUI/AAAAAAAAABs/4-c0GYGKmPw/s320/FeaturesSmall.png" alt="" id="BLOGGER_PHOTO_ID_5052514913496162626" border="0" /&gt;&lt;/a&gt;&lt;span style="font-weight: bold;"&gt;&lt;/span&gt;I will have to be quite stingy with doling out feature requests if I want to keep this parse_transform module as small - and simple - as possible.   EMP2 may be able to guzzle from the feature fountain; EMP1 will be on restricted rations.&lt;br /&gt;&lt;br /&gt;I really only need EMP1 for a single specific purpose: to create a binary table in a lookup function at compile-time.   Anything extra it can do will be icing on the cake.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;There is a bare minimum that EMP1 needs to do in order to qualify as a compile-time macro processor:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Walk the AST of a given module.&lt;/li&gt;&lt;li&gt;On encountering a macro 'trigger', run the designated macro function and retrieve its output.&lt;/li&gt;&lt;li&gt;Parse the macro function's output and convert it into an AST of the Erlang term/s.&lt;/li&gt;&lt;li&gt;Replace the macro trigger in the original AST with the AST of the function's output.&lt;/li&gt;&lt;/ol&gt;So how can we achieve this with a minimum of effort?&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;We already know how to walk the AST of a module from our work on the Atomiser, so this part should not be a problem.&lt;br /&gt;&lt;br /&gt;The simplest macro 'trigger' I can think of is having '-macro' module attributes contain a {module,function,args} tuple value.   These attributes are easy enough to find, and we can use the Erlang 'apply' function to call an arbitrary function.&lt;br /&gt;&lt;br /&gt;By only using module attributes as macro triggers the EMP1 module only has to look for (and replace) nodes at the top level of the given AST.   EMP1 does not have to walk through (or even know about) any other nodes, so almost all of the walk_ast function clauses that had to be included in the Atomiser can be left out of this module.&lt;br /&gt;&lt;br /&gt;The easiest way to implement macro functions is probably for them to return a textual representation of the function that we want them to create.   This will actually make it much easier to debug the macros as well: we will be able to print the output and see if it really is the function we are after.&lt;br /&gt;&lt;br /&gt;Replacing a macro trigger module attribute node in an AST is as simple as prepending a different node to the outbound AST in the walk_ast function.&lt;br /&gt;&lt;br /&gt;The only tricky bit with all of this would be parsing the textual representation of an Erlang term and converting it into an AST, but I am sure there will be something available for this purpose.   I have seen some erl_scan and erl_parse modules floating around in the documentation - they or something similar should do.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;On the other hand, there are a few caveats that I can think of with implementing EMP1 this way.&lt;br /&gt;&lt;br /&gt;As EMP1 is a parse_transform module, it would not be possible to use it to define a standard Erlang substitution macro (using the '-define' keyword).   Normal Erlang macros are already expanded into the AST before EMP1 receives it; as far as I am aware there there is no such thing as an AST for a substitution macro.   I am not sure if there is anything that can be done about this, unless there is some way of hooking directly into the Erlang preprocessor.&lt;br /&gt;&lt;br /&gt;The biggest limitation with using module attributes as the trigger to call a macro is that EMP1 will only be able to build whole functions at a time.   Module attributes [only] live at the top level of the AST, and you cannot just insert any old Erlang term up there.&lt;br /&gt;&lt;br /&gt;For example, to create a binary lookup table in a function a macro will have to return the whole function with the table embedded rather than just the table by itself.   In my case the lookup function will be quite small anyway, so this does not bother me overly much.&lt;br /&gt;&lt;br /&gt;Finally, EMP1 will be executing code while the module is being compiled.  Any macro code that is to be run must be in a separate (and already compiled) module, which will introduce a dependency in the order in which certain modules can be compiled.&lt;br /&gt;&lt;br /&gt;Do any of these limitations worry me?&lt;br /&gt;&lt;br /&gt;Hell no!&lt;br /&gt;&lt;br /&gt;If EMP1 can achieve the modest goals outlined above then I will consider this project a resounding success.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1893218858608219953-1614800760234562791?l=chlorophil.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chlorophil.blogspot.com/feeds/1614800760234562791/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://chlorophil.blogspot.com/2007/04/erlang-macro-processor-v1-part-ii.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/1614800760234562791'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/1614800760234562791'/><link rel='alternate' type='text/html' href='http://chlorophil.blogspot.com/2007/04/erlang-macro-processor-v1-part-ii.html' title='Erlang Macro Processor (v1), Part II'/><author><name>Philip Robinson</name><uri>http://www.blogger.com/profile/17605642659657207381</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp0.blogger.com/_6zQp6LtHMTo/Rh4ji2FNYUI/AAAAAAAAABs/4-c0GYGKmPw/s72-c/FeaturesSmall.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1893218858608219953.post-8085854904570291699</id><published>2007-04-11T23:09:00.000+10:00</published><updated>2007-04-12T00:12:05.388+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='emp1'/><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><title type='text'>Erlang Macro Processor (v1), Part I</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_6zQp6LtHMTo/RhzsDWFNYTI/AAAAAAAAABk/lCsiCc6ZRZE/s1600-h/MacroManSmall.png"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://bp0.blogger.com/_6zQp6LtHMTo/RhzsDWFNYTI/AAAAAAAAABk/lCsiCc6ZRZE/s320/MacroManSmall.png" alt="" id="BLOGGER_PHOTO_ID_5052172424214044978" border="0" /&gt;&lt;/a&gt;Lisp macros.   &lt;span style="font-style: italic;"&gt;Sigh.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The power at your disposal when you can write code to generate code is... truly awesome.&lt;br /&gt;&lt;br /&gt;Unfortunately (as far as my macrophilia is concerned) I happen to prefer the open-source Erlang/OTP implementation far more than any Lisp I have encountered.&lt;br /&gt;&lt;br /&gt;I had sullenly resigned myself to a life filled with mere substitution macros (cue the violins, please) when, like an oyster presented with a piece of grit, I was faced with an irritating problem.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;It was a dark and moonless night.   Quietly the function gained mass as the the minutes inched past midnight and into the early dawn.   Its time-consuming mathematical calculation greedily drained CPU cycles every time it was run, and finally, a lightning-quick strike of doom: a simple call vampirically inserted into an inner loop cemented the function's status as the program's major bottleneck.   You could almost hear the evil cackle mocking the developer's feeble resistance.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Naturally the first thing I attempted as a cure to this disease was a bit of &lt;a href="http://en.wikipedia.org/wiki/Memoization"&gt;memoization&lt;/a&gt; to cache this function's results.   Unfortunately this approach actually had the exact &lt;span style="font-style: italic;"&gt;opposite&lt;/span&gt; effect to the one I intended...   I suspect that the sheer number of key/value pairs held was causing the dictionary to swap to disk; there was an awful lot of hard drive thrashing going on after I made that change.&lt;br /&gt;&lt;br /&gt;The additional fact that this routine had a known, contiguous, sequential input range bugged me too.   Why should I need to put up with all of the indexing overhead with storing a dictionary of keys and values, when I only really needed a binary array of values to access by (key) offset?&lt;br /&gt;&lt;br /&gt;The next obvious step would have been to create a start function to initialise a binary table with the needed results.   I did not like this option very much, though, mainly because I would have had to manage the initialised table by either&lt;br /&gt;&lt;ul&gt;&lt;li&gt;    passing the binary as an argument to every function that needs it (or calls a function that needs it, ad infinitum), or&lt;br /&gt;&lt;/li&gt;&lt;li&gt;storing the binary in the process dictionary (a measure of last resort in a functional program), or&lt;/li&gt;&lt;li&gt;creating a separate process to manage the binary and respond to message requests for its contents, a far more complicated solution than I desired.&lt;/li&gt;&lt;/ul&gt;Another minor consideration was that an initialisation routine would slow down the beginning of my program every time it was run, even if I was only going to run the program on a small range of test values.   This might not be so important during production (barring crash/restart situations), but it can definitely slow down an iterative development cycle.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;What I really, &lt;span style="font-style: italic;"&gt;really&lt;/span&gt; wanted to do was create the lookup binary at compile-time and embed it directly in the calculation function as a literal term.   Just the sort of thing I would have done automatically in Lisp, without it even registering as a problem.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;"Now," the developer says out loud, scratching his head so the audience knows that he is deep in thought, "What can I do to examine the Abstract Syntax Tree of a program and replace portions of it with new code?"&lt;br /&gt;&lt;br /&gt;(cue fanfare)&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;"This is a job for parse_transform!"&lt;/span&gt;&lt;span style="font-size:100%;"&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="font-size:78%;"&gt;(&lt;a href="http://www.erlang.org/doc/doc-5.5.4/lib/stdlib-1.14.4/doc/html/erl_id_trans.html"&gt;Programmers are strongly advised not to engage in parse transformations and no support is offered for problems encountered.&lt;/a&gt;)&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1893218858608219953-8085854904570291699?l=chlorophil.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chlorophil.blogspot.com/feeds/8085854904570291699/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://chlorophil.blogspot.com/2007/04/erlang-macro-processor-v1-part-i.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/8085854904570291699'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/8085854904570291699'/><link rel='alternate' type='text/html' href='http://chlorophil.blogspot.com/2007/04/erlang-macro-processor-v1-part-i.html' title='Erlang Macro Processor (v1), Part I'/><author><name>Philip Robinson</name><uri>http://www.blogger.com/profile/17605642659657207381</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp0.blogger.com/_6zQp6LtHMTo/RhzsDWFNYTI/AAAAAAAAABk/lCsiCc6ZRZE/s72-c/MacroManSmall.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1893218858608219953.post-2250215283500779265</id><published>2007-04-08T23:55:00.000+10:00</published><updated>2007-04-10T01:31:48.466+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><category scheme='http://www.blogger.com/atom/ns#' term='atomiser'/><title type='text'>The Atomiser, Part VII</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp2.blogger.com/_6zQp6LtHMTo/RhkHWYh2FpI/AAAAAAAAABU/bXj_U1YbnSw/s1600-h/At-omiserSmall.png"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://bp2.blogger.com/_6zQp6LtHMTo/RhkHWYh2FpI/AAAAAAAAABU/bXj_U1YbnSw/s320/At-omiserSmall.png" alt="" id="BLOGGER_PHOTO_ID_5051076538195646098" border="0" /&gt;&lt;/a&gt;As promised, here is the full listing of my current atomiser.erl file:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;-module(atomiser).&lt;br /&gt;-author("Philip Robinson").&lt;br /&gt;-export([parse_transform/2]).&lt;br /&gt;%-compile({parse_transform, atomiser}). % Uncomment after initial compile.&lt;br /&gt;&lt;br /&gt;-atoms([atom, attribute, bin, bin_element, call, 'case', char]).&lt;br /&gt;-atoms([clause, clauses, cons, eof, 'fun', function, generate]).&lt;br /&gt;-atoms(['if', integer, lc, match, nil, op, 'receive', record]).&lt;br /&gt;-atoms([record_field, remote, string, tuple, var]).&lt;br /&gt;-atoms([atoms, error, found, ok]).&lt;br /&gt;&lt;br /&gt;parse_transform(AST, _Options) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;atoms_unused_print(walk_ast(AST, dict:new())),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AST.&lt;br /&gt;&lt;br /&gt;atoms_from_attribute(Line, AtomList, Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AddAtom = fun(Atom, Dict) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case dict:find(Atom, Dict) of&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{ok, LineAlreadyDefined} -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;io:format("Line ~B: Atom ~w already defined on line ~B.~n",&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[Line, Atom, LineAlreadyDefined]),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Dict;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error -&gt; dict:store(Atom, Line, Dict)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lists:foldl(AddAtom, Atoms, AtomList).&lt;br /&gt;&lt;br /&gt;atom_check(Atom, Line, Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case dict:find(Atom, Atoms) of&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{ok, found} -&gt; Atoms;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{ok, _LineDefinedOn} -&gt; dict:store(Atom, found, Atoms);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;io:format("Line ~B: Atom ~w unexpected.~n", [Line, Atom]),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Atoms&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end.&lt;br /&gt;&lt;br /&gt;atoms_unused_print(Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Filter = fun({_Atom, FoundOrDefinedLine}) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;FoundOrDefinedLine =/= found&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;PrintUnusedAtom = fun({Atom, Line}) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;io:format("Line ~B: Atom ~w unused.~n", [Line, Atom])&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lists:foreach(PrintUnusedAtom,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lists:keysort(2, lists:filter(Filter, dict:to_list(Atoms)))).&lt;br /&gt;&lt;br /&gt;-define(WALK_AST(Pattern, Expressions),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast([Pattern|ASTRest], Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Fun = fun(AST, AtomsMarked) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(AST, AtomsMarked)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(ASTRest, lists:foldl(Fun, Atoms, Expressions))).&lt;br /&gt;&lt;br /&gt;walk_ast([], Atoms) -&gt; Atoms;&lt;br /&gt;walk_ast([{atom,Line,Atom}|RestAST], Atoms) -&gt; % Check whether atom is valid.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(RestAST, atom_check(Atom, Line, Atoms));&lt;br /&gt;walk_ast([{attribute,Line,atoms,AtomList}|RestAST], Atoms) -&gt; % Valid atoms.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(RestAST, atoms_from_attribute(Line, AtomList, Atoms));&lt;br /&gt;?WALK_AST({attribute,_Line,_Name,_Value}, []);&lt;br /&gt;?WALK_AST({bin,_Line,Elements}, [Elements]);&lt;br /&gt;?WALK_AST({bin_element,_Line,_Name,_Size,_Type}, []);&lt;br /&gt;?WALK_AST({call,_Line,_Fun,Args}, [Args]);&lt;br /&gt;?WALK_AST({'case',_Line,Test,Clauses}, [[Test], Clauses]);&lt;br /&gt;?WALK_AST({char,_Line,_Char}, []);&lt;br /&gt;?WALK_AST({clause,_Line,Args,Guards,Exprs}, [Args] ++ Guards ++ [Exprs]);&lt;br /&gt;?WALK_AST({cons,_Line,Head,Tail}, [[Head], [Tail]]);&lt;br /&gt;?WALK_AST({eof,_Line}, []);&lt;br /&gt;?WALK_AST({error,_Details}, []); % Ignore compiler errors.&lt;br /&gt;?WALK_AST({'fun',_Line,{clauses,Clauses}}, [Clauses]);&lt;br /&gt;?WALK_AST({function,_Line,_Fun,_Arity,Clauses}, [Clauses]);&lt;br /&gt;?WALK_AST({generate,_Line,A,B}, [[A, B]]);&lt;br /&gt;?WALK_AST({'if',_Line,Clauses}, [Clauses]);&lt;br /&gt;?WALK_AST({integer,_Line,_Integer}, []);&lt;br /&gt;?WALK_AST({lc,_Line,Head,Tail}, [[Head|Tail]]);&lt;br /&gt;?WALK_AST({match,_Line,Left,Right}, [[Left], [Right]]);&lt;br /&gt;?WALK_AST({nil,_Line}, []);&lt;br /&gt;?WALK_AST({op,_Line,_BinaryOperator,Left,Right}, [[Left], [Right]]);&lt;br /&gt;?WALK_AST({op,_Line,_UnaryOperator,_Operand}, []);&lt;br /&gt;?WALK_AST({'receive',_Line,Clauses}, [Clauses]);&lt;br /&gt;?WALK_AST({'receive',_Line,Clauses1,_TimeAfter,Clauses2}, [Clauses1, Clauses2]);&lt;br /&gt;?WALK_AST({record,_Line,_Record,Fields}, [Fields]);&lt;br /&gt;?WALK_AST({record_field,_Line,Field,Contents}, [[Field,Contents]]);&lt;br /&gt;?WALK_AST({record_field,_Line,_Variable,_Record,Field}, [[Field]]);&lt;br /&gt;?WALK_AST({remote,_Line,_Module,_Function}, []);&lt;br /&gt;?WALK_AST({string,_Line,_String}, []);&lt;br /&gt;?WALK_AST({tuple,_Line,Elements}, [Elements]);&lt;br /&gt;?WALK_AST({var,_Line,_Name}, []);&lt;br /&gt;walk_ast([Node|ASTRest], Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;io:format("Unknown node: ~p~n", [Node]),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(ASTRest, Atoms).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Some final notes, in no particular order:&lt;br /&gt;&lt;br /&gt;I am quite pleased with the functionality of the Atomiser, especially considering that it currently weighs in at just under 100 lines of code.  I can honestly attribute the relatively small size of this module to the use of the single WALK_AST substitution macro.  If this macro had not been used then we would be looking at an increase of 50% in lines of code, at least.&lt;br /&gt;&lt;br /&gt;The fact that the Atomiser does not alter the parse tree of the program it is examining made it an ideal project to get used to working with Erlang parse_transform programs.  Without support for parse_transform modules I would have had to hack at the source code of the compiler to achieve a similar result... which is not really a viable option if the addition is not accepted into the project.&lt;br /&gt;&lt;br /&gt;Obviously in this implementation I have only added walk_ast function clauses for those AST nodes my own programs require; your mileage may vary.  If you do run this module over your own code and an unknown node appears then please let me know so I can add an appropriate clause here.  Likewise, please feel free to drop me a line with questions, comments, and/or (especially!) suggestions.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Update 10/4/2007:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://yarivsblog.com/"&gt;Yariv Sadan&lt;/a&gt; suggested that I take a look at Recless, one of his many ongoing projects (see comments in &lt;a href="http://chlorophil.blogspot.com/2007/04/atomiser-part-i.html"&gt;Part 1&lt;/a&gt;).  As Yariv did in Recless, I have removed the atoms_from_ast function by rolling the gathering of atoms into the walk_ast function.  It saves five lines of code, but more importantly it saves one pass through the top level of the AST.  (The down side is that you can no longer expect the Atomiser to validate atoms before the appropriate module attribute is encountered, but I have no problem with that.)&lt;br /&gt;&lt;br /&gt;"ayrnieu" posted a link to this series on &lt;a href="http://programming.reddit.com/info/1gc3p/comments"&gt;Reddit&lt;/a&gt;, and also mentioned the Dialyzer tool.  I have just played with the Dialyzer and have only one thing to say: &lt;span style="font-style:italic;"&gt;Use the Dialyzer on your code&lt;/span&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1893218858608219953-2250215283500779265?l=chlorophil.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chlorophil.blogspot.com/feeds/2250215283500779265/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://chlorophil.blogspot.com/2007/04/atomiser-part-vii.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/2250215283500779265'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/2250215283500779265'/><link rel='alternate' type='text/html' href='http://chlorophil.blogspot.com/2007/04/atomiser-part-vii.html' title='The Atomiser, Part VII'/><author><name>Philip Robinson</name><uri>http://www.blogger.com/profile/17605642659657207381</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp2.blogger.com/_6zQp6LtHMTo/RhkHWYh2FpI/AAAAAAAAABU/bXj_U1YbnSw/s72-c/At-omiserSmall.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1893218858608219953.post-4480694431785321481</id><published>2007-04-08T23:14:00.000+10:00</published><updated>2007-04-08T23:55:13.056+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><category scheme='http://www.blogger.com/atom/ns#' term='atomiser'/><title type='text'>The Atomiser, Part VI</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp2.blogger.com/_6zQp6LtHMTo/Rhjzloh2FoI/AAAAAAAAABM/L_7Dee82p0Y/s1600-h/HelloNotUsedSmall.png"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;" src="http://bp2.blogger.com/_6zQp6LtHMTo/Rhjzloh2FoI/AAAAAAAAABM/L_7Dee82p0Y/s320/HelloNotUsedSmall.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5051054809956095618" /&gt;&lt;/a&gt;We still have one (optional) requirement left for the Atomiser:&lt;br /&gt;&lt;br /&gt;* Have the Erlang compiler tell us if an atom in the valid list is not used.&lt;br /&gt;&lt;br /&gt;This can be done quite easily: the atom_check function returns an updated dictionary whenever an atom has been found.  All we have to do is go through the valid atoms dictionary and pick up all the atom keys that do not have the value 'found' associated with them, and print them out in line-number order.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;atoms_unused_print(Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Filter = fun({_Atom, FoundOrDefinedLine}) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;FoundOrDefinedLine =/= found&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AtomsUnused = lists:keysort(2, lists:filter(Filter, dict:to_list(Atoms))),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;PrintUnusedAtom = fun({Atom, Line}) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;io:format("Line ~B: Atom ~w unused.~n", [Line, Atom])&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lists:foreach(PrintUnusedAtom, AtomsUnused).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Incidentally, another requirement raised its ugly head during the development of this module.  If you ever use the Atomiser when compiling an invalid program, you will receive a horrible 'error' node in the AST.  The Atomiser is not interested in these nodes (they will stop the compile process later anyway) and so we just want to ignore them.&lt;br /&gt;&lt;br /&gt;A new specialised function clause for walk_ast takes care of this:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;?WALK_AST({error,_Details}, []);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Believe it or not, the Atomiser is now pretty much complete.&lt;br /&gt;&lt;br /&gt;If you have managed to stay with me so far on this meandering journey, you will probably have noticed that there are a few more minor details we need to clean up before we are finished.  The missing pieces are just a few more atoms that need to be identified in the atoms module attributes, and a couple of extra function clauses that need to be added walk_ast.  I will leave both of these as an exercise for any impatient readers that cannot wait until the next post, when I will list the entire module in all its naked glory.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1893218858608219953-4480694431785321481?l=chlorophil.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chlorophil.blogspot.com/feeds/4480694431785321481/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://chlorophil.blogspot.com/2007/04/atomiser-part-vi.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/4480694431785321481'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/4480694431785321481'/><link rel='alternate' type='text/html' href='http://chlorophil.blogspot.com/2007/04/atomiser-part-vi.html' title='The Atomiser, Part VI'/><author><name>Philip Robinson</name><uri>http://www.blogger.com/profile/17605642659657207381</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp2.blogger.com/_6zQp6LtHMTo/Rhjzloh2FoI/AAAAAAAAABM/L_7Dee82p0Y/s72-c/HelloNotUsedSmall.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1893218858608219953.post-6422581370392274176</id><published>2007-04-08T14:52:00.000+10:00</published><updated>2007-04-08T20:14:08.939+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><category scheme='http://www.blogger.com/atom/ns#' term='atomiser'/><title type='text'>The Atomiser, Part V</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_6zQp6LtHMTo/RhhwMIh2FnI/AAAAAAAAABE/0_9D3dne6Eg/s1600-h/ValidAtomsSmall.png"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://bp0.blogger.com/_6zQp6LtHMTo/RhhwMIh2FnI/AAAAAAAAABE/0_9D3dne6Eg/s320/ValidAtomsSmall.png" alt="" id="BLOGGER_PHOTO_ID_5050910335846192754" border="0" /&gt;&lt;/a&gt;The Atomiser can now walk its own Abstract Syntax Tree and pluck lists of valid atoms from its own source code.&lt;br /&gt;&lt;br /&gt;Next on the list is to&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Have the Erlang compiler tell us when we use an atom that is not in the valid list.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Sounds easy: let's get to it!&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;We want to turn our placeholder atom clause in the walk_ast function into something a little more useful:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;walk_ast([{atom,Line,Atom}|RestAST], Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(RestAST, atom_check(Atom, Line, Atoms));&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Our new atom_check function will compare the supplied atom against the given atoms dictionary.  If the atom exists in the dictionary then a new dictionary will be returned, with that atom's value updated to 'found' (taking the place of the atom's line-of-definition integer).  If the atom does not exist in the dictionary then a warning message will be displayed and the original dictionary will be returned:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;atom_check(Atom, Line, Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case dict:find(Atom, Atoms) of&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{ok, found} -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Atoms;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{ok, _LineDefinedOn} -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dict:store(Atom, found, Atoms);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;io:format(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"Line ~B: Atom ~w unexpected.~n",&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[Line, Atom]),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Atoms&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;...and we are almost done.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;A bit of a shock - but a good sign - is the number of unknown atoms that appear when you first run the Atomiser on itself.  All of the atoms we use in our walk_ast pattern tuples show up as unknown atoms, so let's add some proper valid atom lists to the top of the file.  First, the atoms we use in our patterns:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;-atoms([atom, attribute, call, 'case', clause, clauses, compile]).&lt;br /&gt;-atoms([cons, eof, export, file, 'fun', function, match, module]).&lt;br /&gt;-atoms([nil, remote, string, tuple, var]).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;And then some other atoms we are use elsewhere in the code:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;-atoms([atoms, error, found, ok]).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;And... all the function names are showing up as unknown atoms, too?&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold; font-style: italic;"&gt;Executive decision time!&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Since internal function-calls are already picked up by the compiler when they do not match a function in the module, I think it should be safe to modify the 'call' line to:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;?WALK_AST({call,_Line,_Fun,Args}, [Args]);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;This will skip validating the called function name.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;And now,&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;1&gt; c(atomiser), l(atomiser).&lt;br /&gt;{module,atomiser}&lt;br /&gt;2&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Beautiful.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1893218858608219953-6422581370392274176?l=chlorophil.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chlorophil.blogspot.com/feeds/6422581370392274176/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://chlorophil.blogspot.com/2007/04/atomiser-part-v.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/6422581370392274176'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/6422581370392274176'/><link rel='alternate' type='text/html' href='http://chlorophil.blogspot.com/2007/04/atomiser-part-v.html' title='The Atomiser, Part V'/><author><name>Philip Robinson</name><uri>http://www.blogger.com/profile/17605642659657207381</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp0.blogger.com/_6zQp6LtHMTo/RhhwMIh2FnI/AAAAAAAAABE/0_9D3dne6Eg/s72-c/ValidAtomsSmall.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1893218858608219953.post-7558109955106521689</id><published>2007-04-07T11:32:00.000+10:00</published><updated>2007-06-20T14:58:35.037+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><category scheme='http://www.blogger.com/atom/ns#' term='atomiser'/><title type='text'>The Atomiser, Part IV</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_6zQp6LtHMTo/RhdbvIh2FmI/AAAAAAAAAA8/25Agvy_op7Y/s1600-h/WalkingTheASTSmall.png"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://bp3.blogger.com/_6zQp6LtHMTo/RhdbvIh2FmI/AAAAAAAAAA8/25Agvy_op7Y/s320/WalkingTheASTSmall.png" alt="" id="BLOGGER_PHOTO_ID_5050606372420720226" border="0" /&gt;&lt;/a&gt;The story so far:&lt;br /&gt;&lt;br /&gt;Our intrepid (also whiny, and quite lazy) developer suffers from a serious case of fat fingers and failing eyesight.  Mistyped Erlang atoms have caused him countless hours of anguish and hair loss, and the compiler does not even have the decency to emit so much as a warning or apology.&lt;br /&gt;&lt;br /&gt;In an effort to fill this massive hole in his development tools, the developer has managed to bolt together the bare bones of a parse transformation module.  Soon this unnatural creation will be lumbering around the neighbourhood, causing all sorts of havoc.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The Atomiser currently has just enough intelligence to recognise valid atom lists specified in a given source file, but it is not able to actually do anything with that information.  We need to walk the AST and locate the atoms used in the source code.&lt;br /&gt;&lt;br /&gt;This walk_ast function will be the work-horse of the Atomiser module:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;walk_ast([], Atoms) -&gt; Atoms;&lt;br /&gt;&lt;br /&gt;walk_ast([Node|ASTRest], Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;io:format("Unknown node:~n~p~n", [Node]),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(ASTRest, Atoms).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;And we will modify parse_transform to call it:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;parse_transform(AST, _Options) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Atoms = atoms_from_ast(AST),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_AtomsMarked = walk_ast(AST, Atoms),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AST.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;With this simple walk_ast function we will get a huge output list of 'unknown' Abstract Syntax Tree nodes when we compile atomiser.erl&lt;span style="font-weight: bold;"&gt;[1]&lt;/span&gt;.  The last unknown node will appear as:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;Unknown node: {eof,41}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Obviously we will need to add more function clauses to cater for the different nodes in the Abstract Syntax Tree.  Rather than starting from the top of the output list, I will work my way up from the bottom.  (It saves scrolling up... see reference to 'lazy' above.)&lt;br /&gt;&lt;br /&gt;Keeping the empty-list and the unknown-node function clauses as the first and last entries respectively, we add a clause entry for the eof node:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;walk_ast([{eof,_Line}|ASTRest], Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(ASTRest, Atoms);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Compiling the atomiser.erl program again&lt;span style="font-weight: bold;"&gt;[2]&lt;/span&gt; shows that, indeed, the {eof,41} node is now missing from the 'unknown node' output.&lt;br /&gt;&lt;br /&gt;Continuing on we can enter some more function clauses.  Something like this:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;walk_ast([], Atoms) -&gt; Atoms;&lt;br /&gt;walk_ast([{call,_Line,Fun,Args}|ASTRest], Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(ASTRest, walk_ast(Args, walk_ast([Fun], Atoms)));&lt;br /&gt;walk_ast([{clause,_Line,Args,Guards,Exprs}|ASTRest], Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(ASTRest, walk_ast(Exprs, walk_ast(Guards, walk_ast(Args, Atoms))));&lt;br /&gt;walk_ast([{cons,_Line,Head,Tail}|ASTRest], Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(ASTRest, walk_ast([Tail], walk_ast([Head], Atoms)));&lt;br /&gt;walk_ast([{eof,_Line}|ASTRest], Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(ASTRest, Atoms);&lt;br /&gt;walk_ast([{function,_Line,_Fun,_Arity,Clauses}|ASTRest], Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(ASTRest, walk_ast(Clauses, Atoms));&lt;br /&gt;walk_ast([{nil,_Line}|ASTRest], Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(ASTRest, Atoms);&lt;br /&gt;walk_ast([{var,_Line,_Name}|ASTRest], Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(ASTRest, Atoms);&lt;br /&gt;walk_ast([Node|ASTRest], Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;io:format("Unknown node: ~p~n", [Node]),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(ASTRest, Atoms).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Hang on, I am typing the same stuff over and over again... this is &lt;span style="font-weight: bold;"&gt;way&lt;/span&gt; too inefficient.&lt;br /&gt;&lt;br /&gt;It looks like there is a bit of a pattern going on here.  Something along the lines of:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;walk_ast([&lt;/span&gt;&lt;span style="font-style: italic;font-size:85%;" &gt;PatternTuple&lt;/span&gt;&lt;span style="font-size:85%;"&gt;|ASTRest], Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(ASTRest, &lt;/span&gt;&lt;span style="font-style: italic;font-size:85%;" &gt;PossiblyNestedCallsToWalkAST&lt;/span&gt;&lt;span style="font-size:85%;"&gt;(&lt;/span&gt;&lt;span style="font-style: italic;font-size:85%;" &gt;SomeValueInThePattern&lt;/span&gt;&lt;span style="font-size:85%;"&gt;, Atoms));&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Where the possibly-nested calls to walk_ast are based on the elements of the pattern tuple that we are interested in processing recursively.&lt;br /&gt;&lt;br /&gt;Now, Erlang does not have a built-in full macro system like Lisp does, but it does at least have basic substitution macros.  I think that we can make the code above much nicer to read by using a macro like this:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;-define(WALK_AST(Pattern, Expressions),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast([Pattern|ASTRest], Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(ASTRest,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lists:foldl(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun(AST, AtomsMarked) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(AST, AtomsMarked)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Atoms,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Expressions))).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Now that hideous function clause&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;walk_ast([{clause,_Line,Args,Guards,Exprs}|ASTRest], Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(ASTRest, walk_ast(Exprs, walk_ast(Guards, walk_ast(Args, Atoms))));&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;may be written like this:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;?WALK_AST({clause,_Line,Args,Guards,Exprs}, [Args, Guards, Exprs]);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Continuing to write a function clause for every unknown node in the AST&lt;span style="font-weight: bold;"&gt;[3]&lt;/span&gt;, we get:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;walk_ast([], Atoms) -&gt; Atoms;&lt;br /&gt;?WALK_AST({atom,_Line,Atom}, []); % Need to check for valid atom.&lt;br /&gt;?WALK_AST({attribute,_Line,file,_File}, []);&lt;br /&gt;?WALK_AST({attribute,_Line,module,_Module}, []);&lt;br /&gt;?WALK_AST({attribute,_Line,export,_ExportList}, []);&lt;br /&gt;?WALK_AST({attribute,_Line,compile,_CompilerDirective}, []);&lt;br /&gt;?WALK_AST({attribute,_Line,atoms,_AtomList}, []);&lt;br /&gt;?WALK_AST({call,_Line,Fun,Args}, [[Fun], Args]);&lt;br /&gt;?WALK_AST({'case',_Line,Test,Clauses}, [[Test], Clauses]);&lt;br /&gt;?WALK_AST({clause,_Line,Args,Guards,Exprs}, [Args, Guards, Exprs]);&lt;br /&gt;?WALK_AST({cons,_Line,Head,Tail}, [[Head], [Tail]]);&lt;br /&gt;?WALK_AST({eof,_Line}, []);&lt;br /&gt;?WALK_AST({'fun',_Line,{clauses,Clauses}}, [Clauses]);&lt;br /&gt;?WALK_AST({function,_Line,_Fun,_Arity,Clauses}, [Clauses]);&lt;br /&gt;?WALK_AST({match,_Line,Left,Right}, [[Left], [Right]]);&lt;br /&gt;?WALK_AST({nil,_Line}, []);&lt;br /&gt;?WALK_AST({remote,_Line,_Module,_Function}, []);&lt;br /&gt;?WALK_AST({string,_Line,_String}, []);&lt;br /&gt;?WALK_AST({tuple,_Line,Elements}, [Elements]);&lt;br /&gt;?WALK_AST({var,_Line,_Name}, []);&lt;br /&gt;walk_ast([Node|ASTRest], Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;io:format("Unknown node: ~p~n", [Node]),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;walk_ast(ASTRest, Atoms).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;And this is all we need&lt;span style="font-weight: bold;"&gt;&lt;/span&gt; to walk through the Atomiser's current code, with no unknown node messages appearing.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;[1]&lt;/span&gt; We need to compile atomiser.erl twice to pick up the changes and see them in action.  The first time the compilation is performed the new code is compiled and loaded.  The second time the compilation is performed the new code is actually run against the source.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;[2]&lt;/span&gt; Twice!&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;[3]&lt;/span&gt; Rather than consisting of a bunch of pattern matching clauses, the walk_ast function &lt;span style="font-style: italic;"&gt;could&lt;/span&gt; be made "smarter" by transforming the given node tuple into a list, and applying some rules-based logic to the elements of that list (from the third element onwards).  I chose to do things the function-clause way mainly because I am not completely familiar with all of the elements in an Erlang AST.  Having newly-encountered nodes come up as unknown elements is a good way to be exposed to the underlying structure of the parse tree, and to ensure that we have caught everything we need to.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1893218858608219953-7558109955106521689?l=chlorophil.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chlorophil.blogspot.com/feeds/7558109955106521689/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://chlorophil.blogspot.com/2007/04/atomiser-part-iv.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/7558109955106521689'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/7558109955106521689'/><link rel='alternate' type='text/html' href='http://chlorophil.blogspot.com/2007/04/atomiser-part-iv.html' title='The Atomiser, Part IV'/><author><name>Philip Robinson</name><uri>http://www.blogger.com/profile/17605642659657207381</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp3.blogger.com/_6zQp6LtHMTo/RhdbvIh2FmI/AAAAAAAAAA8/25Agvy_op7Y/s72-c/WalkingTheASTSmall.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1893218858608219953.post-1707358603675158236</id><published>2007-04-06T19:10:00.000+10:00</published><updated>2007-04-06T22:54:14.014+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><category scheme='http://www.blogger.com/atom/ns#' term='atomiser'/><title type='text'>The Atomiser, Part III</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_6zQp6LtHMTo/RhYpU4h2FlI/AAAAAAAAAA0/GxbaSg3n6PE/s1600-h/BareBonesSmall.png"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;" src="http://bp1.blogger.com/_6zQp6LtHMTo/RhYpU4h2FlI/AAAAAAAAAA0/GxbaSg3n6PE/s320/BareBonesSmall.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5050269470891054674" /&gt;&lt;/a&gt;Right, so we have a skeleton parse transformation module that emits a message and returns the unchanged Abstract Syntax Tree.  What do we do now?&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;There are a few requirements listed in &lt;a href="http://chlorophil.blogspot.com/2007/04/atomiser-part-ii.html"&gt;Part II&lt;/a&gt;; we might as well start from the top:&lt;br /&gt;&lt;br /&gt;* Ability to include a list of valid atoms into a source file.&lt;br /&gt;&lt;br /&gt;Implicit in this requirement is that the Atomiser will actually be able to read this list and do something with it.   Let's work on that.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;To begin with we want to embed our list of valid atoms in our source code.  Unfortunately we cannot use just any old syntax to do this - the syntax we choose must be parse-able by the existing Erlang compiler so that it can build us an Abstract Syntax Tree to play with.&lt;br /&gt;&lt;br /&gt;Luckily we can use a &lt;a href="http://www.erlang.se/doc/doc-5.5.4/doc/reference_manual/modules.html"&gt;module attribute&lt;/a&gt; to specify our list of valid atoms.   For no particular reason I will name this attribute 'atoms':&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family: courier new;"&gt;-atoms([atom1, atom2, atom2, atom4]).&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The Atomiser will do a quick pass through the top level of the Abstract Syntax Tree to pull in all the 'atoms' module attributes, storing their contents in a dictionary of valid atoms.   The atom names themselves will be the keys of this dictionary; the line number of the attribute that specified the atom will be the value stored against each atom key.  (We will use these line numbers for reporting later, if a specified atom is unused.)&lt;br /&gt;&lt;br /&gt;Here is a function to scan an AST and print all the atoms attributes it finds:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;atoms_find([]) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ok;&lt;br /&gt;atoms_find([{attribute,Line,atoms,AtomList}|ASTRest]) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;io:format("Found atom list on line ~B: ~p~n", [Line, AtomList]),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;atoms_find(ASTRest);&lt;br /&gt;atoms_find([_Node|ASTRest]) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;atoms_find(ASTRest).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;And with a slight modification, instead of printing them out we can store the atoms we find in a dictionary:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;atoms_from_ast(AST) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;atoms_from_ast(AST, dict:new()).&lt;br /&gt;&lt;br /&gt;atoms_from_ast([], Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Atoms;&lt;br /&gt;atoms_from_ast([{attribute,Line,atoms,AtomList}|ASTRest], Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;atoms_from_ast(ASTRest, atoms_from_attribute(Line, AtomList, Atoms));&lt;br /&gt;atoms_from_ast([_|ASTRest], Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;atoms_from_ast(ASTRest, Atoms).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Neat, huh?&lt;br /&gt;&lt;br /&gt;Well, okay, I haven't yet added the code to extract the atoms from an atoms attribute and store them in the dictionary.   It sounds a bit complicated...&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;atoms_from_attribute(Line, AtomList, Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AddAtom = fun(Atom, Dict) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dict:store(Atom, Line, Dict)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lists:foldl(AddAtom, Atoms, AtomList).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;...well, maybe that is not too complicated after all.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Oh, wait!  If I make the Atomiser report on atoms that have &lt;span style="font-style: italic;"&gt;already&lt;/span&gt; been specified as valid, then I can totally make this look impressive:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;atoms_from_attribute(Line, AtomList, Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AddAtom = fun(Atom, Dict) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case dict:find(Atom, Dict) of&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{ok, LineAlreadyDefined} -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;io:format(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"Line ~B: Atom ~w already defined on line ~B.~n",&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[Line, Atom, LineAlreadyDefined]),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Dict;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error -&gt; dict:store(Atom, Line, Dict)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lists:foldl(AddAtom, Atoms, AtomList).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;There we go.  Now the Atomiser will let us know if we have accidentally specified a valid atom more than once.&lt;br /&gt;&lt;br /&gt;We can try this out now.  Here is the full listing of the Atomiser so far:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;-module(atomiser).&lt;br /&gt;-export([parse_transform/2]).&lt;br /&gt;-compile({parse_transform, atomiser}). % Comment out for initial compile.&lt;br /&gt;&lt;br /&gt;-atoms([atom1, atom2, atom2, atom4]).&lt;br /&gt;&lt;br /&gt;parse_transform(AST, _Options) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Atoms = atoms_from_ast(AST),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;io:format("Retrieved these valid atoms: ~p~n", [dict:fetch_keys(Atoms)]),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AST.&lt;br /&gt;&lt;br /&gt;atoms_from_ast(AST) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;atoms_from_ast(AST, dict:new()).&lt;br /&gt;&lt;br /&gt;atoms_from_ast([], Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Atoms;&lt;br /&gt;atoms_from_ast([{attribute,Line,atoms,AtomList}|ASTRest], Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;atoms_from_ast(ASTRest, atoms_from_attribute(Line, AtomList, Atoms));&lt;br /&gt;atoms_from_ast([_|ASTRest], Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;atoms_from_ast(ASTRest, Atoms).&lt;br /&gt;&lt;br /&gt;atoms_from_attribute(Line, AtomList, Atoms) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AddAtom = fun(Atom, Dict) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case dict:find(Atom, Dict) of&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{ok, LineAlreadyDefined} -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;io:format(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"Line ~B: Atom ~w already defined on line ~B.~n",&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[Line, Atom, LineAlreadyDefined]),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Dict;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error -&gt; dict:store(Atom, Line, Dict)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lists:foldl(AddAtom, Atoms, AtomList).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Compiling this atomiser.erl file should give us the list of the three unique atoms specified, and a complaint about atom2 occurring twice:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;1&gt; c(atomiser), l(atomiser).&lt;br /&gt;Line 5: Atom atom2 already defined on line 5.&lt;br /&gt;Retrieved these valid atoms: [atom1,atom2,atom4]&lt;br /&gt;{module,atomiser}&lt;br /&gt;2&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;This thing had better do some real work soon... it is already past thirty lines of code!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1893218858608219953-1707358603675158236?l=chlorophil.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chlorophil.blogspot.com/feeds/1707358603675158236/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://chlorophil.blogspot.com/2007/04/atomiser-part-iii.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/1707358603675158236'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/1707358603675158236'/><link rel='alternate' type='text/html' href='http://chlorophil.blogspot.com/2007/04/atomiser-part-iii.html' title='The Atomiser, Part III'/><author><name>Philip Robinson</name><uri>http://www.blogger.com/profile/17605642659657207381</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp1.blogger.com/_6zQp6LtHMTo/RhYpU4h2FlI/AAAAAAAAAA0/GxbaSg3n6PE/s72-c/BareBonesSmall.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1893218858608219953.post-7850601308857823447</id><published>2007-04-05T22:00:00.000+10:00</published><updated>2007-04-07T11:29:44.016+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><category scheme='http://www.blogger.com/atom/ns#' term='atomiser'/><title type='text'>The Atomiser, Part II</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp2.blogger.com/_6zQp6LtHMTo/RhYobIh2FkI/AAAAAAAAAAs/utt4vsB9PxU/s1600-h/ParseTransformSmall.png"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;" src="http://bp2.blogger.com/_6zQp6LtHMTo/RhYobIh2FkI/AAAAAAAAAAs/utt4vsB9PxU/s320/ParseTransformSmall.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5050268478753609282" /&gt;&lt;/a&gt;So now we have some initial requirements to work with:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Ability to include a list of valid atoms into a source file.&lt;/li&gt;&lt;li&gt;Have the Erlang compiler tell us when an atom used is not in the valid list.&lt;/li&gt;&lt;/ul&gt;And optionally:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Have the Erlang compiler tell us when an atom in the valid list is &lt;span style="font-weight: bold;"&gt;not&lt;/span&gt; used.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;To achieve this I am going to write an Erlang parse transformation module. (A parse transformation takes the already-parsed syntax tree of an Erlang program and (usually) applies some modification to it.)&lt;br /&gt;&lt;br /&gt;As an aside, &lt;a href="http://www.erlang.org/doc/doc-5.5.4/lib/stdlib-1.14.4/doc/html/erl_id_trans.html"&gt;programmers are strongly advised not to engage in parse transformations and no support is offered for problems encountered&lt;/a&gt;... but where is the fun in that?&lt;br /&gt;&lt;br /&gt;In this case we are not actually going to engage in any transformations - we just want to hook into the compiler and scan the source code for atoms at compile-time.&lt;br /&gt;&lt;br /&gt;So without further ado, I present the Atomiser, first draft:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;-module(atomiser).&lt;br /&gt;-export([parse_transform/2]).&lt;br /&gt;&lt;br /&gt;parse_transform(AST, _Options) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;io:format("The Atomiser is running.~n"),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AST.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;All we are doing at this stage is printing a message and returning the unchanged Abstract Syntax Tree.&lt;br /&gt;&lt;br /&gt;Once this atomiser.erl file is compiled into atomiser.beam we can put a parse_transform compiler directive in any source file and the compiler will call the Atomiser's parse_transform function automatically.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;-compile({parse_transform, atomiser}).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Since we are already engaging in a highly ill-advised activity, we might as well go the whole way and include this directive into the Atomiser itself.  (Only do this &lt;span style="font-weight: bold;"&gt;after&lt;/span&gt; the original version has been compiled, or the compiler will hang when it tries to compile this file.)&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;-module(atomiser).&lt;br /&gt;-export([parse_transform/2]).&lt;br /&gt;-compile({parse_transform, atomiser}).&lt;br /&gt;&lt;br /&gt;parse_transform(AST, _Options) -&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;io:format("The Atomiser is running.~n"),&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AST.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;So when we compile this program we should be informed that the Atomiser is actually running:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;1&gt; c(atomiser), l(atomiser).&lt;br /&gt;The Atomiser is running.&lt;br /&gt;{module, atomiser}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;So far, so good.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1893218858608219953-7850601308857823447?l=chlorophil.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chlorophil.blogspot.com/feeds/7850601308857823447/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://chlorophil.blogspot.com/2007/04/atomiser-part-ii.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/7850601308857823447'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/7850601308857823447'/><link rel='alternate' type='text/html' href='http://chlorophil.blogspot.com/2007/04/atomiser-part-ii.html' title='The Atomiser, Part II'/><author><name>Philip Robinson</name><uri>http://www.blogger.com/profile/17605642659657207381</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp2.blogger.com/_6zQp6LtHMTo/RhYobIh2FkI/AAAAAAAAAAs/utt4vsB9PxU/s72-c/ParseTransformSmall.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1893218858608219953.post-4299117283253611524</id><published>2007-04-04T23:33:00.000+10:00</published><updated>2007-04-07T11:26:51.381+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='erlang'/><category scheme='http://www.blogger.com/atom/ns#' term='atomiser'/><title type='text'>The Atomiser, Part I</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_6zQp6LtHMTo/RhYnvoh2FjI/AAAAAAAAAAk/dRmrsCzR9Fw/s1600-h/TypingAtDeskSmall.png"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;" src="http://bp0.blogger.com/_6zQp6LtHMTo/RhYnvoh2FjI/AAAAAAAAAAk/dRmrsCzR9Fw/s320/TypingAtDeskSmall.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5050267731429299762" /&gt;&lt;/a&gt;Erlang has &lt;span style="font-style: italic;"&gt;atoms&lt;/span&gt; - one of many language features that I find hard to live without when I have to work with other systems.&lt;br /&gt;&lt;br /&gt;But the compiler for Erlang does not check that the atoms you pass to a function are actually used by that function.&lt;br /&gt;&lt;br /&gt;While I love programming with Erlang, stuff like this happens to me more often than I care to admit:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;1&gt; Greeting = fun&lt;br /&gt;1&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;(hello) -&gt; "Hooray! You said hello!";&lt;br /&gt;1&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;(_) -&gt; "You said something else."&lt;br /&gt;1&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end.&lt;br /&gt;#Fun&lt;br /&gt;&lt;br /&gt;2&gt; Greeting(he11o).&lt;br /&gt;"You said something else."&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;...&lt;br /&gt;&lt;br /&gt;Huh?  I said hello!&lt;br /&gt;&lt;br /&gt;Didn't I?&lt;br /&gt;&lt;br /&gt;The more observant of you will have immediately spotted the number '1's masquerading as 'l's up there, but believe me, depending on the obviousness of the typo and the lateness of the hour this sort of pest can be quite painful to hunt down and eradicate.&lt;br /&gt;&lt;br /&gt;It was especially painful when I first started learning the language.  I would find some strange behaviour or receive these odd "function_clause" errors, and I had no idea what was wrong.&lt;br /&gt;&lt;br /&gt;Wouldn't it be nice if you could optionally give the compiler a list of valid atoms for a source file, and have the compiler warn you if you were using an atom that was not in that list?&lt;br /&gt;&lt;br /&gt;I thought so too.&lt;br /&gt;&lt;br /&gt;Let's fix it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1893218858608219953-4299117283253611524?l=chlorophil.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://chlorophil.blogspot.com/feeds/4299117283253611524/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://chlorophil.blogspot.com/2007/04/atomiser-part-i.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/4299117283253611524'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1893218858608219953/posts/default/4299117283253611524'/><link rel='alternate' type='text/html' href='http://chlorophil.blogspot.com/2007/04/atomiser-part-i.html' title='The Atomiser, Part I'/><author><name>Philip Robinson</name><uri>http://www.blogger.com/profile/17605642659657207381</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp0.blogger.com/_6zQp6LtHMTo/RhYnvoh2FjI/AAAAAAAAAAk/dRmrsCzR9Fw/s72-c/TypingAtDeskSmall.png' height='72' width='72'/><thr:total>5</thr:total></entry></feed>
