1. 程式人生 > >[Erlang 0120] Know a little Core Erlang

[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?

   目前Core Erlang專案由HIPE和OTP團隊共同維護,專案首頁上有兩份非常重要的資料:語言規格說明和簡介性質的論文 注:不想下載Postscript檔案閱讀器的,可以線上轉換ps到PDF http://www.ps2pdf.com/

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 再次補充