声明:本文讨论的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新版本的发布.

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

[Erlang 0117] 当我们谈论Erlang Maps时,我们谈论什么 Part 2的更多相关文章

  1. 当我们谈论Erlang Maps时,我们谈论什么 Part 2

    声明:本文讨论的Erlang Maps是基于17.0-rc2,时间2014-3-4.兴许Maps可能会出现语法或函数API上的有所调整,特此说明. 前情提要: [Erlang 0116] 当我们谈论E ...

  2. [Erlang 0116] 当我们谈论Erlang Maps时,我们谈论什么 Part 1

         Erlang 增加 Maps数据类型并不是很突然,因为这个提议已经进行了2~3年之久,只不过Joe Armstrong老爷子最近一篇文章Big changes to Erlang掀起不小了风 ...

  3. 当我们谈论Erlang Maps时,我们谈论什么 Part 1

         Erlang 添加 Maps数据类型并非非常突然,由于这个提议已经进行了2~3年之久,仅仅只是Joe Armstrong老爷子近期一篇文章Big changes to Erlang掀起不小了 ...

  4. [Erlang 0121] 当我们谈论Erlang Maps时,我们谈论什么 Part 3

    Erlang/OTP 17.0 has been released  http://www.erlang.org/download/otp_src_17.0.readme     Erlang/OTP ...

  5. Erlang基础 -- 介绍 -- 历史及Erlang并发

    前言 最近在总结一些Erlang编程语言的基础知识,拟系统的介绍Erlang编程语言,从基础到进阶,然后再做Erlang编程语言有意思的库的分析. 其实,还是希望越来越多的人关注Erlang,使用Er ...

  6. 话题讨论&amp;征文--谈论大数据时我们在谈什么 获奖名单发布

    从社会发展趋势的角度,非常明显大数据会是眼下肉眼可及的视野范围里能看到的最大趋势之中的一个.从传统IT 业到互联网.互联网到移动互联网,从以智能手机和Pad 为主要终端载体的移动互联网到可穿戴设备的移 ...

  7. [Erlang 0109] From Elixir to Erlang Code

    Elixir代码最终编译成为erlang代码,这个过程是怎样的?本文通过一个小测试做下探索.         编译一旦完成,你就看到了真相   Elixir代码组织方式一方面和Erlang一样才用非常 ...

  8. [Erlang 0128] Term sharing in Erlang/OTP 下篇

    继续昨天的话题,昨天提到io:format对数据共享的间接影响,如果是下面两种情况恐怕更容易成为"坑", 呃,恰好我都遇到过; 如果是测试代码是下面这样,得到的结果会是怎样?猜! ...

  9. [Erlang 0127] Term sharing in Erlang/OTP 上篇

    之前,在 [Erlang 0126] 我们读过的Erlang论文 提到过下面这篇论文: On Preserving Term Sharing in the Erlang Virtual Machine ...

随机推荐

  1. EasyPR--开发详解(8)文字定位

    今天我们来介绍车牌定位中的一种新方法--文字定位方法(MSER),包括其主要设计思想与实现.接着我们会介绍一下EasyPR v1.5-beta版本中带来的几项改动. 一. 文字定位法 在EasyPR前 ...

  2. SQL Server 索引和表体系结构(聚集索引)

    聚集索引 概述 关于索引和表体系结构的概念一直都是讨论比较多的话题,其中表的各种存储形式是讨论的重点,在各个网站上面也有很多关于这方面写的不错的文章,我写这篇文章的目的也是为了将所有的知识点尽可能的组 ...

  3. C# 线程同步的三类情景

    C# 已经提供了我们几种非常好用的类库如 BackgroundWorker.Thread.Task等,借助它们,我们就能够分分钟编写出一个多线程的应用程序. 比如这样一个需求:有一个 Winform ...

  4. ABP框架 - 值对象

    文档目录 本节内容: 简介 值对象基类 最佳实践 简介 “一个表示领域的一个描述性方面的没有概念上的身份对象,称为值对象.“(Eric Evans). 与一个有身份(Id)实体相反,一个值对象没有身份 ...

  5. IT公司的女流之辈

    声明:并不是对女性怎么怎么滴歧视, 我只是想陈述事实. 女性来IT公司工作, 真的适合吗? 如果是杰出女性也就罢了, 如果只是一般女性呢? 她能够像一般男性一样的 努力工作, 像牛马一样的工作? 在某 ...

  6. IL指令详细表

    名称 说明 Add 将两个值相加并将结果推送到计算堆栈上. Add.Ovf 将两个整数相加,执行溢出检查,并且将结果推送到计算堆栈上. Add.Ovf.Un 将两个无符号整数值相加,执行溢出检查,并且 ...

  7. 【Win 10应用开发】自定义浮动层——Flyout

    最近几天总是下雨,真是“何处秋窗无雨声”,也“不知风雨几时休”. 好,进入正题. 弹出层有三种. 第一种是ContentDialog,即内容对话框,它其实类似于模态对话框,弹出后会覆盖整个窗口区域,并 ...

  8. 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(13)-系统日志和异常的处理③

    系列目录 上一节我们讲了如何捕获异常和记录日志,这一节我们讲,没有捕获的或者忘记捕获的异常包括404错误等,我们统一处理这个异常. 这一讲是利用 Application_Error 捕获所有异常,全局 ...

  9. 详解Javascript中正则表达式的使用

    正则表达式用来处理字符串特别好用,在JavaScript中能用到正则表达式的地方有很多,本文对正则表达式基础知识和Javascript中正则表达式的使用做一个总结. 第一部分简单列举了正则表达式在Ja ...

  10. [C#] 走进异步编程的世界 - 剖析异步方法(下)

    走进异步编程的世界 - 剖析异步方法(下) 序 感谢大家的支持,这是昨天发布<走进异步编程的世界 - 剖析异步方法(上)>的补充篇. 目录 异常处理 在调用方法中同步等待任务 在异步方法中 ...