[Erlang02] 那些經歷過的Erlang小坑1-10

寫著寫著就懂了發表於2014-05-17

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條是看似矛盾的,一個說如果可以一次處理完就不要分多次,一個是如果太大就要分多次!注意,如果一個訊息體太大了,也要分多次哦。

相關文章