一、指定数据和函数类型
-module(walks).
-export([]).
%定义区
-type point() :: {integer(),integer()}.
-spec plan_route(Start::point(),End::point())-> route().
-type direction() :: east|west|north|south|none.
-type route() :: {go,direction(),point()}.
-type angle() :: Degrees::0..360.
-spec getXDict(integer())->direction().
-spec getYDict(integer())->direction().
-spec getDict(integer(),integer()) - > [direction()].
%---------------
getDict(X,Y)->
[getXDict(X),getYDict(Y)].
getXDict(P)->
if
P=:=0 -> none;
P>0 -> west;
P<0 -> east
end.
getYDict(P)->
if
P=:=0 -> none;
P>0 -> south;
P<0 -> north
end.
plan_route(S,E)->
{Xa,Ya}=S,
{Xb,Yb}=E,
{go,getDict(Xa-Xb,Ya-Yb),E}.
我们来拿上面的代码做例子:
-
-type point() :: {integer(),integer()}.
这一个定义的意思是定义类型point()元组{整数,整数} Ps:integer()为整数类型,其中Start::和From::只是给参数一个注释,方便看代码 -
-type direction() :: east|west|north|south|none.
这一个定义的意思是定义类型direction()为后面5个原子其中之一 -
-spec plan_route(point(),point())-> route().
这一个定义的意思是定义函数的两个参数都为point()类型,返回值是route()类型 -
-spec getDict(integer(),integer()) - > [direction()].
这一个定义的意思是定义函数getDict的两个参数都为integer()类型,返回值是成员为direction()的列表 -
-type angle() :: Degrees::0..360.
这一个定义的意思是定义类型angle()为0-360的取值范围
看完这个例子你应该可以掌握大部分的类型定义方法了
二、Erlang的类型表示法
1.类型的语法
Type:: any() | none() | pid() | port() | reference() | [] | Atom | binary() | float() | Fun | Integer | [Type] | Tuple | Union | UserDefined
Union:: Type1 | Type2 | ....
Atom:: atom() | Erlang_Atom
Integer:: integer() | Min..Max
Fun:: fun() | fun((...) -> Type)
Tuple:: tuple() | {T1,T2,...,Tn}
以上就包含了所有Erlang的内置类型和语法了,特别说明如下:
- any() : 这个类型是任意Erlang数据类型
- none() : 这个类型是指永不返回的函数类型
- [Type] : 这个是指由Type类型构成的列表
2.指定函数的输入和输出类型
编写规范如下:
-spec functionName(T1,T2,..,Tn) -> Tret when
T1 :: Type1,
T2 :: Type2,
...
Tret :: TypeRet.
3.导出类型、本地类型和透明类型
-module(a)
-type rich_text() :: [{front(),char()}].
-type font() :: integer().
-type for_example(integer(),integer()):: [ {Key,Val} ].
-opaque rich_text2() :: [{front(),char()}].
-export_type([rich_text()/0,font()/0,for_example()/2]).
-module(b)
-spec rich_text_length(a:rich_text())->integer().
通过上面两个例子,我们可以了解到一些东西:
a.导出变量类型:
-export_type([type/typeVarNum]).
type是类型,typeVarNum是类型变量的数量,如果你不知道是什么,我们回滚到上面的代码,有一个for_example变量,他有一个Key,和Val 这两个其实是类型的变量,for_example是由元组{Key类型的变量,Val类型的变量}组成的[{Key,Val}]列表
b.使用导入的类型
ModName:TypeName()
这就不多说了
c.不透明类型
什么是不透明类型?就是你在a定义的不透明类型rich_text2(),在b中使用的时候,比如说拿去匹配,编译器会告诉你:抽象违规(abstraction violation),因为别人是不透明的,b不可能会知道rich_text2()的结构是怎样的(虽然你自己知道),所以就会匹配错误。
定义方法:
- opaque typename():: xxxx
三、dialyzer差错分析
1.第一次运行dialyzer
跟着他的提示运行:
dialyzer --build_plt --apps erts kernel stdlib mnesia
就okay了,大概花个两分钟就完成了,这个步骤其实就是生成 erts kernel stdlib mnesia的PLT(持久性查询表)的操作
2.使用dialyzer分析erl源码
在学习类型的定义与使用的时候,我还在想这样定义有什么用呢?直到我开始学习dialyzer才知道:哦,原来是为了检查源代码中有没有错误(错误使用内置函数的返回值,内置函数的错误参数,错误的程序逻辑),这让我想起了我写C++的日子,用着VS2017享受着Intellicode给我自动提醒有没有哪里有错误,反过头来看,erlang好像还没有这么便捷的工具。
回到正题,怎么样使用dialyzer分析源码呢,其实很简单,cmd -> dialyzer ErlSrc
就可以完成这个工作
3.干扰dialyzer的事物
- 避免-compile(export_all)的使用
-
对导出函数的所有参数越规范越好
这个规范是指,对类型的范围越精确越好,比如整数有正负之分
-为记录定义里的所有元素都提供默认参数
-把匿名变量用作函数的参数经常会导致结果类型不如你想得这么精确,尽量添加限制
四、typer类型推断
有时候dialyzer生成的错误报告,你会看的一头雾水,这个时候你就需要用typer来看看系统是怎么理解你的变量类型的,他会打印出你所有的函数类型定义-spec xxx
网友评论