% lfe_types(7) % Robert Virding % 2021
lfe_types - LFE Types and Functions Specifications
This is a description of the type syntax.
LFE type | Erlang type |
---|---|
(any) |
any() |
(none) |
none() |
(atom) |
atom() |
(integer) |
integer() |
(range i1 i2) |
I1..I2 |
(float) |
float() |
(bitstring m n) |
<<_:M,_:_*N>> |
(binary) |
<<_:0,_:_*8>> |
(bitstring) |
<<_:0,_:_*1>> |
(nil) |
[] %% nil |
... |
... |
(lambda any <type>) |
fun((...) -> <type>) |
(lambda () <type>) |
fun(() -> <type>) |
(lambda (<tlist>) <type>) |
fun((<tlist>) -> <type>) |
(map) |
map() |
#M() |
#{} |
#M(<key> <value> ...) |
#{<pairlist>} |
(tuple) |
tuple() |
#() |
{} |
#(<tlist>) |
{<tlist>} |
(UNION <tlist>) |
` |
Apart from the predefined types in the Erlang type system we also have
the following predefined types which cannot be redefined: UNION
,
call
, lambda
and range
. The usage of bitstring
, tuple
and
map
have also been extended.
Note that the type #M()
is the empty map and the type #()
is the
empty tuple. We can still use the older (map <key valuelist>)
and
(tuple <tlist>)
formats when declaring types for maps and tuples.
The general form of bitstrings is (bitstring m n)
which denotes a
bitstring which starts with m
bits and continues with segments of
n
bits. (binary)
is a short form for a sequence of bytes while
(bitstring)
is a short form for a sequence of bits. There is
currently no short form for an empty binary, (bitstring 0 0)
must be
used.
(deftype (type-name) type-def)
(defopaque (type-name) type-def)
(deftype (type-name par1 par2) type-def)
(defopaque (type-name par1 par2) type-def)
For unparameterised types the parentheses around the type name are optional. An example:
(deftype foo (tuple 'foo (integer) (list)))
(deftype bar (tuple 'bar (integer) (list)))
(defrecord rec (field1 default1 type1) (field2 default2) (field3))
Fields with type annotations MUST give a default value and fields
without type annotations get the default type (any)
.
(defspec (func-name arity) function-spec ...)
where
function-spec = (arg-type-list ret-type)
function-spec = (arg-type-list ret-type constraint-list)
function-spec = #M(arg-types arg-type-list ret-type ret-type)
function-spec = #M(arg-types arg-type-list ret-type ret-type
contraints constraint-list)
arg-type-list = (arg-type ...)
constraint-list = (constraint ...)
constraint = (var var-type)
For multiple types add more function specs. The parentheses around the function name and the arity are optional. For example from the docs:
(defspec foo ([(pos_integer)] (pos_integer)))
(defspec (foo 1)
([(pos_integer)] (pos_integer))
([(integer)] (integer)))
(defspec (remove-if 2)
([(lambda ((any)) (boolean)) (list)] (list)))
Or with constraints:
(defspec id ((X) X ((X (tuple)))))
(defspec (foo 1)
([(tuple X (integer))] X ((X (atom))))
([(list Y)] Y ((Y (number)))))
(defspec (remove-if 2)
([pred (list)] (list) [(pred (lambda ((any)) (boolean)))]))
Note that a constraint variable doesn't need to start with an upper-case like an Erlang variable, though in some case it may be easier to read.
Note we are using the alternate list form with [ ]
instead of
parentheses to make it easier to see the function arguments.
Types can also be defined in the module declaration, for example:
(defmodule this-module
...
(type ((foo-type) (tuple 'foo (integer) (list)))
((bar-type) (tuple 'bar (integer) (list))))
(spec ((foo 1) ([(integer)] (foo-type)))
((id 1) ([x] x ((x (tuple))))))
...)