[erlang-questions] Runtime checking for types

Kostis Sagonas kostis@REDACTED
Tue Apr 19 17:15:37 CEST 2011


Ken Robinson wrote:
> I know this sounds crazy but I was wondering if there was some runtime
> check for types. I've created an abstract data type in an .erl (see
> below). I want to now pattern match
> 
> -opaque u16int() :: 0..(1 bsl 16 - 1).
> -export_type([u16int/0]).
> ...
> -record(headerrec, {message_id :: u16int()}).
> -opaque headerrec() :: #headerrec{}.
> -export_type([headerrec/0]).
> 
> I can do a runtime test like so:
> foo(#headerrec{} = HeaderRec) ->
>     ...
> 
> What I would like to do is test for the new type similar to the guards
> is_record() and the like. Any ideas? I realize I can put this all in a
> .hrl file but this would break the encapsulation.

You asked an interesting question and IMO the thread you started got 
responses that are not related to what you want to do, do not help you 
much, and actually show a bit of ignorance about what modern Erlang is 
capable of doing.  I find this a pity...

First of all, you've not told us why you would like to pattern match. 
Perhaps the proper answer to your question is that you should not. Then 
you can preserve encapsulation and make sure things work as you expect 
them to.

Here is what you can do.  You can define your opaque data type in a 
module, say 'header', which looks as follows:

%%-------------------------------------------------------------------
-module(header).
-export([new/0, new/1, get_message_id/1, set_message_id/2]).

-export_type([headerrec/0]).

-record(headerrec, {message_id :: u16int()}).
-opaque headerrec() :: #headerrec{}.

%% You can actually declare the following opaque if you want, but
%% it really does not make any difference for what you want to do
-type u16int() :: 0..(1 bsl 16 - 1).

new() ->
     new(42).  % put some sensible default here

new(Id) when is_integer(Id), 0 =< Id, Id =< (1 bsl 16 - 1) ->
     #headerrec{message_id = Id}.

get_message_id(#headerrec{message_id = Id}) -> Id.

set_message_id(#headerrec{} = R, Id)
   when is_integer(Id), 0 =< Id, Id =< (1 bsl 16 - 1) ->
     R#headerrec{message_id = Id};
set_message_id(_R, _Id) ->
     error("SOME APPROPRIATE ERROR MESSAGE/TERM HERE").
%----------------------------------------------------------------------

This module declares an abstract data type and functions to create, 
access and update its elements.  At least as far as dialyzer is 
concerned, this data type is *unforgeable*.  Dialyzer will actually find 
and point out each and every attempt to create some term that looks like 
the abstract data type but is not created by this module.  It will also 
flag every attempt (outside this module) to deconstruct a #headerrec{} 
record created by the new/[0,1] functions.  Don't just believe me; tesy 
this for yourself and try to break it if you can.

What you now do is create such terms and pass them around in other 
modules and you can be rest assured that the types are correct as long 
as dialyzer does not complain about it.

So, I strongly suspect that the above does what you want to do. The only 
reason I can think of (but perhaps I lack imagination here) that you may 
want to do a runtime test for the type outside the module that defines 
it is if you want to have a function that expects either a #headerrec{} 
or something else.

Currently, there is no way to do this in Erlang.  But stay tuned for 
user-defined guards which will give you this ability.

So, IMO, you should NOT expose the record definition in a header file.

Kostis



More information about the erlang-questions mailing list