RTypes

RTypes is an Elixir library which helps automatically create a verification function for a given user type. The function can be used to check the shape of the data after de-serialisation or in unit-tests.

Let's suppose we have a type

@type t :: 0..255

and we have a value x. To ensure that our value corresponds to the type t we can use the function

def is_t(x) when is_integer(x) and x >= 0 and x <= 255

Now, if we have a compound type

@type list_of_ts :: [t]

and a value xs, we can use is_list/1 guard on xs and then ensure that all elements of the list conform to t. And if we have a more complex structure

@type state(a, b) :: %{key1: {a, b}, key2: list_of_ts()}

and a value s, we can check that s is a map which has keys key1 and key2, apply the logic above for the value of key2 and for any concrete types a and b we can check that he value of key1 is a tuple of length 2 and its elements conform to a and b respectively. So we just recursively apply those checks.

That's the gist of it.

Usage

The library defines derive!/1 macro, derive!/3 and derive/3 functions which can be used to derive a run-time checker for the given type. The validating function returned by "bang" versions either returns true or throws an exception explaining what went wrong. The function returned by derive/3 does not throw, returning true or false and typically is faster than the throwing versions.

derive!/1 macro

iex> require RTypes
iex> is_port_number = RTypes.derive(:inet.port_number())
iex> is_port_number.(8080)
true

Note that the macro expects the argument as in module.type(arg1, arg2). That is a module name followed by . and the type name, followed by type parameters enclosed in parenthesis.

derive!/3 function

The function expects a module, type name, and a list of type args, represented as AST

iex> is_keyword_list = RTypes.derive(Keyword, :t, [{:type, 0, :pos_integer, []}])
iex> is_keyword_list.(key1: 4, key2: 5)
true

Implementation

The generated function is essentially a walk-the-tree interpreter of the expanded AST that represents the type. However, instead of evaluating the expression it applies a specific clause of the checker function.

derive/3 provides alternative implementation where instead of walking the tree every time the validating function is invoked, the tree is walked only once, at the call site, building a corresponding tree of closures.

Notes

TODO