Erlang type and function declaration Specification

Source: Internet
Author: User
Tags define function

 

Overview

It indicates that Erlang is a dynamic language and variables are dynamically bound at runtime, which is difficult for us to obtain the type information of function parameters and returned values. In Erlang, we can use type and spec to define the data type and function prototype. Through this information, we conduct static checks on functions and calls to find some problems in code. At the same time, this information is also easy for others to understand function interfaces and can be used to generate documents.

Meaning
  • Define various Custom Data Types
  • Define function parameters and return values
  • Static code analysis by dialyzer
  • Edoc uses this information to generate a document
Standard Type and definition syntax

Data types are composed of a series of Erlang terms, which are composed of various basic data types (suchINTEGER (),
Atom (),
PID ()). Erlang pre-defined data types represent all data of this type, such
Atom ()Indicates all atom data types.

Data type, which consists of basic data types and other custom data types. Its range is a collection of corresponding data types. For example:

atom() | 'bar' | integer() | 42

And:

atom() | integer()

Has the same meaning.

There is a hierarchical relationship between various types, among which the top-levelAny ()Can represent any Erlang type, and the underlyingNone ()Indicates the empty data type.

The predefined types and syntax are as follows:

Type: Any () % top-level type, indicating any Erlang term | none () % bottom-level type, excluding any term | PID () | Port () | REF () | [] % nil | atom | binary | float () | fun | integer | list | tuple | Union | userdefined % described in section 2 Union :: type1 | type2atom: Atom () | erlang_atom % 'foo', 'bar ',... binary: binary () % <_: _ * 8 >|<>>|<<_: erlang_integer >%% base size |< _: _ * erlang_integer >>%% unit size | <_: erlang_integer, _: _ * erlang_integer> fun: Fun () % arbitrary function | fun ((...) -> type) % Any Arity. Only the return type is defined. | fun ()-> type) | fun (tlist)-> type) integer: INTEGER () | erlang_integer % ..., -1, 0, 1 ,... 42... | erlang_integer .. erlang_integer % defines an Integer Range list: List (type) % formatted standard list (ended with []) | improper_list (type1, type2) % type1 = contents, type2 = termination | maybe_improper_list (type1, type2) % type1 and type2 as abovetuple: tuple () % Represents the tuple containing any element |{}| {tlist} tlist :: type | type, tlist

Because
Lists is often used. We can abbreviated list (T)[T], And[T,...]A list of non-empty elements whose type is T. The difference between the two is that [T] may be null, while [T,...] contains at least one element.

'_'Can be used to represent any type.

Note that list () indicates any type of list, which is equivalent to [_] or [any ()], while[], Indicating a separate type, that is, an empty list.

For convenience, the following is a list of built-in types

Built-in type Stands
Term () Any ()
Bool () 'False'
| 'True'
Byte () 0... 255
Char () 0 .. 16 #10 FFFF
Non_neg_integer () 0 ..
Pos_integer () 1 ..
Neg_integer () ...-1
Number () INTEGER ()
| Float ()
List () [Any ()]
Maybe_improper_list () Maybe_improper_list (any (), any ())
Maybe_improper_list (t) Maybe_improper_list (t,
Any ())
String () [Char ()]
Nonempty_string () [Char (),...]
Iolist ()
Maybe_improper_list (
Char ()
| Binary () | Iolist (), binary ()
| [])
Module () Atom ()
MFA () {Atom (), Atom (), byte ()}
Node () Atom ()
Timeout () 'Infinity'
| Non_neg_integer ()
No_return () None ()

The type definition cannot have duplicate names, and the compiler can detect them.

Note:: Some Other
Built-in types related to lists, but because of its long name, we seldom use:

nonempty_maybe_improper_list(Type) :: nonempty_maybe_improper_list(Type, any())nonempty_maybe_improper_list() :: nonempty_maybe_improper_list(any())

We can also use the record flag to indicate the Data Type:

Record :: #Erlang_Atom{}        | #Erlang_Atom{Fields}

Currently, the type description in record definition is supported in r13b.

Custom Type Definition

Based on the introduction in the previous chapter, we know that the basic type syntax is an atom followed by a pair of parentheses. If we want to use the first new type, we need to use'Type'Keywords:

-type my_type() :: Type.

My_type is our custom type name. It must be atom. type is the type described in the previous chapter. It can be built-in or visible (defined) custom data type. Otherwise, errors will be saved during compilation.

This recursive type definition is not currently supported.

The type definition can also be parameterized. We can include the type in parentheses, just like the variable definition in Erlang. This parameter must begin with an uppercase letter. A simple example:

-type orddict(Key, Val) :: [{Key, Val}].
Use Type Declaration in record

We can specify the field type in the record. The syntax is as follows:

-record(rec, {field1 :: Type1, field2, field3 :: Type3}).

If the field does not specify the type declaration, the default value isAny (). For example, the above record definition is the same as this:

-record(rec, {field1 :: Type1, field2 :: any(), field3 :: Type3}).

If we specify the initial value when defining a record, the type declaration must be placed after the initial value:

-Record (REC, {field1 = []: type1, field2, field3 = 42: type3}) $ you can specify the field type in record. The syntax is as follows :: -Record (REC, {field1: type1, field2, field3: type3 }).

If the field does not specify the type declaration, the default value isAny (). For example, the above record definition is the same as this:

-record(rec, {field1 :: Type1, field2 :: any(), field3 :: Type3}).

If we specify the initial value when defining a record, the type declaration must be placed after the initial value:

-record(rec, {field1 = [] :: Type1, field2, field3 = 42 :: Type3}).

If the initial value type is different from the field type declaration, a compilation error occurs. The default value of filed is'Undefined'So the following record defines the same effect:

-record(rec, {f1 = 42 :: integer(),                f2      :: float(),                f3      :: 'a' | 'b').-record(rec, {f1 = 42 :: integer(),                f2      :: 'undefined' | float(),                f3      :: 'undefined' | 'a' | 'b').

Therefore, we recommend that you specify the initial value when defining a record.

After the record is defined, we can use it as a type. Its usage is as follows:

#rec{}

When using the recored type, we can also re-specify the type of a field:

#rec{some_field :: Type}

The specified filed is not specified. The type is the same as that specified in the record definition.

Function Specification Definition

The Function Specification can use the newly introduced keywords'Spec'To define (discard the old
@ SpecStatement ). The syntax is as follows:

-spec Module:Function(ArgType1, ..., ArgTypeN) -> ReturnType.

The number of parameters of a function must be the same as that defined in the Function Specification. Otherwise, an error occurs during compilation.

In the same module, it can be simplified:

-spec Function(ArgType1, ..., ArgTypeN) -> ReturnType.

In addition, to facilitate document generation, we can specify the parameter name:

-spec Function(ArgName1 :: Type1, ..., ArgNameN :: TypeN) -> RT.

The function spec declaration can be reloaded. Pass';'To achieve:

-spec foo(pos_integer()) -> pos_integer()           ; (integer()) -> integer().

We can use spec to specify the relationship between input and output of a function:

-spec id(X) -> X.

However, for the above spec, there is no limit on the input and output. We can add some limits similar to guard to the return value:

-spec id(X) -> X when is_subtype(X, tuple()).

It indicates that X is a tuple type. Currently onlyIs_subtypeIs the only supported guard.

In some cases, some functions are the main loop of the server, or the return value is ignored, and only an exception is thrown. We can useNo_return ()Return Value Type:

-spec my_error(term()) -> no_return().my_error(Err) -> erlang:throw({error, Err}).
Static analysis using dialyzer

We have defined the type and spec, and we can use
Dialyzer performs static analysis on the Code and finds many low-level or hidden errors before running the code.

Generate PLT

To analyze our app or module, we can generate a PLT file (persistent lookup table) to accelerate the code analysis process. Many types and function information in Plt are involved.

First, we generate a common PLT file, which contains the following Lib: ERTs, kernel, stdlib, mnesia, crypto, SASL, and erl_top: Erlang installation directory, different lib versions vary depending on Erlang versions. Currently, I use r13b (ERL 5.7.1 ):

dialyzer --build_plt -r $ERL_TOP/lib/erts-5.7.1/ebin \           $ERL_TOP/lib/kernel-2.13.1/ebin \           $ERL_TOP/lib/stdlib-1.16.1/ebin \           $ERL_TOP/lib/mnesia-4.4.9/ebin \           $ERL_TOP/lib/crypto-1.6/ebin \           $ERL_TOP/lib/sasl-2.1.6/ebin

After ten minutes of waiting, A ~ is generated ~ /. Dialyzer_plt file. When creating a PLT, you can use -- output_plt to specify the name of the generated PLT.

We can also use the following methods at any time:Dialyzer
-- Add_to_plt -- PLT ~ /. Dialyzer_plt-c
Path_to_appTo add an application to an existing PLT, you can also use:Dialyzer
-- Remove_from_plt -- PLT
~ /. Dialyzer_plt-c path_to_app
Delete an application from an existing PLT.

Example:

% Generate pltdialyzer -- build_plt-r/usr/local/lib/Erlang/lib/erts-5.7.1/Ebin \/usr/local/lib/Erlang/lib/kernel-2.13.1/Ebin \/ usr/local/lib/Erlang/lib/stdlib-1.16.1/Ebin \/usr/local/lib/Erlang/lib/mnesia-4.4.9/Ebin \/usr/local/lib/Erlang/lib/ crypto-1.6/Ebin \/usr/local/lib/Erlang/lib/sasl-2.1.6/Ebin % from PLT crypto application dialyzer -- remove_from_plt -- PLT ~ /. Dialyzer_plt-C/usr/local/lib/Erlang/lib/crypto-1.6/Ebin % add crypto application dialyzer -- add_to_plt -- PLT ~ to PLT ~ /. Dialyzer_plt-C/usr/local/lib/Erlang/lib/crypto-1.6/Ebin
Analysis Using dialyzer

After the PLT is generated, you can perform static checks on the applications we write.

Suppose we write a simple module (SPEC/spec. erl ):

-module(spec).-compile([export_all]).-vsn('0.1').-spec index(any(), pos_integer(), [any()]) -> non_neg_integer().index(Key, N, TupleList) ->   index4(Key, N, TupleList, 0).index4(_Key, _N, [], _Index) -> 0;index4(Key, N, [H | _R], Index) when element(N, H) =:= Key -> Index;index4(Key, N, [_H | R], Index) -> index4(Key, N, R, Index + 1).% correct:%-spec fa( non_neg_integer() ) -> pos_integer().% invalid:-spec fa( N :: atom() ) -> pos_integer().fa(0) -> 1;fa(1) -> 1;fa(N) -> fa(N-1) + fa(N-2).-spec some_fun() -> any().some_fun() ->   L = [{bar, 23}, {foo, 33}],   lists:keydelete(1, foo, L).

Compile spec. erl:

erlc +debug_info spec.erl

Use dialyzer for analysis:

dialyzer -r ./spec

Display result:

Checking whether the PLT /home/litao/.dialyzer_plt is up-to-date... yesProceeding with analysis...spec.erl:15: Invalid type specification for function 'spec':fa/1. The success typing is (non_neg_integer()) -> pos_integer()spec.erl:22: Function some_fun/0 has no local returnspec.erl:24: The call lists:keydelete(1,'foo',L::[{'bar',23} | {'foo',33},...]) will never return since it differs in argument position 2 from the success typing arguments: (any(),pos_integer(),maybe_improper_list())done in 0m0.29sdone (warnings were emitted)

We can see that the spec information of our fa/1 function is incorrect and we can fix it:

Changed from-spec FA (non_neg_integer ()-> pos_integer (). To-spec FA (N: Atom ()-> pos_integer ().

In some_fun, the lists: keydelete/3 Parameter order is modified:

Lists: keydelete (1, Foo, L). changed to: Lists: keydelete (Foo, 1, L ).

Re-compile and perform dialyzer analysis. The following message is displayed:

litao@litao:~/erltest$ dialyzer -r ./specChecking whether the PLT /home/litao/.dialyzer_plt is up-to-date... yesProceeding with analysis... done in 0m0.28sdone (passed successfully)

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.