`
litaocheng
  • 浏览: 337189 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Erlang类型及函数声明规格

阅读更多

Erlang类型及函数声明规格

Author: Mail: Date: Copyright:
litaocheng
litaocheng@gmail.com
2009.6.8
This document has been placed in the public domain.

概述

Erlang为动态语言,变量在运行时动态绑定,这对于我们获取函数的参数及返回值的类型信息具有一定的难度。 为了弥补这个不足,在Erlang中我们可以通过type及spec定义数据类型及函数原型。通过这些信息,我们对函数及调用进行静态检测, 从而发现一些代码中问题。同时,这些信息也便于他人了解函数接口,也可以用来生成文档。

意义

  • 定义各种自定义数据类型
  • 定义函数的参数及返回值
  • dialyzer 进行代码静态分析
  • edoc利用这些信息生成文档

规范

类型及其定义语法

数据类型由一系列Erlang terms组成,其有各种基本数据类型组成(如 integer() , atom() , pid() )。Erlang预定义数据类型代表属于此类型的所有数据,比如 atom() 代表所有的atom类型的数据。

数据类型,由基本数据类型及其他自定义数据类型组成,其范围为对应数据类型的合集。 比如:

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

与:

atom() | integer()

具有相同的含义。

各种类型之间具有一定的层级关系,其中最顶层的 any() 可以代表任何Erlang类型, 而最底层的 none() 表示空的数据类型。

预定义的类型及语法如下:

Type :: any()           %% 最顶层类型,表示任意的Erlang term
     | none()           %% 最底层类型,不包含任何term
     | pid()
     | port()
     | ref()
     | []               %% nil
     | Atom
     | Binary
     | float()
     | Fun
     | Integer
     | List
     | Tuple
     | Union
     | UserDefined      %% described in Section 2

Union :: Type1 | Type2

Atom :: atom()
     | Erlang_Atom      %% 'foo', 'bar', ...

Binary :: binary()                        %% <<_:_ * 8>>
       | <<>>
       | <<_:Erlang_Integer>>            %% Base size
       | <<_:_*Erlang_Integer>>          %% Unit size
       | <<_:Erlang_Integer, _:_*Erlang_Integer>>

Fun :: fun()                             %% 任意函数
    | fun((...) -> Type)                 %% 任意arity, 只定义返回类型
    | fun(() -> Type)
    | fun((TList) -> Type)

Integer :: integer()
        | Erlang_Integer                 %% ..., -1, 0, 1, ... 42 ...
        | Erlang_Integer..Erlang_Integer %% 定义一个整数区间

List :: list(Type)                       %% 格式规范的list (以[]结尾)
     | improper_list(Type1, Type2)       %% Type1=contents, Type2=termination
     | maybe_improper_list(Type1, Type2) %% Type1 and Type2 as above

Tuple :: tuple()                          %% 表示包含任意元素的tuple
      | {}
      | {TList}

TList :: Type
      | Type, TList

由于 lists 经常使用,我们可以将 list(T) 简写为 [T] ,而 [T, ...] 表示一个非空的元素类型为T的规范列表。两者的区别是 [T] 可能为空,而 [T, ...] 至少包含一个元素。

'_' 可以用来表示任意类型。

请注意, list()表示任意类型的list,其等同于 [_]或[any()], 而 [] ,仅仅 表示一个单独的类型即空列表。

为了方便,下面是一个内建类型列表

Built-in type Stands for
term() any()
bool() 'false' | 'true'
byte() 0..255
char() 0..16#10ffff
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()

类型定义不可重名,编译器可以进行检测。

注意 : 还存在一些其他 lists 相关的内建类型,但是因为其名字较长,我们很少使用:

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

我们也可以使用record标记法来表示数据类型:

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

当前R13B中,已经支持record定义中的类型说明

自定义类型定义

通过前一章节的介绍,我们知道基本的类型语法为一个atom紧随一对圆括号。如果我们想 第一个一个新类型,需要使用 'type' 关键字:

-type my_type() :: Type.

my_type为我们自定义的type名称,其必须为atom,Type为先前章节介绍的各种类型, 其可以为内建类型定义,也可以为可见的(已经定义的)自定义数据类型。否则会 编译时保错。

这样递归的类型定义,当前还不支持。

类型定义也可以参数化,我们可以在括号中包含类型,如同Erlang中变量定义, 这个参数必须以大写字母开头,一个简单的例子:

-type orddict(Key, Val) :: [{Key, Val}].

在record中使用类型声明

我们可以指定record中字段的类型,语法如下:

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

如果字段没有指明类型声明,那么默认为 any() . 比如,上面的record定义与此相同:

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

如果我们在定义record的时候,指明了初始值,类型声明必须位于初始值之后:

-record(rec, {field1 = [] :: Type1, field2, field3 = 42 :: Type3})$
我们可以指定record中字段的类型,语法如下::

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

如果字段没有指明类型声明,那么默认为 any() . 比如,上面的record定义与此相同:

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

如果我们在定义record的时候,指明了初始值,类型声明必须位于初始值之后:

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

如果初始值类型与字段的类型声明不一致,会产生一个编译期错误。 filed的默认值为 'undefined' ,因此下面的来个record定义效果相同:

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

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

所以,推荐您在定义record时,指明初始值。

record定义后,我们可以作为一个类型来使用,其用法如下:

#rec{}

在使用recored类型时,我们也可以重新指定某个field的类型:

#rec{some_field :: Type}

没有指明的filed,类型与record定义时指明的类型相同。

函数规范定义

函数规范可以通过新引入的关键字 'spec' 来定义(摒弃了旧的 @spec 声明)。 其语法如下:

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

函数的参数数目必须与函数规范定义相同,否则编译出错。

在同一个module内部,可以简化为:

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

同时,为了便于我们生成文档,我们可以指明参数的名称:

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

函数的spec声明可以重载。通过 ';' 来实现:

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

我们可以通过spec指明函数的输入和输出的某些关系:

-spec id(X) -> X.

但是,对于上面的spec,其对输入输出没有任何限定。我们可以对返回值增加一些类似guard的限定:

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

其表示X为一个tuple类型。目前仅仅支持 is_subtype 是唯一支持的guard。

某些情况下,有些函数是server的主循环,或者忽略返回值,仅仅抛出某个异常,我们可以使用 no_return() 作为返回值类型:

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

使用dialyzer进行静态分析

我们定义了type及spec,我们可以使用 dialyzer 对代码进行静态分析,在运行之前发现 很多低级或者隐藏的错误。

生成plt

为了分析我们的app或者module,我们可以生成一个plt文件(Persistent Lookup Table), 其目的是为了加速我们的代码分析过程,plt内部很多类型及函数信息。

首先我们生成一个常用的plt文件, 其包含了以下lib:erts, kernel, stdlib, mnesia, crypto, sasl, ERL_TOP为erlang的安装目录,各个lib因为erlang版本不同会有所差别,我当前使用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

经过十几分钟的的等待,生成了一个~/.dialyzer_plt文件,在生成plt时,可以通过--output_plt 指定生成的plt的名称。

我们也可以随时通过: dialyzer --add_to_plt --plt ~/.dialyzer_plt -c path_to_app 添加应用到既有plt中, 也可以通过: dialyzer --remove_from_plt --plt ~/.dialyzer_plt -c path_to_app 从已有plt中删除某个应用。

例子:

% 生成plt
dialyzer --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

% 从plt中去处crypto应用
dialyzer --remove_from_plt --plt ~/.dialyzer_plt -c /usr/local/lib/erlang/lib/crypto-1.6/ebin

% 向plt中添加crypto应用
dialyzer --add_to_plt --plt ~/.dialyzer_plt -c /usr/local/lib/erlang/lib/crypto-1.6/ebin

使用dialyzer分析

生成plt后,就可以对我们书写的应用进行静态检查了。

假设我们书写一个简单的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).

编译spec.erl:

erlc +debug_info spec.erl

使用dialyzer进行分析:

dialyzer -r ./spec

显示结果:

Checking whether the PLT /home/litao/.dialyzer_plt is up-to-date... yes
Proceeding 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 return
spec.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.29s
done (warnings were emitted)

我们可以看到,我们的fa/1函数的spec信息错误,我们进行修正:

由
-spec fa( non_neg_integer() ) -> pos_integer().
改为:
-spec fa( N :: atom() ) -> pos_integer().

some_fun中,lists:keydelete/3参数顺序进行修改:

lists:keydelete(1, foo, L).
改为:
lists:keydelete(foo,1, L).

重新编译,进行dialyzer分析,提示成功:

litao@litao:~/erltest$ dialyzer -r ./spec
Checking whether the PLT /home/litao/.dialyzer_plt is up-to-date... yes
Proceeding with analysis... done in 0m0.28s
done (passed successfully)
分享到:
评论
4 楼 Arbow 2009-06-09  
使用dialyzer分析还是太挫了,期待Erlang以后也实现静态类型推导!
看了《TypEr: A Type Annotator of Erlang Code》和 《Practical Type Inference Based on Success Typings》 这Paper,感觉还是有点可能的
3 楼 jackyz 2009-06-09  
已转。
编译阶段不对类型系统做检查。
2 楼 litaocheng 2009-06-09  
恩,dialyzer是咱们的好伙伴
1 楼 mryufeng 2009-06-09  
dialyzer很好用 伴我解决了很多问题

相关推荐

    Erlang开发及应用

    Erlang开发及应用

    erlang编程 Introducing Erlang

    Erlang是一种函数式编程语言,由爱立信在1986年开发,主要用于构建高可用性、容错性和并发性的分布式系统。"Introducing Erlang"是Simon St. Laurent撰写的一本入门级教程,旨在帮助初学者理解和掌握Erlang的核心...

    erlang文献及资料汇总

    erlang文献及资料汇总 入门资料: erlang中文手册(R11B 文档译文,最适合入门) erlang位运算与二进制解析 erlang二进制高效编程 erlang异常处理详解 开发经验: 面对软件错误构建可靠的分布式系统 编写分布式的 ...

    erlang资源

    1. **Erlang语法**:涵盖基本的变量、数据类型(如原子、列表、元组和二进制)、控制结构(如case表达式和if语句)以及函数定义。 2. **函数式编程概念**:Erlang是纯函数式语言,书中可能会介绍函数式编程的基本...

    Erlang程序设计及源码

    对于初学者,可以通过阅读《Erlang程序设计》这本书了解基本语法、数据类型、控制结构、模式匹配以及Erlang的并发特性。同时,结合源码分析,可以提升实战能力。 总之,这份资源为Erlang初学者提供了全面的学习...

    《Programming Erlang》

    Erlang是一种静态类型、函数式编程语言,最初由爱立信开发,用于构建电信系统,因其在处理高并发、分布式计算和容错性方面的优势,现在也被广泛应用于互联网和实时系统。 Erlang的关键特性包括: 1. **并行计算**...

    erlang25.0 windows版本

    2. **API更新**:可能对Erlang的内置函数或模块进行增强,提供新的功能或修复已知问题。 3. **兼容性提升**:与先前版本相比,25.0可能增强了与其他软件或框架的兼容性。 4. **错误修复**:解决上一版本中的已知问题...

    erlang趣学指南

    Erlang是一种动态类型语言,类型检查在运行时进行,这意味着你不需要在代码中声明变量的数据类型。不过,理解Erlang的类型系统对于编写高效且可维护的代码至关重要。 递归在Erlang程序设计中占有重要地位,因为...

    erlang_版本24.3.4.4

    Erlang是一种面向并发的、函数式编程语言,由瑞典电信设备制造商Ericsson开发,主要用于构建高可用性、分布式和实时系统。版本24.3.4.4是Erlang的一个更新版本,包含了对先前版本的改进和修复。Erlang以其强大的错误...

    xiandiao_erlang_Erlang课后习题_

    1. **函数式编程**:Erlang基于函数式编程范式,强调无副作用的纯函数,以及通过数据不可变性来简化并发处理。在Erlang中,程序是由一系列相互独立的函数构成的,它们可以并行执行,提高了系统的性能。 2. **并发与...

    windows下安装Erlang环境

    **函数式编程**:Erlang是一种函数式语言,函数无副作用,强调纯函数和不可变数据,这有助于编写简洁、可预测的代码。 **动态类型**:Erlang是动态类型的,变量的类型在运行时确定,提供了灵活性。 **及早求值或...

    Erlang_CNode用户指

    3. **Erlang数据类型和API**:介绍CNode API,包括如何在C中表示和操作Erlang的数据类型(如整数、原子、列表等),以及如何调用Erlang函数和处理返回结果。 4. **进程通信**:阐述如何在CNode中创建Erlang进程,...

    erlang23.2版windows64位.zip

    4. **动态类型**:Erlang采用动态类型,允许快速开发和迭代,但同时也可能导致运行时错误。 5. **函数式编程**:Erlang是函数式的,强调无副作用的纯函数,以及数据不可变性,这有助于编写简洁、易于理解和测试的...

    Erlang趣学指南

    (494页带目录的高清扫描版) 这是一本讲解Erlang编程语言的入门指南,内容通俗...内容涉及模块、函数、类型、递归、错误和异常、常用数据结构、并行编程、多处理、OTP、事件处理,以及所有Erlang的重要特性和强大功能。

    两本erlang电子书

    此外,这本书还会讨论Erlang的模块系统、类型系统以及如何利用REPL(Read-Eval-Print Loop)进行调试和测试。 这两本书的结合,为学习Erlang提供了全面的视角。《Erlang and OTP in Action》以其实践导向,帮助...

    Erlang入门

    Erlang是一种面向并发的、函数式编程语言,由瑞典电信设备制造商Ericsson在1986年开发,主要用于构建高度可靠和可扩展的分布式系统。这本书"Introducing Erlang"引领读者踏入这个独特的编程世界,揭示了Erlang的核心...

    erlang22最新下载包

    Erlang是一种面向并发的、函数式编程语言,由瑞典电信设备制造商Ericsson为了实现分布式实时、高可靠性系统而开发。Erlang以其强大的并行处理能力、容错性和易于构建大规模分布式系统的特点,在电信、金融和互联网等...

    erlang programming

    编程+Erlang.pdf可能会详细介绍Erlang的基础语法、数据类型、过程和模块等概念。 2. **并发编程**:Erlang的并发模型是其独特之处。它通过轻量级进程(Lightweight Processes, LWP)实现并发,进程间通信(Inter-...

    erlang9.rar

    Erlang语言的核心特点包括轻量级进程(Erlang中的进程与操作系统进程不同,它们更轻便且能快速切换)、模式匹配、函数式编程和热代码替换等。这些特性使得Erlang在处理高并发场景下表现出色,例如在电信、网络设备和...

Global site tag (gtag.js) - Google Analytics