热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

[Erlang]前人挖坑,后人还要进坑?

1.保护式(guard)中如果出错,不会报错,只会返回false!case1:1oftruewhennoterlang:len

1. 保护式(guard)中如果出错,不会报错,只会返回false!

case 1=:1 of
     true when not erlang:length(t) =:= 1 orelse true ->
           ok;
     _ ->
         error
end.
Result is:   error

保护式中对t (atom) 求length会出错,本应crash掉,但因为在保护式中,默认出错后结束此保护式计算并返回false,这也是为什么保护式不接受复杂的函数,只能用erlang的bif来做的原因之一。

2. try catch 时如果不写catch类型,默认为throw类型!

try_catch(Value) ->
    try
        case Value  of
            error -> erlang:error({error,plz_catch_me});
            throw -> erlang:throw({throw,oh_get_you});
            exit -> erlang:exit({exit,sorry_by_mistake})
        end
    catch
        T -> T
    end.

Result :

try_catch

所以最好是:明确: Catch   throw:T –> {throw,T}; error:T –> {error,T}; exit:T –> {exit,T} end.

 

3. 在保护式中使用erlang:length/1要小心再小心!(要遍历列表,时间长度不定)

%%写成这样的耗时与列表长度成平方比:Do not do this
foo(List) when lenght(List) >0 ->
         do_something;
foo(_) ->
       done.
%%使用匹配模式来做可做到任意长度断定
better([One]) ->
       do_something_one();
better([One,Two]) ->
       do_something_two();
better([one,Two|_]) ->
       do_something_big();
better([]) ->
      do_something_empty()
end.

Tip:  如果要判定List是一个非空List 可用 case List of [_|_] –> do_something(); _ –> done end.

4. ++只是lists:append/2的一个别名:如果要用一定要确定 ShortList++LongList !(可记为长短的反义短长…每次用他我都会条件反射想一下)

%% DO NOT DO
naive_reverse([H|T]) ->
    naive_reverse(T)++[H];
naive_reverse([]) ->
    [].

which is the most inefficient way there is to reverse a list. Since the ++ operator copies its left operand, the result will be copied again and again and again... leading to quadratic complexity.

这是最没效率去反转一个list,”++“会复制左边的元素,这个会使复制多次,导致平方倍的复杂度。

但是另一方面:下面这种用法就好了:

 
%% OK
naive_but_ok_reverse([H|T], Acc) ->
    naive_but_ok_reverse(T, [H]++Acc);
naive_but_ok_reverse([], Acc) ->
    Acc.

这并不是一个是非常坏的尝试,每个列表元素只被copy一次,那增长的Acc是在++的右边的,他不会每次都被copy的

当然,最佳实践还是下面这种:

%% Best Do
vanilla_reverse([H|T], Acc) ->
    vanilla_reverse(T, [H|Acc]);
vanilla_reverse([], Acc) ->
    Acc.

这比上面的还要高效一点点,你根本不用去建造一个list元素,直接copy他就可以了(或者:如果编译器不把[H]++Acc重写为[H|Acc] ,那就更高效啦)。

5. receive 和case的区别很大,虽然写法类似:

case_test(Value) ->
    case Value of
        1 -> ok;
        2 -> error
    end.
receive_test(Value)when Value>2 ->
    PID = spawn(fun () ->
        receive
            {msg,1} ->
                ok;
            {msg,2} ->
                error
        end
    end),
    [begin PID ! {msg,ValueT} end ||ValueT<-lists:seq(3,Value)],
    PID.

Result:

recieve

从上面可以看出:

5.1 case如果没有匹配就会出错;

5.1 recieve 会在没有匹配消息时阻塞,只要信箱中没有匹配消息,就会在等待期间挂起,=有新消息到时才会被唤醒,每次执行时,receive会先检查最老的消息(位于队列头部),像在case表达式中那样尝试匹配,如果找不到,就会继续下一消息,如果与当前匹配成功,且保护式成立(如果有),此消息就会被移出信箱,同时子句对应的正文会被执行,如果一直没找到合适消息就会一直等待至超时(如果有的话,after Times).

6. erl 用-noshell –noinput 启动一个node时,看不到,又不能输入怎么调试?用-remsh参数

>erl -name foo@127.0.0.1 -setCOOKIE 123456 -noshell -noinput

>erl -name bob@127.0.0.1 -setCOOKIE  123456 -remsh foo@127.0.0.1

%%注意起的节点叫foo哦,不是bob了!
foo@127.0.0.1> nodes().
foo@127.0.0.1>['bob@127.0.0.1']
foo@127.0.0.1>node().
foo@127.0.0.1>'foo@127.0.0.1'

这里的坑在于:

6.1 在remote出来的节点上调用q(),2个节点都会退出!好可怕,所有最好把q()这个函数在user_default.erl里面重写,让他不要执行init: stop().

6.2 window下要用werl 代替erl;

6.3 erl支持自定义参数,比如你写erl –rmsh test 也是不会报错的,如果不小心写错了,就会查好久……..

Tip: 已 起A,B二个节点,要用A 控制B,可以在A中使用Ctrl+G  r  NodeA j  2操作  具体见:Learn some erlang remote shell.

 

7.如果有一个ArgList 是:从不可知道的地方传过来是这样子:”[test1,test2,test3]”,要怎么使用才能动态执行?

场景:知道方法调用的方法:Method 使用 erlang:apply(Module,Method,ArgList)调用产生结果,这时的ArgList是不符合格式:

%% String = "[test1,test2,4]."注意最后面的结束小句号!
string_to_args(String) ->
    {ok, Scan1, _} = erl_scan:string(String),
    {ok,P}=erl_parse:parse_exprs(Scan1),
    {value,Value,[]} = erl_eval:exprs(P, []),
    Value.

以上合适List中所有的参数都是绑定的:,如果是有Test1这样的变量,我也没试过,还没遇到这种需求悲伤

可以参考

 

8. erl 启动参数可以自己定义:如

>erl -test erlang1  -test hello -test1 test1

>init:get_argument(test).
{ok,[["erlang1"],["hello"]]

>init:get_arguments().
[{root,["C:\\Program Files\\erl6.0"]},
 {progname,["erl"]},
 {home,["C:\\Users\\**"]},
 {test,["erlang1"]},
 {test,["hello"]}]

8.1  不要把参数定义为string了:比如 “hello world”

8.2 如果这个是启动application启动用的,就不要指望用这个自定义的参数了,用config定义吧

Applications should not normally be configured with command line flags, but should use the application environment instead. Refer to Configuring an Application in the Design Principles chapter for details

 

9.使用RPC时一定要记得你是在distributed的,时刻关注你在那个进程!

比如:把rpc:call放在loop里面和外面会得到不一样的效率反馈,以下这例子的结果是等价的,但是第一种会发出很多的call,第二种只有一个call.

 
 
%%Example - Bad
[rpc:call(node, ets, lookup, [table, K]) || K <- ListOfKeys].

%%Example - Good
rpc:call(node, ?MODULE, get_data, [ListOfKeys, []]).
get_data([], Out) -> lists:reverse(Out);
get_data([K|ListOfKeys], Out) -> get_data(ListOfKeys, [ets:lookup(table,K)|Out]).

同理你可以自己改一下:[gen_server:call(Pid,{func,Fun})||Fun<- FunList].

总之要能一次发消息处理的就不要多次发啦.
10 不要构造超极大的terms(或者你不可控制大小的terms). 
具体就是如果要遍历ets里面所有的元素,用ets:tab2list/1得出来的结果可能什么非常大,这可怎么办啊!
%% 一次性也出所有元素:不要这样子做
bad_traverse_to_print() ->
    [begin print(Person) end||Person <- ets:tab2list(person)],
    ok.

%%从第一个一直遍历到最后一个:数据要从erts内部搬到process 当ets很大的时候就效率低
good_traverse_to_print() ->
    good_traverse_to_print2(ets:first(person)).

good_traverse_to_print2('$end_of_table') ->
    ok;
good_traverse_to_print2(Key) ->
    [Person] = ets:lookup(person,Key),
    print(Person),
    good_traverse_to_print2(ets:next(person,Key)).

%%分页:最佳实践使用ets:select match MatchSpec:ets内部实现了把matchspec编译成opcode 然后eval的时候把需要的数据才拷贝到process去 大大减少了数据量
best_traverse_to_print() ->
    case ets:match(person,'$1',10) of
        {PersonList,'$end_of_table'} ->
            [begin print(Person) end|| [Person] <- PersonList];
        {PersonList,Key} ->
            [begin print(Person) end|| [Person] <- PersonList],
            best_traverse_to_print2(Key)
    end,
    ok.
best_traverse_to_print2(Key) ->
    case ets:match(Key) of
        {PersonList,'$end_of_table'} ->
            [begin print(Person) end|| [Person] <- PersonList];
        {PersonList,Key2} ->
            [begin print(Person) end|| [Person] <- PersonList],
            best_traverse_to_print2(Key2)
    end.

print(Person) ->
    io:format("Name~p     Phone:~p~n",[Person#person.name, Person#person.phone]),
    ok.

第10条和第9条是看似矛盾的,一个说如果可以一次处理完就不要分多次,一个是如果太大就要分多次!注意,如果一个消息体太大了,也要分多次哦。


11.每次重装系统时都会重新安装Erlang,Ubuntu安装sh秒杀一切.

     https://gist.github.com/zhongwencool/11174620

12. Erlang Shell隐藏的小技巧:

    f(). %%把所有绑定变量释放掉
    f(Val). %%把Val变量释放掉
    v(Line). %%把Line行函数重新执行一次
    v(-1). %%把前一行的函数重新执行一次
    rr(Module).%%把Module中的Record加载到Shell中,【超有用】
    rr("*/*"). %%把在这个子目录下的所有Module里面的Record给加载到Shell里面
    rp(Expression).%%把Expression的全部元素给打印到shell里面【超有用】
     rl(). %%列出所有已定义过的Record. rf(RecordName).%%不加载名为RecordName的Record
     %%如果你的code加了debug_info信息【compile:file(Module,[debug_info])】你可以这样看源代码
    {ok,{_,[{abstract_code,{_,AC}}]}} = beam_lib:chunks(Beam,[abstract_code]),
    io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).

13. Erlang中有很多不可逆函数:比如 binary_to_list/1 , list_to_binary/1

  > binary_to_list(list_to_binary([ <<1,2>>,<<3,4>>])).
    [1,2,3,4]

    这个坑有点深,踩过才知道!这还有个和binary一起的小Tip:

   > <<"xyz","ets","bt">> =:= <<"xyzetsbt">>.           
     true

    binary居然是等价的。

14.在保护式里面 ,等价于andalso ;等价于orelse------[但是:不是完全相同,原因如下]

   %%相同点:
   right_age(X) when X >= 16, X =<104 -> %% X>=16 andalso X =<104
    true;
   right_age(_) –>
    false.

     不同点:

    %%当Condition1异常出错时,还会去判断Condtion2,如果使用orelse则会直接返回false
    func() when Condition1 ; Condition2 –> 
      ok;
    func() –>
      error.

     可以和http://www.cnblogs.com/zhongwencool/p/3712909.html 第一条对比一下,理解更深哦。

15.如果你想在实践中使用二叉树,请看gb_tree.erl,不要再造不必要的轮子啦,【不过可以考虑用来做练习】

16. 在try catch 的异常处理中禁止使用尾递归:

      因为在这个异常处理部分有一个保护机制,Erlang绝对相信异常处理是正常的,如果你在这里面使用尾递归,会出现以下情况:

    1) 尾递归变死循环啦【项目实践中可能会发生的】;
    2) 非常多的进程进入了这个异常中,这个尾递归处理的东西又非常复杂,VM运行长久后,block大量的进程和消耗内存

      这样的结果是:内存耗尽或程序特别慢,关键你还是查找不到最后的crash的原因。

      所以推荐:异常处理中只使用必要且简单的处理就行了

17. 如果你想在Erlang中 kill一个进程:找到Pid后,exit(Pid,Reasno).【效果和kill一样啦】

      大部分情况不会起作用!!!!!因为在项目里面你的进程如果合规范都是在监控树下的,如果被exit/2后还会被监控树自动重启啦,所以你要先把进程移除监控树!!!

    supervisor:terminate_child(SupPid,Pid), 
    supervisor: delete_child(SupPid,Pid).

18. 2个进程可以双向连接和单向连接:

     双向: link(Pid1,Pid2). 重复调用效果一样,也就是说对同2个进程无论调用多少次link,只要使用一次unlink(Pid1,Pid2)就解除连接

     单向:erlang:monitor/2   erlang: demonitor/1

19. receive  after Time

      这个Time最大值是50*24*60*60*1000,当时间大于50天时,就会报错:

      所以要把Time拆成小于最大值的列表:

  normalize(Time) –>
      Limit = 49*24*60*60,
      [Time rem Limit | lists:duplicate(Time div Limit, Limit)].

      然后如果时间列表不为空就不断减少列表并等待直到列表为空:

  loop([T|Next]}) –>
     receive
        {Server, Ref, cancel} –>
           Server ! {Ref, ok}
        after T*1000 –>
           if Next =:= [] –>
               Server ! {done, S#state.name};
              Next =/= [] –>
              loop(Next})
           end
     end.

20. gen_server里面的handle_call/3 和handle_cast/2返回值都可以加个Timeout时间。

     如果这个时间内没有处理完就会发出一个timeout信息:由handle_info处理,具体可见:http://www.cnblogs.com/zhongwencool/p/erlang_timer.html 里面的方法二。


21.在生成随机数过程中一直使用:

random:seed(erlang:now()).

erlang:now()是可以一直递增的数,从不重复,但是不适合用于随机算法,最好使用下面这种

-spec get_random_num(integer()) -> integer().
get_random_num(Max) ->
    <> = crypto:rand_bytes(12),
    random:seed({A,B,C}),
    random:uniform(Max).

22.Pid的那3位数字代表着什么?

  • A, 对应是哪一个节点 (0 代表是本地节点 ,其它数字代表远程节点) ;
  • B, 低15字节代表进程唯一记数(一个进程表的索引)
  • C, 16~18字节也是进程唯一记数(和B一样)

http://stackoverflow.com/questions/243363/can-someone-explain-the-structure-of-a-pid-in-erlang

验证如下:

打开一个终端test1

打开终端test2

23.gen_server进程如果长期不需要处理其它消息,可以使用返回值{noreply,hibernate}进入休眠状态:使用场景:

比如使用者知道在处理完一个特定的消息后这个进程在很长一段时间内不会再有消息处理,可以设定这个消息的返回后进入hibernate状态!

这可以减少内存和cpu消耗。

24.Record这么好用,为什么还是有很多大神吐槽record非常不好?

缺点:

%%1.只能使用原子作为index元素;

%% 2.编译前就确定了结构,不能动态的增加index,比如person要增加一个phone属性,record是做不到的。

所以17后就有了Map这个数据结构,可以突破上面的限制,真正的Map.

http://www.cnblogs.com/zhengsyao/p/erlang_map_brief.html

但是目前的mnesia只支持record的。不支持map,那Record的升级麻烦还会在mneisa中继续存在的。

25. receive 里面after的妙用:

进程阻塞时,用于清空进程信箱消息

receive 
 Msg ->
           do_something(Msg);
after 0 ->
      do_something2)
end.

关键看清楚那个after 0 ,调用这个函数就会把这个进程以前旧的消息都用do_something(Msg)处理掉:(当然你的do_something/1里面会尾递归调用回这个函数啦)

25.ETS 的默认限制个数为1400个,你可以通过:

erl -env ERL_MAX_ETS_TABLES Number

来改变这个默认值,但是这个限制也提示了一个非常重要的信息:不要尝试每一个进程都建立一个ets表!

26. 不要跨节点连接或监控(link,monitor)很多进程:

因为如果节点由于网络故障通信断了,那么所有link或monitor的进程会被马上触发,产生超大量的消息分给超多的进程,这就会给系统增加非常大的负担。

27.使用receive 嵌套实现接收消息的先后关系

receive
       {first_deal_msg,Msg} ->
                 do_someting(Msg),
                  receive
                        {second_deal_msg,Msg2} ->
                              do_someting2(Msg2)
                  end
end.

28.Erlang为什么叫Erlang?(哈哈,乱入一通):

erlang

n.厄兰(话务单位),占线小时;

名副其实的为通信而先的语言哇…

29.begin end语句块的简洁使用:
问题描述:
将一堆人随机平均分开2组,,怎样去平均?

当然效率不高,但是!看上去很cool…

30. split_binary/2

It is usually more efficient to split a binary using matching instead of calling the split_binary/2 function. Furthermore, mixing bit syntax matching and split_binary/2 may prevent some optimizations of bit syntax matching.

使用比特语法来分离二进制比用split_binary/2更加高效,更进一步来讲,混合使用比特语法和split_binary/2会把编译器弄sb的(不会进行相关的优化任务了)

DO

<> = Bin,

DO NOT

{Bin1,Bin2} = split_binary(Bin, Num)

转自:http://blog.csdn.net/erlib/article/details/40743849


推荐阅读
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了Perl的测试框架Test::Base,它是一个数据驱动的测试框架,可以自动进行单元测试,省去手工编写测试程序的麻烦。与Test::More完全兼容,使用方法简单。以plural函数为例,展示了Test::Base的使用方法。 ... [详细]
  • 不同优化算法的比较分析及实验验证
    本文介绍了神经网络优化中常用的优化方法,包括学习率调整和梯度估计修正,并通过实验验证了不同优化算法的效果。实验结果表明,Adam算法在综合考虑学习率调整和梯度估计修正方面表现较好。该研究对于优化神经网络的训练过程具有指导意义。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
author-avatar
零乱772_553
这个家伙很懒,什么也没留下!
Tags | 热门标签
RankList | 热门文章
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有