zoukankan      html  css  js  c++  java
  • [Erlang 0117] 当我们谈论Erlang Maps时,我们谈论什么 Part 2

      声明:本文讨论的Erlang Maps是基于17.0-rc2,时间2014-3-4.后续Maps可能会出现语法或函数API上的有所调整,特此说明.

         

          前情提要: [Erlang 0116] 当我们谈论Erlang Maps时,我们谈论什么 Part 1 

          继续昨天的话题,在Erlang Factory SF Bay Area 2013有一个议题:"Where are we on the Map?" [PDF ],这个Talk基本上就是选取了EEP43的要点,有兴趣的同学可以翻墙观看视频 Where are We on the Map? - Kenneth Lundin - YouTube 如果是腿脚不利索的,可以看墙内的.仔细阅读EEP43,其信息量巨大,包括Maps的设计演变来龙去脉,各种取舍,也是我们学习设计的极佳范例.下面我将按照自己的逻辑顺序重新解读EEP43,先从如何使用开始,直观上感受一下区别,然后再回答"何必有我"的问题.

    Maps Basic

          EEP43 给出了Map比较规范的定义, Map M包含一定数量的键值对,实现从K1..Kn到V1..Vn的映射,其中没有两个Key是相等的(equal). equal指的是K1==K2,matching指的是K1 =:= K2. erlang:is_map(M)用于判断数据是否map类型.不过按照现在的情况,当出现1.0和1做key的时候,结果和EEP43中设计的结果不同,还是要看下一个版本是怎么处理的,这个不小心就是个坑:

    Eshell V6.0  (abort with ^G)
    1> M=#{1=>a}.   %% Construction syntax
    #{1 => a}
    2> M#{1.0 => b}.
    #{1 => a,1.0 => b}
    3> M#{1 => b}.
    #{1 => b}
    4> M#{1 := b}.
    #{1 => b}
    5> M#{1.0 := b}.
    ** exception error: bad argument
         in function  maps:update/3
            called as maps:update(1.0,b,#{1 => a})
         in call from erl_eval:'-expr/5-fun-0-'/2 (erl_eval.erl, line 249)
         in call from lists:foldl/3 (lists.erl, line 1261)
    6> M2= #{1=>a,1=>b,1.0 =>c}.
    #{1 => b,1.0 => c}
    7> 1 == 1.0.
    true
    8> #{1.0 =>a ,1 =>b}.
    #{1 => b,1.0 => a}
     
    

      

         构造Map的时候我们重点要验证的就是"Maps in Erlang are ordered, Important!!!! – Maps with the same set of keys are always presented in the same way":

    Eshell V6.0  (abort with ^G)
    1> #{a=>124,b=>1024,c=>23}.
    #{a => 124,b => 1024,c => 23}
    2> #{b=>1024,c=>23,a=>124}.
    #{a => 124,b => 1024,c => 23}
    3> #{b=>1024,a=>124,c=>23}.
    #{a => 124,b => 1024,c => 23}
    4> M=#{b=>1024,a=>124,c=>23}.
    #{a => 124,b => 1024,c => 23}
    

      

    看下Map的基本操作,构造,更新,模式匹配.注意下面代码中 #{f:=F,a:={A,B}} =  M.做匹配的时候,前面的部分key是顺序无关的.

    Eshell V6.0  (abort with ^G)
    1> M=#{a=>{1,2},b=>23,<<"OK">> =>ok, f=>fun()->receive a ->"Got a!" end end}.
    #{a => {1,2},b => 23,f => #Fun<erl_eval.20.101568567>,<<"OK">> => ok}
    2> M#{b=>1024}.
    #{a => {1,2},b => 1024,f => #Fun<erl_eval.20.101568567>,<<"OK">> => ok}
    3> M#{b2=>1023}.
    #{a => {1,2},b => 23,b2 => 1023,f => #Fun<erl_eval.20.101568567>,<<"OK">> => ok}
    4> M#{b2 :=1024}.
    ** exception error: bad argument
         in function  maps:update/3
            called as maps:update(b2,1024,
                                  #{a => {1,2},b => 23,f => #Fun<erl_eval.20.101568567>,<<"OK">> => ok})
         in call from erl_eval:'-expr/5-fun-0-'/2 (erl_eval.erl, line 249)
         in call from lists:foldl/3 (lists.erl, line 1261)
    5> M#{b :=1024}.
    #{a => {1,2},b => 1024,f => #Fun<erl_eval.20.101568567>,<<"OK">> => ok}
    6>  #{f:=F,a:={A,B}} =  M.
    #{a => {1,2},b => 23,f => #Fun<erl_eval.20.101568567>,<<"OK">> => ok}
    7> b().
    A = 1
    B = 2
    F = fun() ->
               receive
                   a ->
                       "Got a!"
               end
        end
    M = #{a => {1,2},b => 23,f => #Fun<erl_eval.20.101568567>,<<"OK">> => ok}
    ok
    8> 
    
     
    
     
    
    2> M=#{a=>123,b=>234}.
    #{a => 123,b => 234}
    3> M#{a=>1000}.
    #{a => 1000,b => 234}
    4> M#{not_key=>1000}.
    #{a => 123,b => 234,not_key => 1000}
    5> M#{a :=1024}.
    #{a => 1024,b => 234}
    6> M#{not_key :=1024}.
    ** exception error: bad argument
         in function  maps:update/3
            called as maps:update(not_key,1024,#{a => 123,b => 234})
         in call from erl_eval:'-expr/5-fun-0-'/2 (erl_eval.erl, line 249)
         in call from lists:foldl/3 (lists.erl, line 1261)
    7> P=#{name=>"zen",id=>2002}.
    #{id => 2002,name => "zen"}
    8> P#{naem=>1024}.
    #{id => 2002,naem => 1024,name => "zen"}
    

       

      上面更新字段名称的方式,是用=> 还是:= ?从工程层面考虑,我会选择 := 运算,因为当给一个不存在的key进行赋值的时候,会抛出错误bad argument.而上面第7~8行代码,本意是更新name字段,但是由于拼写错误原来的字段值没有修改,只增加了一错误字段.在工程代码中显然:=会大大降低排错的成本.

     然后就是常用的 Function Header Match,这里可以看到新增了两个Guard  erlang:is_map erlang:map_size

    -module(a).
    -compile(export_all).
    test(#{state := {ok,State}} =S) ->
      State.
     
    Eshell V6.0  (abort with ^G)
    1> a:test().
    ** exception error: undefined function a:test/0
    2> a:test(#{state => {ok,200}}).
    200
    3> a:test(#{state => {error,200}}).
    ** exception error: no function clause matching a:test(#{state => {error,200}}) (a.erl, line 7)
    4> 
     
    
     
    
    
    -module(a).
    -compile(export_all).
    
    a(#{state := {ok,State}} = S) when erlang:is_map(S) ->
      State.
    
    b(#{state := {ok,State}} = S) when erlang:map_size(S) >=1 ->
      State.
    
    test(#{state := {ok,State}} =S) when erlang:is_map(S) andalso erlang:map_size(S) >=1 ->
      State.
    
    Eshell V6.0  (abort with ^G)
    1> a:b(#{state => {ok,200},id=>123,dd=>11}).
    200
    2> a:a(#{state => {ok,200},id=>123,dd=>11}).
    200
    3> a:test(#{state => {ok,200},id=>123,dd=>11}).
    200
    4>
    4> 
    

       

    maps模块方面不知道变动还会有多少,就目前提供的函数和EEP中的描述还是有不少细微差别的,比如foldr foldl全部整合为fold函数;这个代码太长了,折叠一下:

      

    Eshell V6.0  (abort with ^G)
    
    1> M=#{a=>123,b=>{ok,200},{c,d}=> <<"dvd">>}.
    #{a => 123,b => {ok,200},{c,d} => <<"dvd">>}
    2> maps:new().
    #{}
    3> maps:remove(b,M).
    #{a => 123,{c,d} => <<"dvd">>}
    4> maps:get(b,M).
    {ok,200}
    5> maps:keys(M).
    [a,b,{c,d}]
    6> maps:get(not_key,M).
    ** exception error: bad_key
         in function  maps:get/2
            called as maps:get(not_key,#{a => 123,b => {ok,200},{c,d} => <<"dvd">>})
    7> maps:find(not_key,M).
    error
    8> maps:foldr(fun({K,V},Acc)->Acc++[{K,V}] end,[],M).
    ** exception error: undefined function maps:foldr/3
    9> maps:foldl(fun({K,V},Acc)->Acc++[{K,V}] end,[],M).
    ** exception error: undefined function maps:foldl/3
    10> maps:fold(fun({K,V},Acc)->Acc++[{K,V}] end,[],M).
    ** exception error: no function clause matching maps:fold(#Fun<erl_eval.12.101568567>,[],
                                                              #{a => 123,b => {ok,200},{c,d} => <<"dvd">>}) (maps.erl, line 168)
    11> maps:fold(fun({K,V},Acc)->Acc++[{K,V}] end,[],M).
    ** exception error: no function clause matching maps:fold(#Fun<erl_eval.12.101568567>,[],
                                                              #{a => 123,b => {ok,200},{c,d} => <<"dvd">>}) (maps.erl, line 168)
    12> maps:fold(fun(K,V,Acc)->Acc++[{K,V}] end,[],M).
    [{a,123},{b,{ok,200}},{{c,d},<<"dvd">>}]
    13> maps:to_list(M).
    [{a,123},{b,{ok,200}},{{c,d},<<"dvd">>}]
    14> maps:from_list(v(12)).
    #{a => 123,b => {ok,200},{c,d} => <<"dvd">>}
    16> maps:put(a,1984,M).
    #{a => 1984,b => {ok,200},{c,d} => <<"dvd">>}
    17> maps:values(M).
    [123,{ok,200},<<"dvd">>]
    18> maps:put(king,1984,M).
    #{a => 123,b => {ok,200},king => 1984,{c,d} => <<"dvd">>}
    19> maps:update(a,1984,M).
    #{a => 1984,b => {ok,200},{c,d} => <<"dvd">>}
    20> maps:update(king,1984,M).
    ** exception error: bad argument
         in function  maps:update/3
            called as maps:update(king,1984,#{a => 123,b => {ok,200},{c,d} => <<"dvd">>})
    21> maps:merge(M,#{name=>"zen"}).
    #{a => 123,b => {ok,200},name => "zen",{c,d} => <<"dvd">>}
    22> maps:is_key(king,M).
    false
    23> maps:is_key(a,M).
    true
    24> maps:without([a,b,c],M).
    #{{c,d} => <<"dvd">>}
    25>
    

      

    比较!比较!
     
      Map的设计定位是data-type,那就存在Map与其它数据类型的比较规则,以及Map数据之间的比较规则.
     
    [1] 与其它数据类型之间的比较
     
    number < atom < reference < fun < port < pid < tuple <map < list < bit string
     
    11> M= #{a=>123,b=>100}.
    #{a => 123,b => 100}
    12> M>12.
    true
    13> M> <<"12">>.
    false
    14> M> {a,b}.
    true
    15> M  > [1,2].
    false
    

      

    [2] Map之间比较
     
         Two different maps M1 and M2 are sorted first after size and secondly after their Key=>Value pairs.
          lists:sort([list_to_tuple(maps:to_list(M)) || M <- [M1,M2]). 
     

    运算符优先级

       没有应用在Map之间的运算符,只有两个内部的运算符=> :=; => 用于创建和更新k-v, := 用于更新已经存在的k-v, :=在匹配过程用来从指定的key中提取Value值;

    Why Maps?

          我们已经有了record,dicts,gb_trees,ets,proplists,为什么还要Maps?Maps和现有的数据结构相比,最大的优势就是充分发挥Erlang模式匹配的威力. 我还关心的是之前的问题是否解决了:

      

    1.可以把record的name用作参数吗?

    #RecordName{} 可以吗? 因为没有RecordName的限制了,所以这个问题自然消失;


    2.可以把record的filed作为参数使用吗?

    Eshell V6.0  (abort with ^G)
    1> M=#{a=>{1,2},b=>23,<<"OK">> =>ok, f=>fun()->receive a ->"Got a!" end end}.
    #{a => {1,2},b => 23,f => #Fun<erl_eval.20.101568567>,<<"OK">> => ok}
    2> N=a.
    a
    3> #{N := Data} =M.
    * 1: illegal use of variable 'N' in map
    4>
    

      

    而在代码模块中,下面的代码也会报错:illegal use of variable 'N' in map

    test3(N,#{N := Data}=M)->
      Data.


    3. a.b.c.d.e.f 能实现吗?

     
    上次提到的a.b.c.d.e这种fluent 式的写法吗?这是肯定被毙掉的方案.问题就出在点号上,一方面它是语句结束,一方面它还是浮点数中的小数点;下面就是一个非常悲剧的例子:
     
    1> M = #{ 1.1 => a, 1 => #{ 1 => b } }.
    #{ 1 => #{ 1 => b }, 1.1 => a }.
    
    2> #M.1.1.
    a | b ?
     
    

      

    4.record转proplists proplists转record

    现在已经没有必要转换了

    5.key只能是atom

    Maps中的Key可以是任意Erlang terms .

    6.record往往要定义在hrl中

    不需要了.

    那就现在的情况,Maps会替代Record吗?

      EEP43 的重要依据是Richard O'Keefes 的 No more need for records (fifth draft).可以说Maps缘起record替换方案.而Maps最终的设计目标是"Maps does not claim to be an replacement to records as the frames proposal does. Instead maps targets a larger usage domain and wishes to be a complement to records and supersede them where suitable."

      从语言长远发展看,Map如果提供足够的便利,以及性能保障,淘汰掉record是一个开发者主动选择的自然过程,是一个"Maps不杀record,record却因Maps而死"过程.对于开发者倒不必有什么恐慌,record不会一夜之间消失,那么多的项目哪会在一朝一夕之间完成过渡?顺其自然就好.

      

    悬而未决的功能

      有些语法特性在EEP43中提到了,但是在当前版本(17.0-rc2)并没有提供;首当其冲的就是"Accessing a single value",要达到这个目的可以通过模式匹配完成,也可以通过调用maps:get方法完成,所以我对这个方法的期待度并不大.

    Eshell V6.0  (abort with ^G)
    1> M=#{a=>12,b=>200,c=>234}.
    #{a => 12,b => 200,c => 234}
    2> #{b := B}=M.
    #{a => 12,b => 200,c => 234}
    3> B.
    200
    4> #{b := B,c := C}=M.
    #{a => 12,b => 200,c => 234}
    5> #{b := B,c := C,d:=D}=M.
    ** exception error: no match of right hand side value #{a => 12,b => 200,c => 234}
    

      

    其次就是Map comprehension ,我个人非常喜欢list comprehension,所以对这个功能还是非常期待的.

    M1 = #{ E0 => E1 || K := V <- M0  }

     OK,今天就到这里,期待Erlang新版本的发布.

      最后感谢支付宝付款的小伙伴们,昨天早晨老婆跟我说"短信通知有人在支付宝给你打钱了",我还说"这是什么新诈骗手段啊",验证之后真的是很惊喜,谢谢你们的认可和支持,我会继续努力的.

  • 相关阅读:
    新一代MQ apache pulsar的架构与核心概念
    Flutter使用fluwx实现微信分享
    BZOJ3622 已经没有什么好害怕的了 动态规划 容斥原理 组合数学
    NOIP2016提高组Day1T2 天天爱跑步 树链剖分 LCA 倍增 差分
    Codeforces 555C Case of Chocolate 其他
    NOIP2017提高组Day2T3 列队 洛谷P3960 线段树
    NOIP2017提高组Day2T2 宝藏 洛谷P3959 状压dp
    NOIP2017提高组Day1T3 逛公园 洛谷P3953 Tarjan 强连通缩点 SPFA 动态规划 最短路 拓扑序
    Codeforces 873F Forbidden Indices 字符串 SAM/(SA+单调栈)
    Codeforces 873E Awards For Contestants ST表
  • 原文地址:https://www.cnblogs.com/me-sa/p/when_we_talk_about_erlang_maps_2.html
Copyright © 2011-2022 走看看