热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Erlang程序文档(转)

《Erlang程序设计》一书中的附录A指导了程序文档的写法,通过在注释行中添加“spec”或者“type”等标注来对程序中的函数参数、返回值等类型来进行说明。这种方法

  《Erlang程序设计》一书中的附录A指导了程序文档的写法,通过在注释行中添加“@spec”或者“@type”等标注来对程序中的函数参数、返回值等类型来进行说明。这种方法可以使用,但是有新的标注方式,书中没有提到,记录再次备忘。

  文中提到的dialyzer提供了静态代码监测工具,已经在IDEA中使用了,可以帮助检查一些明显的语法错误和手误。

 

  1.函数参数重载的参数声明说明。注意不是参数重载,而是spec声明可以重载。

  2.record用法

 

 

Erlang类型及函数声明规格 Author: Mail: Date: Copyright:
litaocheng
litaocheng@gmail.com
2009.6.8
This document has been placed in the public domain.

Contents

  • 概述
  • 意义
  • 规范
    • 类型及其定义语法
    • 自定义类型定义
    • 在record中使用类型声明
    • 函数规范定义
  • 使用dialyzer进行静态分析
    • 生成plt
    • 使用dialyzer分析
  • 参考
概述

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() %% 最顶层类型&#xff0c;表示任意的Erlang term| none() %% 最底层类型&#xff0c;不包含任何term| pid()| port()| ref()| [] %% nil| Atom| Binary| float()| Fun| Integer| List| Tuple| Union| UserDefined %% described in Section 2Union :: Type1 | Type2Atom :: atom()| Erlang_Atom %% &#39;foo&#39;, &#39;bar&#39;, ...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&#61;contents, Type2&#61;termination| maybe_improper_list(Type1, Type2) %% Type1 and Type2 as aboveTuple :: tuple() %% 表示包含任意元素的tuple| {}| {TList}TList :: Type| Type, TList

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

&#39;_&#39; 可以用来表示任意类型。

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

为了方便&#xff0c;下面是一个内建类型列表

Built-in type Stands for
term()any()
bool()&#39;false&#39; &#39;true&#39;
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()&#39;infinity&#39; non_neg_integer()
no_return()none()

类型定义不可重名&#xff0c;编译器可以进行检测。

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

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中&#xff0c;已经支持record定义中的类型说明

自定义类型定义

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

-type my_type() :: Type.

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

这样递归的类型定义&#xff0c;当前还不支持。

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

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

在record中使用类型声明

我们可以指定record中字段的类型&#xff0c;语法如下:

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

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

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

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

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

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

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

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

-record(rec, {field1 &#61; [] :: Type1, field2, field3 &#61; 42 :: Type3}).

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

-record(rec, {f1 &#61; 42 :: integer(),f2 :: float(),f3 :: &#39;a&#39; | &#39;b&#39;).-record(rec, {f1 &#61; 42 :: integer(),f2 :: &#39;undefined&#39; | float(),f3 :: &#39;undefined&#39; | &#39;a&#39; | &#39;b&#39;).

所以&#xff0c;推荐您在定义record时&#xff0c;指明初始值。

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

#rec{}

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

#rec{some_field :: Type}

没有指明的filed&#xff0c;类型与record定义时指明的类型相同。

函数规范定义

函数规范可以通过新引入的关键字 &#39;spec&#39; 来定义&#xff08;摒弃了旧的 &#64;spec 声明)。 其语法如下:

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

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

在同一个module内部&#xff0c;可以简化为:

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

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

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

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

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

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

-spec id(X) -> X.

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

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

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

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

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

使用dialyzer进行静态分析

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

生成plt

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

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

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

我们也可以随时通过: dialyzer --add_to_plt --plt ~/.dialyzer_plt -c path_to_app 添加应用到既有plt中&#xff0c; 也可以通过: 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后&#xff0c;就可以对我们书写的应用进行静态检查了。

假设我们书写一个简单的module&#xff08;spec/spec.erl):

-module(spec).
-compile([export_all]).
-vsn(&#39;0.1&#39;).-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) &#61;:&#61; Key -> Index;
index4(Key, N, [_H | R], Index) -> index4(Key, N, R, Index &#43; 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) &#43; fa(N-2).-spec some_fun() -> any().
some_fun() ->L &#61; [{bar, 23}, {foo, 33}],lists:keydelete(1, foo, L).

编译spec.erl:

erlc &#43;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 &#39;spec&#39;: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,&#39;foo&#39;,L::[{&#39;bar&#39;,23} | {&#39;foo&#39;,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信息错误&#xff0c;我们进行修正:


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

some_fun中&#xff0c;lists:keydelete/3参数顺序进行修改:

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

重新编译&#xff0c;进行dialyzer分析&#xff0c;提示成功:

litao&#64;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)

参考
[1]EEP 8,Types and function specifications (http://www.erlang.org/eeps/eep-0008.html )
[2]reRestructureText (http://docutils.sourceforge.net/docs/user/rst/quickref.html )
[3]dialyzer (http://www.erlang.org/doc/man/dialyzer.html )


转:https://www.cnblogs.com/moniza/p/3897627.html



推荐阅读
author-avatar
甜甜天蚕宝_431
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有