Erlang Types and Function Specifications

-opaque -type -export_type -spec

Erlang 类型及函数约束规范

Erlang是一种动态类型语言,不过可以通过一些特殊的语法声明一个由Erlang term组成的类型集合,这个集合可用于约束某字段类型或者某函数的参数类型和返回值类型。
类型及函数约束规范可用于:记录接口、使用代码静态类型分析工具(Dialyzer)、生成程序文档(EDoc)。

类型约束语法

所有的类型约束都是由内置类型、自定义类型或者类型实例构建;
整数和原子类型,允许使用类型实例来定义;
当类型约束中既存在类型实例又存在预定义类型,类型实例将被预定义类型覆盖。

1
2
3
atom() | 'bar' | integer() | 56
%% 等同于
atom() | integer()

any()可覆盖任何类型
none()可被任何类型覆盖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
%% 顶部类型,可覆盖任何类型
Type :: any()
%% 底部类型,空
| none()
%% 进程标识符
| pid()
%% 端口 like #Port<0.638>
| port()
%% Unique values in Erlang runtime system
| reference()
%% 原子类型
| atom()
%% 原子值 'foo' 'bar'...
| 'atom'
%% 二进制数据
| binary()
%% 空二进制数据
| <<>>
%% M is a positive integer
| <<_:M>>
%% N is a positive integer
| <<_:_*N>>
%% M N是正整数
| <<_:M, _:_*N>>
%% 任何整数
| integer()
%% 整数的实例
| 38
%% 整数范围 like 1..12
| N..M
%% 大于等于0的整数 非负
| non_neg_integer()
%% 大于0的整数
| pos_integer()
%% 小于0的整数
| neg_integer()
%% 任何浮点数
| float()
%% 任何函数
| fun()
%% 任何没有参数、返回Type类型的函数 like fun(() -> integer())
| fun(() -> Type)
%% 任何有参数、返回Type类型的函数 like fun((...) -> boolean())
| fun((...) -> Type)
%% 给定 参数数量类型、返回值类型 的函数 like fun((atom(), float()) -> any())
| fun((Type1, Type2,..,TypeN) -> Type)
%% 任何tuple
| tuple()
%% 空tuple
| {}
%% 任何大小类型已知的tuple
| {Type1, Type2, Type3}
%% nil 空列表
| []
%% 由Type类型组成的列表,包含空列表 like [tuple()]
| [Type]
%% 等同于 [Type]
| list(Type)
%% [Type1, Type1,... | Type2] 包含空列表
%% like improper_list(integer(), atom()) 可以匹配 [1, 2 | a]
| improper_list(Type1, Type2)
%% 可以匹配 improper_list(Type1, Type2) 和 list(Type1)
| maybe_improper_list(Type1, Type2)
%% [Type1, Type1,... | Type2] 不包含空列表
| nonempty_improper_list(Type1, Type2)
%% [Type] 不可为空
| [Type, ...]
%% 等同于 [Type, ...]
| nonempty_list(Type)
%% 任意大小的map
| map()
%% 空map
| #{}
%% 非空map
| #{PairList}
%% 类型集合
| Union
%% 自定义类型
| UserDefined
PairList :: Pair
| Pair, PairList
Pair :: Type := Type
| Type => Type
Union :: Type1 | Type2

内置类型

Build-in type Defined as
term() any()
binary() <<_:_*8>>
bitstring() <<_:_*1>>
boolean() ‘true’ | ‘false’
byte() 0..255
char() 0..16#10ffff
nil() []
number() integer() | float()
list() [any()]
maybe_improper_list() maybe_improper_list(any(), any())
nonempty_maybe_improper_list() nonempty_maybe_improper_list(any(), any())
nonempty_list() nonempty_list(any())
string() [char()]
nonempty_string() [char(), ...]
iodata() iolist() | binary()
iolist() maybe_improper_list(byte() | binary() | iolist(), binary() | [])
function() fun()
module() atom()
mfa() {module(), atom(), arity()}
arity() 0..255
identifier() pid() | port() | reference()
node() atom()
timeout() ‘infinity’ | non_neg_integer()
no_return() none()一直循环不返回的函数

在record声明中声明field类型约束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
%% 没有类型约束的filed,其默认类型为 any()
-record(RecordName, {field1 :: Type1, field2, field3 :: Type3})
%% 等同于
-record(RecordName, {field1 :: Type1, field2 :: any(), field3 :: Type3})
%% 栗子
-record(rec, {
f1 = 18 :: integer()
,f2 :: [tuple()]
,f3
}).
%% 在R19之前
%% 未初始化的field的'undefined'会被添加到类型约束中
%% 以下两种声明,在R19之前,是相同的
-record(rec, {
f1 = 18 :: integer()
,f2 :: float()
,f3 :: 'a' | 'b'
}).
-record(rec, {
f1 = 18 :: integer()
,f2 :: 'undefined' | float()
,f3 :: 'undefined' | 'a' | 'b'
}).

无论record声明是否包含类型约束,record被声明后,就可以使用#rec{}作为约束类型
同样可以使用#rec{f1 :: non_neg_integer()}对field类型进行约束

声明自定义类型约束

1
2
3
4
5
6
7
8
9
10
11
12
%% 自定义类型约束声明
-type type1() :: integer() | float().
%% 不透明自定义类型约束声明
-opaque type2() :: float() | char().
-type orddict(Key, Val) :: [{Key, Val}].
%% 将本模块内声明的自定义类型约束导出
-export_type([type1/0, orddict/2]).
%% 其它模块使用 xx模块中声明的orddict自定义类型约束
xx:orddict(atom(), integer())

无论是官方文档还是各类教程都不建议使用opaque声明自定义类型约束,用type就好:)

声明函数参数与返回值约束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
-spec Module:Function(ArgType1, ArgType2, .., ArgTypeN) -> ReturnType.
-spec Function(ArgType1, ArgType2, .., ArgTypeN) -> ReturnType.
-spec Function(ArgType1 :: Type1, .., ArgTypeN :: TypeN) -> ReturnType.
%% ; 分号放在下一行仅仅是为了代码的可读性
-spec foo(T1, T2) -> T3
; (T4, T5) -> T6.
%% 这个编译器会警告
%% 因为 pos_integer() 和 integer() 覆盖域有重叠
-spec foo(pos_integer()) -> pos_integer()
; (integer()) -> integer().
%% 这个约束了ArgType和ReturnType要保持一致
-spec id(X) -> X.
-spec id(tuple()) -> tuple()
-spec id(X) -> X when X :: tuple().
%% Arg :: {atom(), integer()} | [number()]
-spec foo({X, integer()}) -> X when X :: atom()
; ([Y]) -> Y when Y :: number().
%% Record类型约束
-record(rec, {key = 0 :: integer()}).
-spec foo(#rec{key :: non_neg_integer()}) -> boolean().
%% 无返回约束
-spec m_error(term()) -> no_return().
m_error(Err) ->
erlang:throw({error, Err}).