声明记录与使用
-module(he).
-record(student,{name=studentName,age=20}).
-export([test/0]).
test()->
W1 = #student{name=tang},
W2 = W1#student{name=ye},
#student{age=Age} = W2,
Age. //20
可以看出来声明有点像类,通过#name{}的方式创建出实例对象,假如没设置值会用默认值补充。据说可以用hrl文件来进行全局定义,但是目前还没看到怎么在文件中调用。如果在erl-cli中则用rr("hrlname.hrl")来进行调用。
ps:值得注意的是W2是对W1的副本拷贝,而不是简单的引用。
ps2:记录等价于元组,当清除记录定义之后,内置格式和元组一样
在函数中的使用
-module(he).
-record(student,{name=studentName,chinese=50,math=50,english=50}).
-export([test/0,say/1]).
test()->
W1 = #student{name=tang,chinese=99,math=20,english=11},
say(W1).
say(#student{chinese=Chinese,math=Math,english=English})->
Sun = Chinese + Math + English,
lists:flatten(io_lib:format("~p",[Sun])).
当需要记录匹配时候可以这么写
isStudent(X) when is_record(X,student) ->
映射组的使用
test()->
W1 = #{name=>tang,age=>20},
W2 = W1#{name:=ye},
W2. //{age=>20,name=>tang}}
要注意的是这边的=>和:=都是匹配,但是:=只能变动现有key,而=>可以增加新的键值对也能对旧key进行value的变动
映射组计算字符重复次数
test()->
getNumber("hello").
getNumber(Str)->
getNumber(Str,#{}).
getNumber([H|Str],X)->
N = maps:get(H,X,0),
getNumber(Str,X#{H=>N+1});
getNumber([],X)->
X.
值得一提的是在作者的书中使用了getNumber([H|Str],#{H=>N}=X)->的写法,这是不对的,因为key不能为动态变量,并且后续到23版本也没有解决,只能采取折中的版本。网上有判断是否存在后采用分支执行的版本,而这里我找到了maps:get的版本,显然这个更适合这个例子。要注意的是这里的maps:get本身也有变动,在书上写的是只有两个参数的方法并且不存在会报错,但是在最新版里面maps:get可以允许你输入一个默认值,这就可以避免报错,并且节约了一个不存在的判断问题。
map的内置函数
maps:new() //创建一个新的空映射组
erlang:is_map(M) //判断M是否是映射组
maps:to_list(M) //将映射组转换为列表
maps:from_list(M) //将一个包含键值对元组的列表转换为映射组,假如出现重复key则
//只看第一个元组的值
maps:map_size(M) //获得条目数量
maps:is_key(Key,M) //查找M中是否有Key
maps:get(Key,Map) //获得Key对应的Value
maps:get(Key,Map,Def) //获得Key对应Value,如果无返回Def
maps:find(Key,Map) //返回{ok,Value},否则返回error
maps:keys(Map) //返回Key的列表,按照升序排列
maps:remove(Key,M) //返回移除了key的新的映射组
maps:without([Key1,...,KeyN],M) //返回M移除了列表之后的新映射组
maps:difference(M1,M2) //返回M1移除了M2中Key的新映射组
map的比较问题
map之间比较先比大小,然后比较第一个key的大小(a开头小于b开头)
而当map和其他类型比较时,列表和元组都小于map
erlang对json的支持
erlang中对json并没有直接支持,如果需要得使用第三方库如jsone或者erlang-rfc4627
erlang的崩溃错误
在erlang中单个进程出现进程崩溃并不可怕,或者说官方鼓励你让他崩溃,而不是采取防御式编程来预防。而当你需要手动触发时候,有三种选择:
exit(Why) //退出式进程崩溃,如果没捕捉则会群发给所以监听他的进程
throw(Why) //抛出一个调用者希望的捕捉的异常错误
error(Why) //抛出一个崩溃性错误,即开发者没办法处理的严重问题
erlang的try的使用
test()->
[getError(Key) || Key <- [1,2,3,4,5]].
getError(Key)->
try toError(Key) of
Val -> {Key,normal,Val}
catch
throw:X -> {Key,caught,thrown,X};
exit:X -> {Key,caught,exited,X};
error:X -> {Key,caught,error,X}
end.
toError(Key)->
case Key of
1 -> a;
2 -> throw(a);
3 -> exit(a);
4 -> {'EXIT',a};
5 -> error(a)
end.
//结果
//[{1,normal,a},
//{2,caught,thrown,a},
//{3,caught,exited,a},
//{4,normal,{'EXIT',a}},
//{5,caught,error,a}]
值得注意的是try还有after这个块,就和java的finally同样的用法。
此外try不一定需要catch或者of,你甚至可以这么写:
test()->
[getError(Key) || Key <- [1,4]].
getError(Key)->
X = try toError(Key) after "aa" end,
X.
//结果[a,{'EXIT',a}]
erlang的catch捕捉
这里的catch和try的catch不一样,是能单独运作的。依旧是上面这个例子,这里用catch来捕捉
test()->
[{Key,catch toError(Key)} || Key <- [1,2,3,4,5]].
//[{1,a},
// {2,a},
// {3,{'EXIT',a}},
// {4,{'EXIT',a}},
// {5,
// {'EXIT',{a,[{he,toError,1,[{file,"he.erl"},{line,22}]},
// {he,'-test/0-lc$^0/1-0-',1,[{file,"he.erl"},{line,5}]},
// {he,'-test/0-lc$^0/1-0-',1,[{file,"he.erl"},{line,5}]},
// {erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,684}]},
// {shell,exprs,7,[{file,"shell.erl"},{line,686}]},
// {shell,eval_exprs,7,[{file,"shell.erl"},{line,642}]},
// {shell,eval_loop,3,[{file,"shell.erl"},{line,627}]}]}}}]
可以看出来其对于error错误的捕捉详细到了栈级别
erlang的错误处理思路
如果嫌弃系统的默认错误提示太过简单,可以自己主动去判断错误,并为这个错误抛出自定义的错误提示。而假如需要处理函数可能的错误提示,则应该使用case去捕捉或者使用平衡来触发这个错误,如:
case f(X) of
{ok,Value} -> ..
{error,Value} -> ..
end.
{ok,Value} = f(x)
此外要注意的就是假如你不写try的catch的异常类型(error、throw、exit)则默认是throw
erlang中的手动栈跟踪
在老版中使用erlang:get_stacktrace()来进行跟踪,但是在OPO21版本就已经舍弃了,开发人员推荐用以下这种办法来实现栈跟踪:
test()->
try toError(5)
catch
error:Value:Stack ->
{error,Value,Stack}
end.
即在条件选项中多加一个参数Stack就可以捕捉到栈数据
ps:千万不能忘记任其崩溃的核心思想,不能在错误的时候随意返回值出去,而是应该抛出错误让调用者来解决。此外错误日志应该被永久性保留,并且应该告知使用者错误的存在并隐藏具体错误内容只给开发者查看。