-module(hong).
-export([test/0]).
-define(add(X,Y),{X,Y,X+Y}).
-define(Key,55).
test()->
?add(?Key,?Key).
通过使用宏定义可以实现模块切换或者全局变量的定义,此外还有一些默认宏定义:
?FILE //获得文件名"hong,erl"
?MODULE //获得模块名"hong"
?LINE //显示宏所在的行数,如这里我在第五行写的宏,所以显示5
通过使用宏控制可以确定什么会被定义什么不会
-ifdef(Key).
-define(ga(X,Y),X+Y).
-else.
-define(ga(X,Y),X-Y).
-endif.
test()->
?ga(6,5).
因为没有定义Key,所以会执行X-Y的代码,所以ga宏就变成减法。除此之外还有:
-undef(Key). //取消宏定义
-ifdef(Key). //当Key存在才成立
-ifndef(Key). //当Key不存在才成立
-else. //在if语句之后,如果条件为假的执行内容
-endif. //告知if语句结束
fun({X,Y})->
fun2({X,Y}).
要注意这里fun的参数中的XY在传递到fun2时候,会自动重建成新的数据类型,所以要避免这种资源浪费的情况要这么做:
fun({a,{b,c}=Key,d}=Key2)->
fun2(Key),
fun3(Key2).
erlang中整数的长短只受到内存的限制,其表示手段有三种:
传统写法,即直接写具体数字;
K进制手法,即K#123456,K为进制
$写法,即ASCII的字符整数代码
浮点由正负号+整数+小数点+分数+指数组成,绝对值范围为10的-323次方到308次方
:
#
(正负号)+、-、bnot、not
/、*、div、rem、band、and //左
+、-、bor、bxor、bsl、bsr、or、xor //左
++、-- //右
==、/=、=<、<、>=、>、=:=、=/=
andalso
orelse
= ! //右
catch
在进程中内嵌有一个进程独享的数据存储区域,其是一个关联数组(映射组)
put(Key,Value) //向Key中添加一个Key和Value组合,
//如果有旧的值会返回,否则返回undefined
get(Key) //查找Key,假如存在返回值否则undefined
get() //返回整个字典,以{Key,Value}所构成的列表
get_keys(Value) //返回所有value为指定值的键列表
erase(Key) //返回Key的关联值,并删除键值对,如果不存在返回undefined
erase() //清空字典,并将旧数据用{Key,Value}所构成的列表返回
ps:如果使用进程字典会导致可变变量出现,这会导致erlang独特的避免空指针失效,给调试带来难度,所以除非必要不要使用。此外如果一定要使用,最好用于一次性写入的变量。
由内置函数所创建,塞入数据中作为一个锚点而存在,用于后期判断是否相等。
number
此外要注意,当比较数为数字时候,会进行转换,即假如整数和浮点比较,会转换为浮点再进行比较。所以假如要判断相等,最好不要使用==,而是使用=:=因为其还会判断数据类型是否相等。同样这也适用于/=和=/=。
一般下划线变量有两个作用,第一个是抵消一个可能会出现错误的地方,即你希望增加可读性,但是并不希望实际使用该变量的时候。第二种情况是在调试程序的时候,避免因为注释所导致的报错而无法正常编译。在实际使用中要避免让下划线变量担任任务,因为这会导致程序出现bug时候很难调试。
dialyzer是erlang内置的规范检查器,通过以下方式初始化:
dialyzer --build_plt --apps erts kernel stdlib
初始化完毕就可以用其来检查是否规范:
dialyzer hong.erl
要注意的是,想要检查发挥作用必须规范的书写类型规范
-type TypeName(Var1,...VarN) :: Type.
类型语法的目的就是声明一种TypeName的数据类型,并确定其参数变量和类型表达式
-spec funName(T1,...TN) -> Res when
T1 :: Type,
...
函数规范的目的是声明函数funName的参数以及返回值的类型,同时也为说明文档提供帮助
-module(hong).
-export([student_achievement_calculation/2]).
-spec student_achievement_calculation(Config,Data)->Results when
Config :: config(),
Data :: data(),
Results :: results().
-type config() :: {ave|weight,Proportion::0..5}.
-type data() :: {Name::string(),
[{chinese|english|math,Score::float()}]}.
-type results() :: {Name::string(),Results::float()}.
student_achievement_calculation(Config,{Name,Score})->
case Config of
{ave,Key} ->
[{_,One}|_] = Score,
{Name,One*Key};
{weight,Key}->
[{_,One}|_] = Score,
{Name,One/Key}
end.
代码中的函数是一个学生成绩的算法,会根据Config中的信息选择如何处理Score中的数据。
这里对函数声明以及对应的参数和返回值都做出了详细的规范,要注意的是假如函数实现没遵照规范并不会导致程序报错,但是在使用dialyzer对脚本进行检查的时候会出现报错信息。这里执行下dialyzer来检查脚本:
> dialyzer hong.erl
Checking whether the PLT c:/Users/Administrator/.dialyzer_plt is up-to-date... yes
Proceeding with analysis... done in 0m0.17s
看起来没什么问题
-type term() :: any().
-type boolean() :: true | false.
-type byte() :: 0..255.
-type char() :: 0..16#10ffff.
-type number() :: integer() | float().
-type list() :: [any()].
-type maybe_improper_list() :: maybe_improper_list(any(),any()).
-type maybe_improper_list(T) :: maybe_improper_list(T,any()).
-type string() :: [char()].
-type nonempty_string() :: [char(),...].
-type iolist() :: maybe_improper_list(byte()|binary()|iolist(),
binary()|[]).
-type module() :: atom().
-type mfa() :: {atom(),atom(),atom()}.
-type node() :: atom().
-type timeout() :: infinity | non_neg_integer().
-type no_return() :: none().
fun((config())->bool()) //函数同时接受config()到bool()类型参数
A | B //这个位置可以是A也可以是B
0..255 //接受从0到255的数字
Key::bool() //声明会有一个Key的值,其类型为bool
[{a,B}] //声明其是一个数组,并且内部的元素是{a,B}格式的
any() //任意值
none() //不返回任意值
pid()|port()|reference()|[]|atom()|binary()|float()|fun()|interger()|
[Type]|tuple()|Union|UserDefined //可以使用各种类型
有些时候会希望类型被其他erl模块调用或者不希望其知道类型具体内容,这时候要这么写
-module(hong).
-type a() :: {key,integer()}.
-opaque b() :: {a(),bool()}. //不透明类型
-export_type([a/0,b/0]). //对外暴露
这时候其他文件调用就可以获得其数据类型了。这时候你拆分a获得数据可以,但是假如你希望拆分b来获得里面数据时候,就会发生抽象违规。如果你用dialyzer检查就会出现报错。
1对返回值的错误操作
2传入错误的参数
3错误的逻辑代码
1避免使用-compile(export_all),因为会导致全部函数导出,而无法判断逻辑
2尽量给导出的函数的所有参数提供最详细准确的规范
3为记录中所定义的类型提供默认参数,而不是让其将undefined扩散出去
4尽量避免使用匿名变量,多给变量添加限制
typer使用方法和dialyzer类似,他的主要目的就是判断文件中的参数和返回值都是什么数据类型,对于自相矛盾的定义会返回none()来告知有问题,而dialyzer中则会分析这种错误,并把为什么会出现逻辑冲突的原因告诉程序员。但是如何解决这种逻辑冲突就看程序员自己的解决了。
ps:typer和dialyzer本质上都是在读取程序中所设立的约束,然后根据约束检查代码是否有问题。
类型系统说到底还是计算机逻辑,你不明确说那就是不存在。所以很多程序员看起来很正常的事情在计算机看来无法理解。就比如我们要写一个类似and的程序,假如我们这样写
and(true,ture) -> true;
and(false,_) -> false;
and(_,false) -> false;
那么typer一定会判断and(_,_)->boolean()是其数据格式,但其实这是不对的。我们很清楚的知道and的两个参数都应该是bool,但是我们用_表示的话,他就会判断这里可以输入任意值。而假如我们在这个错误的and判断上再套上一层会传number进来的函数,那typer和dialyzer都不会发现错误,因为and的两个参数都可以是任意值。这时候就会报错,因为并不存在允许两个任意值的and函数。