Erlang開發者或多或少都用過或者聽說過Core erlang,它是什么樣的呢?新建一個測試模塊a.erl,如下操作會生成core erlang代碼而非a.beam:
Eshell V6.0 (abort with ^G) 1> c(a,[to_core]).
2> c(a,[from_core]).
{ok,a}
這時已經看到a.beam了,打開a.core的文件,這些看起來熟悉卻又有點奇怪的代碼是什么意思?有什么用呢?
What is Core Erlang?
Core Erlang 項目地址: http://www.it.uu.se/research/group/hipe/cerl/
Why Core Erlang ?
Core Erlang 是Erlang的一種中間表現形式,Erlang在語法層面一直在演變,越發復雜,一些代碼分析工具或者調試工具解讀代碼就不方便了.Core Erlang就是因此而生,它盡可能的保持語法簡單,穩定以方便工具解析,同時具備代碼可讀性以方便手工修改代碼.
換句話說,通過Core Erlang我們可以透過語法糖看到真實的代碼邏輯是怎樣的,在之前分析Erlang語法相關的博文中我就數次使用Core Erlang,比如:
[Erlang 0034] Erlang iolist 通過Core Erlang 查看iolists內部表示
http://www.cnblogs.com/me-sa/archive/2012/01/31/erlang0034.html
[Erlang 0039] Erlang Inheritance 通過Core Erlang看所謂的繼承extends實際上是做了什么
http://www.cnblogs.com/me-sa/archive/2012/02/17/erlang0039.html
[Erlang 0058] Erlang Function調用效率 通過Core Erlang看幾種函數調用方式性能差異是如何產生的
http://www.cnblogs.com/me-sa/archive/2012/05/06/erlang-function-call-efficiency.html
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 補充
http://www.erlang.org/download/otp_src_17.0.readme
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 再次補充
