Erlang 添加 Maps数据类型并非非常突然,由于这个提议已经进行了2~3年之久,仅仅只是Joe Armstrong老爷子近期一篇文章Big changes to Erlang掀起不小了风浪.这篇文章用了比較夸张的说法:"Records are dead - long live maps !",紧接着在国内国外社区这句话就传遍了.立即就有开发人员忧心忡忡的在Stackoverflow上提问:Will Erlang R17 still have records?

 

 
   套用一句文艺的话,当我们谈论Maps时,实际上是表达我们对record的不满,这些不满/痛点恰好就是我们寄希望于Maps能够提供给我们的.本文将尽可能的逐一列出这些点,并尝试分析原因,下篇文章将深入分析Maps的一些细节.
 
 

Record的痛点

 
  使用Record我们遇到哪些痛点呢?

这些痛点在Maps出现之后有所改善吗?

我们先从细数痛点開始:

 
1.能够把record的name用作參数吗?

简单讲就是#RecordName{} 能够吗? 
 
1
2
3
4
5
6
7
8
9
10
7> rd(person,{name,id}).
person
8> #person{}.
#person{name = undefined,id = undefined}
9> P=person.
person
10> #P{}.
* 1: syntax error before: P
10>

  

 
 
2.能够把record的filed作为參数使用吗?

 
1
2
3
4
5
10> N=name.
name
11> #person{N="zen"}.
* 1: field 'N' is not an atom or _ in record person
12>

  

Modify a record in Erlang by programmatically specifying the field to modify
 
解决问题能够关注dynarec项目,能够动态生成record字段值的getter和setter訪问入口. https://github.com/jcomellas/mlapi/blob/master/src/dynarec.erl
 
3. a.b.c.d.e.f 能实现吗?

 
在有些语言中会有Fluent API(或 Fluent Interface)的设计,目的是在语法层面方便完毕一系列连续的逻辑.在使用嵌套record的时候,我们特别希望能用a.b.c.d.e.f的方式来简化代码,而实际上是以下这个样子:
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Eshell V6.0  (abort with ^G)
1> rd(foo,{a,b,c}).
foo
2>  rd(a,{f,m}).
a
3>  rd(f,{id,name}).
f
4>  #foo{a=#a{f=#f{id=2002,name="zen"},m=1984},b=1234,c=2465}.
#foo{a = #a{f = #f{id = 2002,name = "zen"},m = 1984},
     b = 1234,c = 2465}
5> D=v(4).
#foo{a = #a{f = #f{id = 2002,name = "zen"},m = 1984},
     b = 1234,c = 2465}
6> D#foo.a#a.f#f.name.
"zen"

  

有一个开源项目recbird就能够实现这样的效果,解决的路子当然是parse_transform, 须要在代码中加入-compile({parse_transform, recbird}).选项
recbird的作者是dcaoyuan,这个代码也是作为ErlyBird的一部分host在sourceforge:
 
 
4.record转proplists proplists转record
 
  为什么要转换properlist?其目的就是方便检索字段值.
 
 
5.key仅仅能是atom
  的确有人提过这个 
 
6.record往往要定义在hrl中
 
 

原因何在?

 
   在record相关的问题中,经常提到的一个词就是"compile-time dependency",即record仅仅存在于编译时,并没有相应实际的数据类型.record本质上是tuple在语法层面的语法糖,而上面record的诸多问题事实上就是源于tuple,在著名的exprecs项目,有这样一段描写叙述:
 
This parse transform can be used to reduce compile-time dependencies in large systems.

In the old days, before records, Erlang programmers often wrote access functions for tuple data. This was tedious and error-prone. The record syntax made this easier, but since records were implemented fully in the pre-processor, a nasty compile-time dependency was introduced.

This module automates the generation of access functions for records. While this method cannot fully replace the utility of pattern matching, it does allow a fair bit of functionality on records without the need for compile-time dependencies.

 
 
 
Record即Tuple
 
  在内部表示没有record仅仅有tuple, 以下是Erlang数据内部表示的介绍,我做了一个长图:
源文档地址:http://www.erlang-factory.com/upload/presentations/467/Halfword_EUC_2011.pdf  (这个文档在我们的 Erlang Resources 小站多次推荐过)
 
 
 
 
 
 这几张图能够帮助我们建立起来Erlang数据内部表示的思考模型,我们简单梳理一下:
   Beam(Björns/Bogdans Erlang Abstract Machine)虚拟机,包括一个拥有1024个虚拟寄存器的虚拟寄存器机,程序变量可能存储在register或stack;垃圾回收是以进程为单位,逐代进行;Beam包括一个常量池( constant pool)不被GC.大型二进制数据在Heap外,并可被多个进程共享;VM Code中用来表达数据类型使用的概念是Eterm:一个Eterm通常一个字(word)大小( sizeof(void *)),进程的Heap实际上就是Eterm构成的数组,ETS也是以Eterm的形式存储数据.寄存器(register)也是Eterm,VM中的stack也是由Eterm组成;VM须要在进程heap上分配一些Eterm来表示一些复杂的数据结构比方list,tuple;假设变量指向的数据复杂,那么stack/register会包括指向heap的指针,换句话话说,Eterm要支持指针;
 Eterm事实上是使用一些二进制数据位来标记当前的数据类型,Erlang使用了一个层次化的标记系统,最基础的是使用最低两位primary tags来标识:
 00 = Continuation pointer (return address on stack) or header word on heap
 01 = Cons cell (list)
 10 = Boxed (tuple, float, bignum, binary, external pid/port, exterrnal/internal ref ...)
 11 =  Immediate (the rest - secondary tag present)
 
详细到Boxed类型,继续细分:
– 0000 = Tuple
– 0001 = Binary match state (internal type)
– 001x = Bignum (needs more than 28 bits)
– 0100 = Ref
– 0101 = Fun
– 0110 = Float
– 0111 = Export fun (make_fun/3)
– 1000 - 1010 = Binaries
– 1100 - 1110 = External entities (Pids, Ports and Refs)
 
看到了吧,这里已经没有record的踪影了,仅仅有tuple,而对于Maps,我们已经能够在17.0-rc2/erts/emulator/beam/erl_term.h的代码中找到它的subtag:
 
#define ARITYVAL_SUBTAG          (0x0 << _TAG_PRIMARY_SIZE) /* TUPLE */
#define BIN_MATCHSTATE_SUBTAG     (0x1 << _TAG_PRIMARY_SIZE)
#define POS_BIG_SUBTAG          (0x2 << _TAG_PRIMARY_SIZE) /* BIG: tags 2&3 */
#define NEG_BIG_SUBTAG          (0x3 << _TAG_PRIMARY_SIZE) /* BIG: tags 2&3 */
#define _BIG_SIGN_BIT          (0x1 << _TAG_PRIMARY_SIZE)
#define REF_SUBTAG          (0x4 << _TAG_PRIMARY_SIZE) /* REF */
#define FUN_SUBTAG          (0x5 << _TAG_PRIMARY_SIZE) /* FUN */
#define FLOAT_SUBTAG          (0x6 << _TAG_PRIMARY_SIZE) /* FLOAT */
#define EXPORT_SUBTAG          (0x7 << _TAG_PRIMARY_SIZE) /* FLOAT */
#define _BINARY_XXX_MASK     (0x3 << _TAG_PRIMARY_SIZE)
#define REFC_BINARY_SUBTAG     (0x8 << _TAG_PRIMARY_SIZE) /* BINARY */
#define HEAP_BINARY_SUBTAG     (0x9 << _TAG_PRIMARY_SIZE) /* BINARY */
#define SUB_BINARY_SUBTAG     (0xA << _TAG_PRIMARY_SIZE) /* BINARY */
#define MAP_SUBTAG          (0xB << _TAG_PRIMARY_SIZE) /* MAP */
#define EXTERNAL_PID_SUBTAG     (0xC << _TAG_PRIMARY_SIZE) /* EXTERNAL_PID */
#define EXTERNAL_PORT_SUBTAG     (0xD << _TAG_PRIMARY_SIZE) /* EXTERNAL_PORT */
#define EXTERNAL_REF_SUBTAG     (0xE << _TAG_PRIMARY_SIZE) /* EXTERNAL_REF */
 
  感兴趣的话,能够继续在otp_src_17.0-rc2\erts\emulator\beam\erl_term.h中看到tuple实现相关的代码,搜索/* tuple access methods */代码段.
 
  看到这里,Stackoverflow 有个问题讨论"Does erlang implement record copy-and-modify in any clever way?"
 
  注意里面提到的erts_debug:size/1 和 erts_debug:flat_size/1方法,能够帮助我们查看共享和非共享状态数据占用的字数.所谓的共享和非共享,就是通过复用一些数据块(即指针指向)而不是通过数据拷贝,这样提高效率.在一些万不得已的情况下再触发拷贝,比方数据发往别的节点,存入ETS等等,Erlang Efficiency Guide 非常多优化的小技巧都是从这个出发点考虑的.
 
 那去掉primary tag和sub tag之后tuple是一个什么样的数据结构呢?我们能够从两个角度来看,首先是Erlang Interface Reference Manual
erl_mk_tuple方法明白指示了tuple实际上是一个Eterm的数组:
 
ETERM *erl_mk_tuple(array, arrsize)
Types:
ETERM **array;
int arrsize;
Creates an Erlang tuple from an array of Erlang terms.
array is an array of Erlang terms.
arrsize is the number of elements in array.
 
  另外一个角度就是在bif.c中,tuple_to_list和list_to_tuple的实现,事实上就是数组和链表的互相转换,看代码还能够知道通过make_arityval(len)冗余了数组的长度.对于tuple,获得size和依照索引訪问数据都是非常快的.这也就是找EEP43中提到过的Record的优势:
  • 高速查询 O(1), 编译期间完毕了对key的索引,对于小数据量存取相当快 (~50 values),
  • 没有过多额外的内存消耗,仅仅有Value和name 2+ N个字 (name + size+ N)
  • 函数头完毕匹配
而编译期一过,record提供的语法红利没有了,剩下的也就是高速获得tuple size和依照索引訪问数据了.exprecs项目所谓 reduce compile-time dependencies 事实上就是在编译阶段把一些语法红利继续保持下去,比方能够依照record name去new一个record,依照字段索引位置訪问数据等等.上面提到的record与proplists的转换,实际上是把解决问题的时机从编译期推迟到了执行时.
 
 
 
讲到这里,你可能非常期待了,Erlang R17之后加入的Maps又攻克了什么问题?带来了什么惊喜呢?

Maps与Record是一场你死我活的PK么?我们明天再说,敬请关注.

 
 
PS. Joe Armstrong老爷子文章中提到的Names in Funs 之前我们已经讨论过多次了:
 
[Erlang 0056] 用fun在Erlang Shell中编写尾递归 Ⅱ
http://www.cnblogs.com/me-sa/archive/2012/04/28/2474892.html
[Erlang 0063] Joe Armstrong 《A Few Improvements to Erlang》EUC 2012
http://www.cnblogs.com/me-sa/archive/2012/06/06/2538941.html
 
 
相关资料:
 
 

当我们谈论Erlang Maps时,我们谈论什么 Part 1的更多相关文章

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

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

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

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

  3. [Erlang 0116] 当我们谈论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. 话题讨论&amp;征文--谈论大数据时我们在谈什么 获奖名单发布

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

  6. Erlang运行时的错误

    Erlang运行时发生错误时,会返回一些错误信息,理解这些信息,对于学好.用好Erlang来说是必要. Erlang中的运行错误包括:badarg, badarith, badmatch, funct ...

  7. 当我们看到phpinfo时在谈论什么

    我们在渗透测试的过程中,如果存在phpinfo界面,我们会想到什么? 大部分内容摘抄自:https://www.k0rz3n.com/2019/02/12/PHPINFO 中的重要信息/ 关于phpi ...

  8. 当我们在谈论 DevOps,我们在谈论什么?

    Cloud Insight 携手 BearyChat:打造适合运维人员的团队协作工具 走过 C 轮的 OneAPM,旗下的产品已经日渐丰满,从应用性能监控的 Application Insight 到 ...

  9. 项目 erlang启动时死循环

    机子里的otp是新装的 看了一下main 是在util:ensure_started一堆app的时候死讯了, 按照顺序是sasl crypto asn1 public_key ssl 发现是publi ...

随机推荐

  1. python3 之 Ellipsis

    在翻django 代码的时候无意中看到的, 主要还是在注解时候使用 官方参考:https://docs.python.org/3/library/constants.html#Ellipsis 注意: ...

  2. luogu P3414 SAC#1 - 组合数(组合数学)

    题意 求sigma(C(n,i))其中C是组合数(即C(n,i)表示n个物品无顺序选取i个的方案数),i取从0到n所有偶数. 由于答案可能很大,请输出答案对6662333的余数. (n<=101 ...

  3. python 中进制转换及format(),int()函数用法

    python中数值型变量好像只能是十进制形式表示,其他类型变量只能以字符串形式存在,可以通过format函数将int类型变量转换成其他进制字符串,如下所示: v_code=15 # 2进制 x=for ...

  4. MySQL中锁详解(行锁、表锁、页锁、悲观锁、乐观锁等)

    原文地址:http://blog.csdn.net/mysteryhaohao/article/details/51669741 锁,在现实生活中是为我们想要隐藏于外界所使用的一种工具.在计算机中,是 ...

  5. 将shell脚本运行情况写入Rsyslog日志server

    在运维工作中,免不了编写一些脚本交由计划任务(cron)去定时运行完毕一些日常工作,实现运维工作自己主动化.比方在我的日常工作中备份数据是一项重要的工作,须要定时将数据备份到备份服器和一些其它的备份介 ...

  6. 养活一款APP要“烧”多少钱?

    Duang!又一款APP刷爆朋友圈.大片范儿的电影截图.意味深长的经典对白均出自一款名为“足记”的APP. 足记团队刚于去年8月完成天使期融资,投资方是光速创投和紫辉创投,目前正准备A轮融资.且近一周 ...

  7. Java设置Client Socket链接Server超时时间

    Java设置Client Socket链接Server超时时间 学习了:http://blog.csdn.net/tterminator/article/details/52494141 http:/ ...

  8. 13款用于拍摄全景照片的iOS应用

    全景图是一种大画幅.用来展示尽量多的周围环境的照片,甚至能够展示一个球状的完整空间,让观赏者直接“站在”摄影师的位置,在照片里将该环境一览无余.全景照片能够以最直观的方式向人们展示和记录一个美丽风景的 ...

  9. Mysql信息数据库:Information_schema

    1. Information_schema information_schema是mysql的信息数据库. 通过该库能够查看mysql以下的数据库,表.权限等信息. 在数据库中会默认生成这个库.inf ...

  10. php使用flock堵塞写入文件和非堵塞写入文件

    php使用flock堵塞写入文件和非堵塞写入文件 堵塞写入代码:(全部程序会等待上次程序运行结束才会运行,30秒会超时) <?php $file = fopen("test.txt&q ...