Erlang顺序编程杂项集(下篇)

十七:宏

Erlang 的宏以如下方式编写:
-define(Constant,Replacement).
-define(Func(Varl,Var2,..,Var),Replacement).
Erlang 的预处理器 epp 碰到一个 ?MacroName 形式的表达式时,就会展开这个宏。宏定义里
出现的变量会匹配对应宏调用位置的完整形式。例如:
-define(macrol(X,Y),{a,X,Y}).

fo0(A) ->
    ?macro1(A+10,b).

%% 它展开后是这样的:
f00(A) ->
    {a,A+10,b}.
另外还有一些预定义宏提供了关于当前模块的信息。列举如下:
(1) ?FILE                 展开成当前的文件名;
(2) ?MODULE         展开成当前的模块名;
(3) ?LINE                 展开成当前的行号。

宏控制流

模块的内部支持下列指令,可以用它们来控制宏的展开。
(1) -undef(Macro).
%% 取消宏的定义,此后就无法调用这个宏了。

(2) -ifdef(Macro)
%% 仅当Macro有过定义时才执行后面的代码。

(3) -ifndef(Macro).
%% 仅当Macro未被定义时才执行后面的代码。

(4) -else.
%% 可用于ifdef或ifndef语句之后。如果条件为否,else后面的语句就会被执行。

(5) -endif.
%% 标记ifdef或ifndef语句的结尾。
条件宏必须有恰当的嵌套。按惯例它们被归组如下:
-ifdef (<FlagName>).
-define(...).
-else.
-define(...).
-endif.
我们可以用这些宏来定义一个 DEBUG (调试)宏。这里是一个例子:
%% m1.erl
-module(m1).
-export([loop/1]).

-ifdef(debug flag).
-define(DEBUG(X),io:format ("DEBUG ~p:~p ~p~n",[?MODULE,?LINE,X])).
-else.
-define(DEBUG(X),void).
-endif.

loop(0) ->
    done;
loop(N) ->
    ?DEBUG(N),
    1oop(N-1).

注:io:format(String, [Args]) 会根据String里的格式信息在Erlang shell中打印出 [Args] 所含的变量。格式编码用一个~符号作为前缀。~p是美化打印(pretty print)的简称,~n 则会生成一个新行。

为了启用这个宏,我们在编译代码时设置了  debug_flag 。具体做法是给  c / 2  添加一个额外参
数如下:
1> c(m1,{d,debug_flag}).
{ok,m1}

2> m1:loop(4).
DEBUG m1:13 4
DEBUG m1:13 3
DEBUG m1:13 2
DEBUG ml:13 1
done

注:如果没有设置debug_flag,这个宏就只会展开成原子void。选择这个名称没有什么实际意义,只是用来提醒你没有人会对这个宏的值感兴趣。

十八:模式的匹配操作符

假设有如下代码:
funcl([{tag1,A,B} | T])->
    ...
    ... f(..,{tagl,A,B},..)
    ...
我们在第 1 行模式匹配了数据类型  {tag1, A, B} ,在第 3 行用参数  {tag1, A, B}  调用了  f ,这么做时,系统会重建数据类型 {tag1, A, B} 。一种更高效且不易出错的方式是把这个模式指派给一个临时变量 Z ,然后将它传递给  f ,就像这样:
func1([{tag1,A,B}=Z | T])->
    ...
    ...f(..., Z, ...)
    ...
匹配操作符可以用在模式里的任何位置,因此如果有两个需要重建的数据类型,比如下面代
码里的:
func1([{tag,{one,A},B} | T])->
    ...
    ... f(..., {tag,{one,A}, B}, ...),
    ... g(..., {one,A}, ...)
    ...
就可以引入两个新变量 Z1 Z2 ,像下面这样写:
funcl([{tag, {one,A}=Z1, B} = Z2 | T])->
    ...
    ... f(..., Z2, ...),
    ... g(..., Z1, ...),
    ...

总之来说,模式匹配操作符中的数据类型可以用变量表示。

十九:数字

Erlang 里的数字不是整数就是浮点数,Erlang里只有整数浮点数

整数

整数的运算是精确的,而且用来表示整数的位数只受限于可用的内存。整数可以有三种不同的写法:
(1)传统写法:
         在这里,整数的写法和你预料的一样。比如,12、 12375  和  -23427  都是整数。
(2)K 进制整数:
        除10以外的数字进制整数使用K#Digits 这种写法。因此,可以把一个二进制数写成2#00101010,或者把一个十六进制数写成 16#af6bfa23 。对大于 10 的进制而言, abc...(或ABC... )这些字符代表了数字 10 11 12 ,以此类推。最高的进制数是 36
(3) $ 写法:
        $C这种写法代表了 ASCII 字符 C 的整数代码。因此, $a 97 的简写, $1 49 的简写,以此
类推。
还可以紧挨着 $ 使用 任意转义序列。所以, $\n 10 $\^c 3 ,以此类推。
这里有一些整数的例子:
0    -65    2#010001110    -8#377   16#fe34    16#FE34    36#w0w

 它们的值分别是0-65142-255650766507642368

浮点数

一个浮点数由五部分组成:一个可选的正负号,一个整数部分,一个小数点,一个分数部分
和一个可选的指数部分
这里有一些浮点数的例子:
1.0    3.14159   -2.3e+6    23.56E-27

解析后的浮点数在系统内部使用IEEE 75464位格式表示。绝对值在10-32310308范围内的实数可以用Erlang的浮点数表示。

二十:操作符优先级

下表是按照降序的优先级展示了所有 Erlang 操作符和它们的结合性。操作符优先级和结合性用于确定无括号表达式的执行顺序:
操作符结合性
#
(一元)+、(一元)-、bnot、not

/、*、 div、 rem、 band、 and

左结合
+、-、bor、bxor、bsl、bsr、or、xor左结合
++、- -右结合
==、/=、=<、<、>=、>、==、=/=
andalso
orelse
=!右结合
catch
优先级更高(在表格更上方)的表达式会首先执行,然后才轮到优先级较低的表达式。

二十一:进程字典

每个 Erlang 进程都有一个被称为 进程字典 process dictionary )的私有数据存储区域。进程字典是一个关联数组(在其他语言里可能被称作map hashmap  或者 散列表 ),它由若干个键和值组成。每个键只有一个值。这个字典可以用下列内置函数进行操作:
(1) put(Key,Value) -> OldValue.
%% 给进程字典添加一个Key, Value组合。put的值是OldValue,也就是Key之前关联的值。
%% 如果没有之前的值,就返回原子undefined。

(2) get(Key) -> Value.
%% 查找Key的值。如果字典里存在Key,Values组合就返回Value,否则返回原子undefined。

(3) get() -> [{Key,Value}].
%% 返回整个字典,形式是一个由{Key,Value}元组所构成的列表。

(4) get_keys(Value) -> [Key].
%% 返回一个列表,内含字典里所有值为Value的键。

(5) erase(Key) -> Value.
%% 返回Key的关联值,如果不存在则返回原子undefined。最后,删除Key的关联值。

(6) erase()->[[Key,Value}].
%% 删除整个进程字典。返回值是一个由{Key,Value}元组所构成的列表,代表了字典删除之前的状态。

实例:

1> erase().
[]

2> put(×,20).
undefined

3> get(x).
20

4> get(y).
undefined

5> put(y,40).
undefined

6> get(y).
40

7> get().
[{y,40},{×,20}]

8> erase(x).
20

9> get().
[{y,40}]
如你所见,进程字典里的变量命令式编程语言里传统可变变量的行为非常相似。如果使用进程字典,你的代码就不再是无副作用的了,出于这个原因,应当少用进程字典。
注:最好少用进程字典。因为进程字典可能会给你的程序引入不易察觉的bug,让调试变得困难、但是有一种情况可以用,那就是用进程字典来保存 “一次性写入” 的变量。如果某个键一次性获得一个值而且不会改变它,那么将其保存在进程字典里在某些时候还是可以接受的。

 

二十二:引用

引用 reference )是一种全局唯一 Erlang 数据类型。它们由内置函数  erlang:make_ref() 创建。引用的用途是创建独一无二的标签,把它存放在数据里并在后面用于比较是否相等。

二十三:短路布尔表达式

短路布尔表达式( short-circuit boolean expression )是一种只在必要时才对参数求值的表达式。
“短路”布尔表达式有两种:
Exprl orelse Expr2
%% 它会首先执行Expr1。如果Expr1的执行结果是true,Expr2就不再执行。如果Expr1的执
%% 行结果是false,则会执行Expr2。

Expr1 andalso Expr2
%% 它会首先执行Expr1。如果Expr1的执行结果是true,则会执行Expr2。如果Expr1的执行
%% 结果是false,Expr2就不再执行。

注:在对应的布尔表达式里(A or B和A and B),两边的参数总会被执行,即使表达式的真值只需要第一个表达式的值就能确定也是如此。

二十四:比较数据类型

下表列出了全部可用的数据类型比较操作(八种):

操作符含义
X > YX大于Y
X < YX小于Y
X  =< YX等于或小于Y
X >= YX大于或等于Y
X == YX等于Y
X  /= YX不等于Y
X =:= YX与Y完全相同
X =/= YX与Y不完全相同
为了便于比较,我们给所有的数据类型做了全排序( total ordering )的定义。定义的结果
如下:
number < atom < reference < fun < port < pid < tuple (and record) < map < list < binary
比如,根据定义,一个数字(任何数字)小于一个原子(任何原子),而一个元组大于一个原子,以此类推。( 注:出于排序的需要,端口和进程标识符也被包括在这个名单里。
有了所有数据类型的全排序就意味着可以对任何类型的列表进行排序,以及根据键的排序顺序构建高效的数据访问方式。
所有的数据类型比较操作符(除了 =:= =/= )在参数全为数字时具有以下行为:
(1) 如果一个参数是整数而另一个是浮点数,那么整数会先转换成浮点数,然后再进行比较。
(2)如果两个参数都是整数或者都是浮点数,就会“按原样”使用,也就是不做转换。
还应当非常谨慎地使用  == (特别是对 C Java 程序员而言), 100 次里有 99 次应该用的是  =:=
==  只有 在比较浮点数和整数时才有用。 =:=  则是用来测试两个数据类型是否 完全相同
完全相同的意思是具有相同的值(类似 Common Lisp EQUAL )。因为值是不可变的,所以
这里不涉及任何指针标识。如果不确定的话,就用  =:= ,见到  ==  时则要三思。请注意,刚才的注
解也适用于  /=  和  =/= /=  的意思是“不等于”,而  =/=  的意思是“不完全相同”。还应该知道,函数的子句匹配总是意味着精确的模式匹配,所以如果定义了一个 fun F = fun(12) - >  ... end,那么试图执行 F(12.0)就会出错。
注:在很多库代码和已发布代码里虽然是该用 =:= 的地方用了 == 操作符。幸运的是,这类错误通常不会导致程序出错,因为如果 == 的参数不包含任何浮点数的话,那么这两个操作符的行为就是相同的。

二十五:元组模块

调用 M:f(Arg1, Arg2, ..., ArgN) 时,我们假定 M 是一个模块名。但 M 也可以是一个形式 为{Mod1, X1, X2, ... Xn} 的元组。在这种情况下,调用的函数就是 Mod1:f(Arg1, Arg2, ..., Arg3, M)
这种机制可以用来创建“有状态的模块” “适配器模式”。

二十六:下划线变量

_VarName  这种特殊语法代表一个常规变量( normal variable),而不是匿名变量。一般来说,当某个变量在子句里只使用了一次时,编译器会生成一个警告,因为这通常是出错的信号。但如果这个只用了一次的变量以下划线开头,就不会有错误消息。
因为 _Var 是常规变量,所以如果你忘了这一点并将它用于“不关心”的模式,就可能导致

非常微妙的bug。举个例子,在一个非常复杂的模式匹配里,也许很难察觉_Int被多次不恰当地使用,从而导致模式匹配失败。

下划线变量主要有两种用途:

(1)命名一个我们不打算使用的变量。例如,相比open(File, _)open(File, _Mode)这种写法能让程序的可读性更高。

(2)用于调试。例如:

some func(X)->
    {P,Q}=some_other_func(X),
    io:format("Q = ~p ~n",[Q]),
    P.
编译它不会产生错误消息。
当注释掉格式语句:
some func(X)->
{P,Q} = some_other_func(X),
%% io:format("Q = ~p ~n",[Q]),
P.
如果编译它,编译器就会生成一个变量 Q 未使用的警告。所以我们就使用下划线变量来表示任意变量,( 注:如果使用变量一定不要使用带下划线的变量,这不仅是规范,也是防止在某些情况下出现变量匹配异常的出现。 )
如果把这个函数重新编写如下:
some func(X)->
    {P,_Q}some_other_func(X),
    %% io:format("_Q =-p-n",[_Q]),
    P.

上面的注释中使用的_Q这种做法最好不要使用,这里使用只是表示该变量所用的地方注释掉后,就不会报变量Q未使用的警告。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

明明如皓

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值