Erlang Central

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

From ErlangCentral Wiki

(Writing Dynamic Match Specifications)
 
(The Record Definition)
Line 52: Line 52:
  
 
At run time I need to be able to introspect this structure and ask questions like:
 
At run time I need to be able to introspect this structure and ask questions like:
- how many fields does record *index* have?
+
* how many fields does record ''index'' have?
- where is the field *path* in record *index*?
+
* where is the field ''path'' in record ''index''?
 +
 
 +
=== Generating The Helper Function ===
 +
We use the Erlang preprocesser function to parse the header file:
 +
<code>
 +
{ok,Tree}=epp:parse_file("myheader.hrl",["./"],[]),
 +
</code>
 +
which produces output like:
 +
<code>
 +
{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}}]}},
 +
    [...]
 +
</code>
 +
We step through this output and generate a helper file which looks like this:
 +
<code>
 +
%% 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)}).
 +
</code>

Revision as of 12:21, 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)}).