Erlang学习笔记五

宏定义

-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独特的避免空指针失效,给调试带来难度,所以除非必要不要使用。此外如果一定要使用,最好用于一次性写入的变量。

 

引用数据类型 reference

由内置函数所创建,塞入数据中作为一个锚点而存在,用于后期判断是否相等。

 

比较数据类型

number

此外要注意,当比较数为数字时候,会进行转换,即假如整数和浮点比较,会转换为浮点再进行比较。所以假如要判断相等,最好不要使用==,而是使用=:=因为其还会判断数据类型是否相等。同样这也适用于/=和=/=。

 

下划线变量

一般下划线变量有两个作用,第一个是抵消一个可能会出现错误的地方,即你希望增加可读性,但是并不希望实际使用该变量的时候。第二种情况是在调试程序的时候,避免因为注释所导致的报错而无法正常编译。在实际使用中要避免让下划线变量担任任务,因为这会导致程序出现bug时候很难调试。

 

dialyzer模式检查

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错误的逻辑代码

 

避免对dialyzer干扰

1避免使用-compile(export_all),因为会导致全部函数导出,而无法判断逻辑

2尽量给导出的函数的所有参数提供最详细准确的规范

3为记录中所定义的类型提供默认参数,而不是让其将undefined扩散出去

4尽量避免使用匿名变量,多给变量添加限制

 

通过typer和dialyzer对类型进行检查

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函数。

 

 

你可能感兴趣的:(学习总结,erlang)