Erlang Central

Difference between revisions of "Match Specifications And Records (Dynamically!)"

From ErlangCentral Wiki

(The Record Definition)
(Generating The Helper Function)
Line 89:Line 89:
 
get_index(index,row)-> 4;
 
get_index(index,row)-> 4;
 
get_index(index,F) -> exit({error,"Record: index has no field called "++atom_to_list(F)}).
 
get_index(index,F) -> exit({error,"Record: index has no field called "++atom_to_list(F)}).
 +
</code>
 +
 +
===Writing Dynamic Match Specs===
 +
We can now write a little library that enables us to write dynamic match specs
 +
<code>
 +
Test=["Match Me"|'$1'],
 +
Head=ms_util:make_ms(index,[{path,Test}]),
 +
</code>
 +
I can use the atom names of records (index and path) and the function with make_ms to make a dymamic match spec which will always be right at run time:
 +
<code>
 +
>ms_util:make_ms(index,[{path,"Match Me!"}]).
 +
>{index,'_','"Match Me!",'_','_'}
 
</code>
 
</code>

Revision as of 12:40, 3 September 2008

Contents

Author

Gordon Guthrie


Overview

This How To shows how dynamic match specifications can be built using records.

This approach allows record definitions to be extended by adding new fields, or the order of fields in records to be amended, without affecting the dynamic match specifications.

It works by generating a helper library function at compile time which enables a match_specifications module to introspect the record structure and build dynamic match specs.

The Problem

Records are a great way of making tuples easier to handle - they are the Erlang equivalent of a C struct. The ability to reference an element in a structure without having to know it is the third element is great, but...

...the problem is that sometimes you actually need to know the structure - and the main time is when you want to write match specifications.

There is a 'work around' for writing transforming code that has records in it but which generates valid match specs. This uses the function fun2ms from stdlib (see the documentation [1]).

But the problems with fun2ms is that it can't take a variable so you *can* use it to make writing static match specifications easier but you *can't* use it to generate them dynamically.

You might think the answer is to use fun2ms within an eval but you don't have access to the record definition in an eval so you are snookered there too..

The Solution

The solution is to use the Erlang Preprocessor to parse the .hrl file that has the record definition and output a helper function.

This helper function must be generated everytime the header file is changed as part of the make procedures for you application.

Dynamic match specifications written with the library will then 'just work'!

Working Code

The working code for this How To is appended at the end.


Worked Example

The Record Definition

My .hrl file contains a record definition like this:

%% Test Macros
-define(HN_URL1,   "http://127.0.0.1:9000").
-define(HN_URL2,   "http://127.0.0.1:9001").

-record( index,
{
    site,
    path,
    column,
    row
}).

At run time I need to be able to introspect this structure and ask questions like:

  • how many fields does record index have?
  • where is the field path in record index?

Generating The Helper Function

We use the Erlang preprocesser function to parse the header file:

{ok,Tree}=epp:parse_file("myheader.hrl",["./"],[]),

which produces output like:

{attribute,1,file,{"myheader.hrl",1}},
{attribute,18,record,
  {index,
   [{record_field,20,{atom,20,site}},
    {record_field,21,{atom,21,path}},
    {record_field,22,{atom,22,column}},
    {record_field,23,{atom,23,row}}]}},
    [...]

We step through this output and generate a helper file which looks like this:

%% This module automatically generated - do not edit

%%% This module provides utilities for use in building
%%% match specifications from records

-module(ms_util2).

-export([get_index/2,no_of_fields/1]).

no_of_fields(index) -> 4;

get_index(index,site)-> 1;
get_index(index,path)-> 2;
get_index(index,column)-> 3;
get_index(index,row)-> 4;
get_index(index,F) -> exit({error,"Record: index has no field called "++atom_to_list(F)}).

Writing Dynamic Match Specs

We can now write a little library that enables us to write dynamic match specs

Test=["Match Me"|'$1'],
Head=ms_util:make_ms(index,[{path,Test}]),

I can use the atom names of records (index and path) and the function with make_ms to make a dymamic match spec which will always be right at run time:

>ms_util:make_ms(index,[{path,"Match Me!"}]).
>{index,'_','"Match Me!",'_','_'}