rfc: rdbms - new type system
Ulf Wiger (AL/EAB)
ulf.wiger@REDACTED
Thu Feb 23 11:49:57 CET 2006
Rdbms is asymptotically approaching beta status. (:
Some comments on the following are most welcome.
I started doing some cleanups and documentation.
Currently, I'm in some kind of test-doc-driven
development mode (code a little, write test suites,
redesign, document, redesign to comply with the
documentation, and so on.) I'm using a tiddlyWiki
for the documentation, which I think fits very
nicely with this way of working.
The current holdup is that I'm completely redesigning
the type system (after all, no-one demanded backwards
compatibility...) I'm just getting ready to throw out
the 'required' and 'bounds' properties, as they can be
expressed as types.
I removed some old types that were just... messy
(e.g. 'compound'), as well as 'record' and 'tuples'
which are now redundant.
Current approach:
Typedefs: you can specify {typedef, Name, Type}
as a table property or a global property. Table
typedefs shadow global typedefs. It is not
possible to shadow predefined types.
So,
[{{attr,age, type}, integer},
{{attr,age,bounds}, {inclusive, 0, 100}},
{{attr,age,required}, true}]
now becomes:
{{attr, age, type},
{'and', [{'>=', 0},
{'=<', 150}]}}
In this case, 'integer' and 'required' are implicit.
In order to allow the value undefined,
one could do this:
[{{typedef, t_age},
{'and',[{'>=',0},{'=<',150}]}},
{{attr, age, type},
{'or',[{type,t_age},{'==',undefined}]}}
...which is messier than before, but mixing
'undefined' and integers in the same attribute
also messes up the user's code. (:
It certainly simplifies the 'rdbms' code a lot.
The remaining other attribute property
is 'default', which is used e.g. in
cascading updates.
The following type-checking code may serve
to illustrate the type system
(which compiles, but hasn't been tested yet):
%%% check_type(Type, Value) -> boolean()
%%%
check_type(false, _) -> false;
check_type(true, _) -> true;
check_type(any, _) -> true;
check_type({tuple, Arity, Ts}, X)
when is_tuple(X), size(X) == Arity ->
Elems = tuple_to_list(X),
lists:zipwith(
fun check_type/2, Ts, Elems);
check_type({tuple, Arity}, X)
when is_tuple(X), size(X) == Arity -> true;
check_type(tuple, X) when is_tuple(X) -> true;
check_type(integer, X) when is_integer(X) -> true;
check_type(float, X) when is_float(X) -> true;
check_type(number, X)
when is_integer(X); is_float(X) -> true;
check_type(atom, X) when is_atom(X) -> true;
check_type(pid, X) when is_pid(X) -> true;
check_type(port, X) when is_port(X) -> true;
check_type(binary, X) when is_binary(X) -> true;
check_type(string, X) ->
try list_to_binary(X) of
_ -> true
catch
error:_ -> false
end;
check_type(list, X) when is_list(X) -> true;
check_type(nil, []) -> true;
check_type({list,false},_) ->
false; % optimization
check_type({list, T}, [_|_])
when T==any; T==undefined -> true;
check_type({list, T}, X) when is_list(X) ->
while(true, fun(Elem) ->
check_type(T, Elem)
end, X);
check_type(function, X)
when is_function(X) -> true;
check_type({function,Arity}, X)
when is_function(X, Arity) -> true;
check_type({'and',Ts}, X) ->
lists:foldl(fun(T,Acc) ->
Acc and check_type(T,X)
end, true, Ts);
check_type({'andalso',Ts}, X) ->
while(true, fun(T) -> check_type(T, X) end, Ts);
check_type({'or',Ts}, X) ->
lists:foldl(fun(T,Acc) ->
Acc or check_type(T,X)
end, false, Ts);
check_type({'orelse', Ts}, X) ->
while(false, fun(T) -> check_type(T, X) end, Ts);
check_type({'not', T}, X) ->
not(check_type(T, X));
check_type({O,V}, X)
when O=='==';O='=/=';O=='<';O=='>';O=='>=';O=='=<' ->
erlang:O(X,V);
check_type(_, _) ->
false.
while(Bool, F, L) when is_list(L) ->
Not = not(Bool),
try lists:foreach(
fun(Elem) ->
case F(Elem) of
Not -> throw(Not);
Bool -> Bool
end
end, X) of
_ -> Bool
catch
throw:Not ->
Not
end.
When types are added, I run some code that
tries to simplify the types a bit. This code
is naive at best, and I'm hoping to get some
help with this later on. The main candidate
is the {list, T} type. If T can be simplified
into any of the values 'true', 'false', 'any',
or 'undefined', then the list won't have to be
traversed.
While doing this, I also had to ask myself
what {list, any} actually is supposed to mean.
Currently, it's 'any non-empty list', as opposed
to 'nil'. I'm perfectly willing to revisit this,
since it has the unfortunate side effect that
a typed list which includes [] becomes more
difficult to specify:
{'or', [nil, {list, integer}]}
Perhaps it would be better to force those
who want a non-empty list to write:
{'andalso', [{'not',nil}, {list, any}]}
(Formulating the question, I think I have
decided to go for the latter. The test-doc-
driven development process at work again. :)
BTW, why would one want both 'and' and
'andalso' in this context? Shouldn't it
always be 'andalso'?
Regards,
Ulf W
More information about the erlang-questions
mailing list