From 57493ef46d92155f0f7223858c4b612b840f485a Mon Sep 17 00:00:00 2001
From: Kostis Sagonas
- 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.)
-
+ 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.)
+
- where the following two types
- define the set of Erlang terms one would expect:
-
+ where the following two types
+ define the set of Erlang terms one would expect:
+
- Also for convenience, we allow for record notation to be used.
- Records are just shorthands for the corresponding tuples.
-
+ Also for convenience, we allow for record notation to be used.
+ Records are just shorthands for the corresponding tuples.
+
+ 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' and '-opaque'
+ compiler attributes as in the following:
+
- 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:
-
- 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:
-
-
-
-
+
+
+
+
nonempty_maybe_improper_list(Type) :: nonempty_maybe_improper_list(Type, any())
nonempty_maybe_improper_list() :: nonempty_maybe_improper_list(any())
-
-
+
+
nonempty_improper_list(Type1, Type2)
nonempty_maybe_improper_list(Type1, Type2)
-
-
+
+
Record :: #Erlang_Atom{}
| #Erlang_Atom{Fields}
+
+
+-type my_struct_type() :: Type.
+-opaque my_opaq_type() :: Type.
--type my_type() :: Type.
-
-
+ where the type name is an atom (
+ 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: -
-++
+ A module can export some types in order to declare that other modules + are allowed to refer to them as remote types. + This declaration has the following form: +
+-export_type([T1/A1, ..., Tk/Ak]). ++ where the Ti's are atoms (the name of the type) and the Ai's are their + arguments. An example is given below: +
+-export_type([my_struct_type/0, orddict/2]). ++ Assuming that these types are exported from module
+mod:my_struct_type() +mod:orddict(atom(), term()) ++ One is not allowed to refer to types which are not declared as exported. + +
+ Types declared as
+ 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: -
-++
+ 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: -
-++
+ 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
++
+ 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'). + 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: -
-+ 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: -
-++
+ 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. -
-+ 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
+++ +Specifications for functions ++ A specification (or contract) for a function is given using the new + compiler attribute
+'-spec' . The general format is as follows: +-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: -
-+++ 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 suffices: +
+-spec Function(ArgType1, ..., ArgTypeN) -> ReturnType. --- Also, for documentation purposes, argument names can be given: -
-+++ 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 (
-; ): -+++ 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: -
-+++ 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: -
-+++ 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: -
-+++ 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
-is_subtype/2 guard is the only guard which can - be used in a'-spec' attribute. -- The scope of an
-is_subtype/2 constraint is the -(...) -> RetType - specification after which it appears. To avoid confusion, - we suggest that different variables are used in different constituents of - an overloaded contract as in the example below: ---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: -
-++ or equivalently by the more succint and more modern form of the above: ++-spec id(X) -> X when X :: tuple(). +++ and provide bounded quantification. Currently, the
+::2> constraint + (the is_subtype/2 guard) is the only guard constraint which can + be used in the'when' part of a'-spec' attribute. ++ The scope of an
+:: constraint is the +(...) -> RetType + specification after which it appears. To avoid confusion, + we suggest that different variables are used in different + constituents of an overloaded contract as in the example below: ++-spec foo({X, integer()}) -> X when X :: atom() + ; ([Y]) -> Y when 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: -
-+++ 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(). --