Skip to content

Latest commit

 

History

History
81 lines (67 loc) · 2.55 KB

specs.md

File metadata and controls

81 lines (67 loc) · 2.55 KB

Specs

This document tries to describe how to use specs with mekao.

Several times I heard similar question regarded ets:select/2: I have a record but dialyzer complains when I put atom '_' as a field's value, what should I do? The same problem applicable to mekao usage — some of the values have special meaning (i.e. '$skip' or {'$predicate, ...'}).

Typical record may look like this:

-record(book, {
    id      :: pos_integer(),
    isbn    :: binary(),
    title   :: binary(),
    author  :: binary(),
    created :: non_neg_integer()
}).

When we want to select a records by it's author we could write something like this:

mekao:select(#book{author = <<"Joe">>, _ = '$skip'}, Table, Settings).

That is direct violation of the spec, because non of the fields of the #book{} were allowed to put the '$skip' atom as a value.

One of the possible solutions may be just to add this and other special terms to the spec like this:

-record(book, {
    id      :: '$skip' | pos_integer(),
    isbn    :: '$skip' | binary(),
    title   :: '$skip' | binary(),
    author  :: '$skip' | binary(),
    created :: '$skip' | non_neg_integer()
}).

But I suggest not to do this and here is why. I found that regular code uses special values only when in need to communicate with DB, in other cases it tend to work with ordinary record values. And each such communication returns ordinary record too. So I use to divide specs into 3 groups like this:

%% record without the specs
-record(book, {id, isbn, title, author, created}).

%% parametrized typespec for the #book{}
-type book(E) :: #book{
    id      :: E | pos_integer(),
    isbn    :: E | binary(),
    title   :: E | binary(),
    author  :: E | binary(),
    created :: E | non_neg_integer()
}.

%% typespec for regular usage
-type book() :: book(none()).

%% typespec for inserts and updates(set clause)
-type book_inserter() :: book('$skip').

%% typespec for selects, deletes and updates(where clause)
-type book_selector() :: #book{
    id      :: '$skip' | mekao:predicate(pos_integer()),
    isbn    :: '$skip' | mekao:predicate(binary()),
    title   :: '$skip' | mekao:predicate(binary()),
    author  :: '$skip' | mekao:predicate(binary()),
    created :: '$skip' | mekao:predicate(non_neg_integer())
}.

From now on you can safely create record instance with any value, but when you will try to pass it around, function with corresponding spec will warn you about violation.

If this seems like overkill to you feel free to contact me and discuss any other ideas.