惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
Docker
阮一峰的网络日志
阮一峰的网络日志
T
Tailwind CSS Blog
博客园 - 【当耐特】
量子位
博客园 - 叶小钗
有赞技术团队
有赞技术团队
Jina AI
Jina AI
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - Franky
博客园 - 司徒正美
爱范儿
爱范儿
美团技术团队
小众软件
小众软件
酷 壳 – CoolShell
酷 壳 – CoolShell
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
V
V2EX
罗磊的独立博客
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Last Week in AI
Last Week in AI
Hugging Face - Blog
Hugging Face - Blog
I
InfoQ
D
DataBreaches.Net
宝玉的分享
宝玉的分享

博客园 - 坚强2002

[Erlang 0129] Erlang 杂记 VI Graphviz绘制百家争鸣图 [Erlang 0128] Term sharing in Erlang/OTP 下篇 [Erlang 0127] Term sharing in Erlang/OTP 上篇 Elixir 1.0 Release [Erlang 0126] 我们读过的Erlang论文 读几首诗 [Erlang 0125] Know a little Erlang opcode "Becoming Functional" 阅读笔记+思维导图 [Erlang 0124] Erlang Unicode 两三事 - 补遗 [Erlang 0123] Erlang EPMD [Erlang 0122] Erlang Resources 2014年1月~6月资讯合集 [Erlang 0121] 当我们谈论Erlang Maps时,我们谈论什么 Part 3 [Erlang 0119] Erlang OTP 源码阅读指引 [Erlang 0118] Erlang 杂记 V [Erlang 0117] 当我们谈论Erlang Maps时,我们谈论什么 Part 2 [Erlang 0116] 当我们谈论Erlang Maps时,我们谈论什么 Part 1 致鸡鸣狗盗 一个技术人的知识管理方法论
[Erlang 0120] Know a little Core Erlang
坚强2002 · 2014-04-05 · via 博客园 - 坚强2002


  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 项目地址:  http://www.it.uu.se/research/group/hipe/cerl/ 

   目前Core Erlang项目由HIPE和OTP团队共同维护,项目首页上有两份非常重要的资料:语言规格说明和简介性质的论文

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

Abstraction and Model Checking of Core Erlang Programs in Maude

PHP-CoreErlang

PHP-CoreErlang is a DSL (domain specific language) for PHP, which generates "Core Erlang" .core files, which allows one to target the Erlang VM.

Erlang Types, Abstract Form & Core

Write A Template Compiler For Erlang

Implementing languages on the Erlang VM