From 68c2f188c3446f53fad03d0f652207a9a8bb1946 Mon Sep 17 00:00:00 2001
From: Kenneth Lundin The current release implements types and function specifications
- as described in
+ A similar syntax as for module attributes is used for
+ specifying types and function specifications.
+ Read more in
+ The desciption is based on
The implementation and EEP8 may not exactly correspond to
- each other. In a future release, type and function specifications
- will be described in this reference manual.
Note!, they are still preliminary.
---
system/doc/reference_manual/modules.xml | 30 +-
system/doc/reference_manual/part.xml | 7 +-
system/doc/reference_manual/typespec.xml | 464 +++++++++++++++++++++++++++++++
3 files changed, 485 insertions(+), 16 deletions(-)
create mode 100755 system/doc/reference_manual/typespec.xml
(limited to 'system')
diff --git a/system/doc/reference_manual/modules.xml b/system/doc/reference_manual/modules.xml
index f4885be480..0dbc0ab56b 100644
--- a/system/doc/reference_manual/modules.xml
+++ b/system/doc/reference_manual/modules.xml
@@ -4,7 +4,7 @@
+-type my_type() :: atom() | integer().
+-spec my_function(integer()) -> integer().
+
+
+ Although Erlang is a dynamically typed language this section describes + an extension to the Erlang language for declaring sets of Erlang terms + to form a particular type, effectively forming a specific sub-type of the + set of all Erlang terms. +
++ Subsequently, these types can be used to specify types of record fields + and the argument and return types of functions. +
+
+ Type information can be used to document function interfaces,
+ provide more information for bug detection tools such as
+ Types describe sets of Erlang terms.
+ Types consist and are built from a set of predefined types (e.g.
+ For integers and atoms, we allow for singleton types (e.g. the integers
+ atom() | 'bar' | integer() | 42+
+ describes the same set of terms as the type union: +
++atom() | integer()+
+ Because of sub-type relations that exist between types, types form a lattice + where the topmost element, any(), denotes the set of all Erlang terms and + the bottom-most element, none(), denotes the empty set of terms. +
++ The set of predefined types and the syntax for types is given below: +
+> + | <<>> + | <<_:Erlang_Integer>> %% Base size + | <<_:_*Erlang_Integer>> %% Unit size + | <<_:Erlang_Integer, _:_*Erlang_Integer>> + +Fun :: fun() %% any function + | fun((...) -> Type) %% any arity, returning Type + | fun(() -> Type) + | fun((TList) -> Type) + +Integer :: integer() + | Erlang_Integer %% ..., -1, 0, 1, ... 42 ... + | Erlang_Integer..Erlang_Integer %% specifies an integer range + +List :: list(Type) %% Proper list ([]-terminated) + | improper_list(Type1, Type2) %% Type1=contents, Type2=termination + | maybe_improper_list(Type1, Type2) %% Type1 and Type2 as above + +Tuple :: tuple() %% stands for a tuple of any size + | {} + | {TList} + +TList :: Type + | Type, TList +]]>+
+ Because lists are commonly used, they have shorthand type notations.
+ The type
+ Notice that the shorthand for
+ For convenience, the following types are also built-in. + They can be thought as predefined aliases for the type unions also shown in + the table. (Some type unions below slightly abuse the syntax of types.) +
++ Users are not allowed to define types with the same names as the predefined or + built-in ones. + This is checked by the compiler and its violation results in a compilation + error. + (For bootstrapping purposes, it can also result to just a warning if this + involves a built-in type which has just been introduced.) +
++nonempty_maybe_improper_list(Type) :: nonempty_maybe_improper_list(Type, any()) +nonempty_maybe_improper_list() :: nonempty_maybe_improper_list(any()) ++
+ where the following two types + define the set of Erlang terms one would expect: +
++nonempty_improper_list(Type1, Type2) +nonempty_maybe_improper_list(Type1, Type2) ++
+ Also for convenience, we allow for record notation to be used. + Records are just shorthands for the corresponding tuples. +
++Record :: #Erlang_Atom{} + | #Erlang_Atom{Fields} ++
+ Records have been extended to possibly contain type information.
+ This is described in the sub-section
+ As seen, the basic syntax of a type is an atom followed by closed + parentheses. New types are declared using '-type' compiler attributes + as in the following: +
++-type my_type() :: Type. ++
+ where the type name is an atom (
+ This means that currently general recursive types cannot be defined. + Lifting this restriction is future work. +
++ Type declarations can also be parameterized by including type variables + between the parentheses. The syntax of type variables is the same as + Erlang variables (starts with an upper case letter). + Naturally, these variables can - and should - appear on the RHS of the + definition. A concrete example appears below: +
++-type orddict(Key, Val) :: [{Key, Val}]. ++ +
+ The types of record fields can be specified in the declaration of the + record. The syntax for this is: +
++-record(rec, {field1 :: Type1, field2, field3 :: Type3}). ++
+ For fields without type annotations, their type defaults to any(). + I.e., the above is a shorthand for: +
++-record(rec, {field1 :: Type1, field2 :: any(), field3 :: Type3}). ++
+ In the presence of initial values for fields, + the type must be declared after the initialization as in the following: +
++-record(rec, {field1 = [] :: Type1, field2, field3 = 42 :: Type3}). ++
+ Naturally, the initial values for fields should be compatible
+ with (i.e. a member of) the corresponding types.
+ This is checked by the compiler and results in a compilation error
+ if a violation is detected. For fields without initial values,
+ the singleton type
+-record(rec, {f1 = 42 :: integer(), + f2 :: float(), + f3 :: 'a' | 'b'). + +-record(rec, {f1 = 42 :: integer(), + f2 :: 'undefined' | float(), + f3 :: 'undefined' | 'a' | 'b'). ++
+ For this reason, it is recommended that records contain initializers, + whenever possible. +
++ Any record, containing type information or not, once defined, + can be used as a type using the syntax: +
++#rec{} ++
+ In addition, the record fields can be further specified when using + a record type by adding type information about the field in the following + manner: +
++#rec{some_field :: Type} ++
+ Any unspecified fields are assumed to have the type in the original + record declaration. +
+
+ A contract (or specification) for a function is given using the new
+ compiler attribute
+-spec Module:Function(ArgType1, ..., ArgTypeN) -> ReturnType. ++
+ The arity of the function has to match the number of arguments, + or else a compilation error occurs. +
++ This form can also be used in header files (.hrl) to declare type + information for exported functions. + Then these header files can be included in files that (implicitly or + explicitly) import these functions. +
++ For most uses within a given module, the following shorthand is allowed: +
++-spec Function(ArgType1, ..., ArgTypeN) -> ReturnType. ++
+ Also, for documentation purposes, argument names can be given: +
++-spec Function(ArgName1 :: Type1, ..., ArgNameN :: TypeN) -> RT. ++
+ A function specification can be overloaded.
+ That is, it can have several types, separated by a semicolon (
+-spec foo(T1, T2) -> T3 + ; (T4, T5) -> T6. ++
+ A current restriction, which currently results in a warning + (OBS: not an error) by the compiler, is that the domains of the argument + types cannot be overlapping. + For example, the following specification results in a warning: +
++-spec foo(pos_integer()) -> pos_integer() + ; (integer()) -> integer(). ++
+ Type variables can be used in specifications to specify relations for + the input and output arguments of a function. + For example, the following specification defines the type of a + polymorphic identity function: +
++-spec id(X) -> X. ++
+ However, note that the above specification does not restrict the input + and output type in any way. + We can constrain these types by guard-like subtype constraints: +
++-spec id(X) -> X when is_subtype(X, tuple()). ++
+ and provide bounded quantification. Currently,
+ the
+ The scope of an
+-spec foo({X, integer()}) -> X when is_subtype(X, atom()) + ; ([Y]) -> Y when is_subtype(Y, number()). ++
+ Some functions in Erlang are not meant to return; + either because they define servers or because they are used to + throw exceptions as the function below: +
++my_error(Err) -> erlang:throw({error, Err}). ++
+ For such functions we recommend the use of the special no_return() + type for their "return", via a contract of the form: +
++-spec my_error(term()) -> no_return(). ++