[Erlang 0120] Know a little Core Erlang
Erlang開發者或多或少都用過或者聽說過Core erlang,它是什麼樣的呢?新建一個測試模組a.erl,如下操作會生成core erlang程式碼而非a.beam:
Eshell V6.0 (abort with ^G) 1> c(a,[to_core]).這時資料夾中已經生成了一個a.core的檔案,然後我們如下行事:
2> c(a,[from_core]). {ok,a}
這時已經看到a.beam了,開啟a.core的檔案,這些看起來熟悉卻又有點奇怪的程式碼是什麼意思?有什麼用呢?
What is Core Erlang?
Why Core Erlang ?
Core Erlang 是Erlang的一種中間表現形式,Erlang在語法層面一直在演變,越發複雜,一些程式碼分析工具或者除錯工具解讀程式碼就不方便了.Core Erlang就是因此而生,它儘可能的保持語法簡單,穩定以方便工具解析,同時具備程式碼可讀性以方便手工修改程式碼.
換句話說,通過Core Erlang我們可以透過語法糖看到真實的程式碼邏輯是怎樣的,在之前分析Erlang語法相關的博文中我就數次使用Core Erlang,比如:
[Erlang 0034] Erlang iolist 通過Core Erlang 檢視iolists內部表示
[Erlang 0039] Erlang Inheritance 通過Core Erlang看所謂的繼承extends實際上是做了什麼
[Erlang 0058] Erlang Function呼叫效率 通過Core Erlang看幾種函式呼叫方式效能差異是如何產生的
Core Erlang in Action
下面我將通過幾段程式碼把常用的Core Erlang展示一下,模組定義和attitudes之類的基本上都能對應上就不說了,不求完備,但求實用,直接進入方法.準備好夥計,要開始戰鬥了!
第一段程式碼
append_list()-> [a,b] ++ [1,2]. append_list2(L)-> [100,200] ++L. append_list3(L) -> L++[100,200].
對應的Core Erlang程式碼:
'append_list'/0 = %% Line 5 fun () -> ['a'|['b'|[1|[2]]]] 'append_list2'/1 = %% Line 8 fun (_cor0) -> %% Line 9 [100|[200|_cor0]] 'append_list3'/1 = %% Line 11 fun (_cor0) -> %% Line 12 call 'erlang':'++' (_cor0, [100|[200]])
這裡就已經很好玩了對不對,所謂的函式,其實就是把lambda表示式(或者說Fun)賦值給變數.然後看append_list()由於結果是可以編譯時計算出來的,所以做了優化,直接給出了結果.append_list2(L)也做了優化把兩個元素掛在了表頭.append_list3(L)沒有什麼優化餘地老老實實call 'erlang':'++'
第二段程式碼
test()-> A=lists:seq(1,10), B={1,2,3,4}, C= <<"42">>, {M,N,P,Q} =B, {[A,M],[P,Q],N,C}.
可以猜測一下這段程式碼對應的Core Erlang是什麼樣的?我把答案程式碼摺疊一下
'test'/0 = %% Line 14 fun () -> let <A> = %% Line 15 call 'lists':'seq' (1, 10) in %% Line 19 {[A|[1]],[3|[4]],2,#{#<52>(8,1,'integer',['unsigned'|['big']]), #<50>(8,1,'integer',['unsigned'|['big']])}#}
這裡我們特別要關注兩點:1. let原語顯示指定了變數的作用範圍,是不是想到了下面的程式碼?
(define-syntax let (syntax-rules () ((let ((var expr) ...) body ...) ((lambda (var ...) body ...) expr ...)))) (let ((a 1) (b 2)) (+ a b))
2. 二進位制資料<<"42">>在Core Erlang表達的時候會把預設的一些元資料描述出來,程式解析當然方便,人工閱讀就顯得繁瑣了.
第三段程式碼
第三段程式碼純粹是為了演示故意複雜化的,估計沒有誰會會這樣多此一舉的寫加法運算吧
add(A,B)-> case {A,B} of {1,1} -> 2; {0,0}-> 0; {A,B} ->A +B end.
Core Erlang程式碼就有趣的多了,不要被下面這堆東西嚇到:
'add'/2 = %% Line 21 fun (_cor1,_cor0) -> %% Line 22 case <_cor1,_cor0> of %% Line 23 <1,1> when 'true' -> 2 %% Line 24 <0,0> when 'true' -> 0 %% Line 25 <_cor5,_cor6> when let <_cor7> = call 'erlang':'=:=' (_cor5, _cor1) in let <_cor8> = call 'erlang':'=:=' (_cor6, _cor0) in call 'erlang':'and' (_cor7, _cor8) -> call 'erlang':'+' (_cor1, _cor0) ( <_fol6,_fol7> when 'true' -> let <_cor2> = {_fol6,_fol7} in primop 'match_fail' ({'case_clause',_cor2}) -| ['compiler_generated'] ) end
前面兩個邏輯分支需要解釋一下的就是match pattern的語法結構是<v1,v2>;需要仔細看的是第三個邏輯分支,可以看到模式匹配的細節其實是: _cor7 = (_cor5 =:= _cor1), _cor8=((_cor6 =:=_cor0)),_cor7 and _cor8;並且後面還有編譯期間自動生成的match_fail程式碼.
第四段程式碼
加強一下對match pattern的印象,看下面這段程式碼,夠簡單了吧,生成的Core Erlang程式碼同樣會把邏輯補全:
match_test(T)-> {A,B,C} =T, [A,{B,C}].
下一次我們看到模式匹配的時候,腦海中應該能浮現出下面的場景了吧:
'match_test'/1 = %% Line 28 fun (_cor0) -> %% Line 29 case _cor0 of <{A,B,C}> when 'true' -> %% Line 30 [A|[{B,C}|[]]] ( <_cor1> when 'true' -> primop 'match_fail' ({'badmatch',_cor1}) -| ['compiler_generated'] ) end
第五段程式碼
我是列表解析的重度使用患者,特別是在Erlang Shell中,我把它當做迴圈,當做過濾器,當做if;當它轉換成Core Erlang表示的時候,就呈現出其背後的機制:
lc_test()-> [Item * 2 || Item <- lists:seq(1,20),Item rem 2==0].
'lc_test'/0 = %% Line 32 fun () -> %% Line 33 ( letrec 'lc$^0'/1 = fun (_cor4) -> case _cor4 of <[Item|_cor1]> when try let <_cor2> = call 'erlang':'rem' (Item, 2) in call 'erlang':'==' (_cor2, 0) of <Try> -> Try catch <T,R> -> 'false' -> let <_cor5> = call 'erlang':'*' (Item, 2) in let <_cor6> = apply 'lc$^0'/1 (_cor1) in ( [_cor5|_cor6] -| ['compiler_generated'] ) ( <[Item|_cor1]> when 'true' -> apply 'lc$^0'/1 (_cor1) -| ['compiler_generated'] ) <[]> when 'true' -> [] ( <_cor4> when 'true' -> ( primop 'match_fail' ({'function_clause',_cor4}) -| [{'function_name',{'lc$^0',1}}] ) -| ['compiler_generated'] ) end in let <_cor3> = call 'lists':'seq' (1, 20) in apply 'lc$^0'/1 (_cor3) -| ['list_comprehension'] )
這裡要說的就是letrec 它讓我們能夠在 'lc$^0'/1內部呼叫 'lc$^0'/1自身.有興趣的可以找更多關於letrec lisp的資料來看.
第六段程式碼
這段程式碼主要關注尾遞迴和Guard
fact(N) when N>0 -> N * fact(N-1); fact(0) -> 1.
'fact'/1 = %% Line 35 fun (_cor0) -> case _cor0 of <N> when call 'erlang':'>' (_cor0, 0) -> let <_cor1> = %% Line 36 call 'erlang':'-' (N, 1) in let <_cor2> = %% Line 36 apply 'fact'/1 (_cor1) in %% Line 36 call 'erlang':'*' (N, _cor2) %% Line 37 <0> when 'true' -> %% Line 38 1 ( <_cor3> when 'true' -> ( primop 'match_fail' ({'function_clause',_cor3}) -| [{'function_name',{'fact',1}}] ) -| ['compiler_generated'] ) end
第七段程式碼
看看所謂的函式分支是什麼
dump(a)->atom_a; dump([]) ->empty_list; dump(C)->io:format("parameter is : ~p",[C]).
看下面的程式碼,其實所謂邏輯分支其實只是case語句中的邏輯分支而已,只不過要是在專案中寫這樣冗長的程式碼估計要瘋掉了;語法上支援函式分支讓我們可以寫短函式,人工維護起來方便些.
'dump'/1 = %% Line 40 fun (_cor0) -> case _cor0 of <'a'> when 'true' -> 'atom_a' %% Line 41 <[]> when 'true' -> 'empty_list' %% Line 42 <C> when 'true' -> call 'io':'format' ([112|[97|[114|[97|[109|[101|[116|[101|[114|[32|[105|[115|[32|[58|[32|[126|[112]]]]]]]]]]]]]]]]], [C|[]]) end
第八段程式碼
當然少不了receive語句了
recv_test()-> receive a-> "a"; m->io:format("Call M(),Result: ~p ",[m()]),recv_test(); {1,2} ->one_two; H -> io:format("recv ~p",[H]),recv_test() end.
看下面Core Erlang最後幾句是不是恍然大悟,原來是這樣啊
'recv_test'/0 = %% Line 44 fun () -> %% Line 45 receive %% Line 46 <'a'> when 'true' -> [97] %% Line 47 <'m'> when 'true' -> let <_cor0> = apply 'm'/0 () in do call 'io':'format' ([67|[97|[108|[108|[32|[77|[40|[41|[44|[82|[101|[115|[117|[108|[116|[58|[32|[126|[112|[32]]]]]]]]]]]]]]]]]]]], [_cor0|[]]) apply 'recv_test'/0 () %% Line 48 <{1,2}> when 'true' -> 'one_two' %% Line 49 <H> when 'true' -> do call 'io':'format' ([114|[101|[99|[118|[32|[126|[112]]]]]]], [H|[]]) apply 'recv_test'/0 () after 'infinity' -> 'true'
第九段程式碼
-record(person,{id=0,name}). r(#person{id= ID ,name=Name} =P)-> {ID,Name}. r_test()-> P=#person{id=123 , name="zen"}, r(P).
這下看清楚record是什麼了吧?
'r'/1 = %% Line 56 fun (_cor0) -> case _cor0 of <P = {'person',ID,Name}> when 'true' -> %% Line 57 {ID,Name} ( <_cor1> when 'true' -> ( primop 'match_fail' ({'function_clause',_cor1}) -| [{'function_name',{'r',1}}] ) -| ['compiler_generated'] ) end 'r_test'/0 = %% Line 59 fun () -> %% Line 61 apply 'r'/1 ({'person',123,[122|[101|[110]]]})
第十段程式碼
這一段應該算是趕潮流的程式碼,文件裡面暫時還沒有提到的Maps
m()-> M=#{1=>2 , a=>4,{100,200}=>[1,2,3],<<"zen">> => "Hello"}, #{{100,200} := Data} =M, Data.
哇,Maps的Core Erlang表示還真是.....有些人又要說Erlang傷眼睛了
'm'/0 = %% Line 63 fun () -> let <_cor0> = %% Line 64 ~{::<1,2>,::<'a',4>,::<{100,200},[1|[2|[3]]]>,::<#{#<122>(8,1,'integer',['unsigned'|['big']]), #<101>(8,1,'integer',['unsigned'|['big']]), #<110>(8,1,'integer',['unsigned'|['big']])}#,[72|[101|[108|[108|[111]]]]]>}~ in %% Line 65 case _cor0 of <~{~<{100,200},Data>}~> when 'true' -> %% Line 66 Data ( <_cor2> when 'true' -> primop 'match_fail' ({'badmatch',_cor2}) -| ['compiler_generated'] ) end
看過了上面的程式碼,我們可以想想Erlang在語法層面做了哪些設計讓我們更容易表達想法,程式碼更簡單,好了,就到這裡了,假期愉快.
2014-4-10 10:41:08 補充
OTP-11547 The .core and .S extensions are now documented in the erlc documentation, and the 'from_core' and 'from_asm' options are now documented in the compiler documentation. (Thanks to Tuncer Ayaz.)
2014-10-21 14:38:56 再次補充