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

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>

  

Is it possible to use record name as a parameter in Erlang
http://stackoverflow.com/questions/4103731/is-it-possible-to-use-record-name-as-a-parameter-in-erlang

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

http://stackoverflow.com/questions/13188449/modify-a-record-in-erlang-by-programmatically-specifying-the-field-to-modify/13188717#13188717

解决这个问题可以关注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:

http://sourceforge.net/p/erlybird/code/HEAD/tree/trunk/erlybird/erlang-snippets/recbird.erl

4.record转proplists proplists转record

为什么要转换properlist?其目的就是方便检索字段值.

这个之前讨论过 http://www.cnblogs.com/me-sa/archive/2012/05/22/erlang-code-snippet-2.html

record_info扩展项目 https://github.com/hio/erlang-record_info/blob/master/src/record_info.erl

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 0050]用fun在Erlang Shell中编写尾递归
http://www.cnblogs.com/me-sa/archive/2012/03/24/you-win-yourself-zen-this-is-the-50-erlang-article-go-on.html

[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

相关资料:

[0] setelement/3 优化http://www.erlang.org/doc/efficiency_guide/commoncaveats.html#id62422

[1] http://www.erlang.org/doc/man/erl_eterm.html

[2] http://erlang.org/doc/efficiency_guide/users_guide.html

时间: 2024-10-08 16:04:36

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

当我们谈论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的要点,有兴趣的同学能够FQ观看视频 W

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

从社会发展趋势的角度,非常明显大数据会是眼下肉眼可及的视野范围里能看到的最大趋势之中的一个.从传统IT 业到互联网.互联网到移动互联网,从以智能手机和Pad 为主要终端载体的移动互联网到可穿戴设备的移动互联网,然后再到万物互联的物联网,这一定是不可违抗的发展规律和前进方向.伴随着这个趋势必定有越来越多.形态越来越丰富的超量数据不断产生,而大数据明显是由此衍生出来的明白且必定的发展趋势. 讨论话题:谈论大数据时我们在谈什么 话题提示: 1   您能接触到的大数据有哪些? 2   您最想了解的大数据

谈论大数据时我们在谈什么

从社会发展趋势的角度,很明显大数据会是目前肉眼可及的视野范围里能看到的最大趋势之一.从传统IT 业到互联网.互联网到移动互联网,从以智能手机和Pad 为主要终端载体的移动互联网到可穿戴设备的移动互联网,然后再到万物互联的物联网,这一定是不可违抗的发展规律和前进方向.伴随着这个趋势必然有越来越多.形态越来越丰富的超量数据不断产生,而大数据明显是由此衍生出来的明确且必然的发展趋势. 讨论话题:谈论大数据时我们在谈什么 话题提示: 1   您能接触到的大数据有哪些? 2   您最想了解的大数据架构与算

当我们谈论跳槽时在谈论什么

3月9号时,微信上突然收到一条消息: 公司给涨了几K工资,且答应让我自己完成公司的DLP的网络驱动--终于能锻炼了. 我一看,是原来在微信上向我咨询过职业选择问题的一个朋友. 当时他在微信里说,他喜欢做底层开发,可所在公司安排他做应用开发,他觉得没意思,学不到什么东西,担心将来没竞争力,想要跳槽去做自己喜欢的事情. 当时我考虑到他学历较差,劝他先工作几年涨涨经验,用经验和能力洗掉学历对自己的影响之后再跳槽.我的建议出于现实情况,是比较保守的.他犹豫着,觉得非离职不可. 后来他提到可以跟公司谈一下

项目 erlang启动时死循环

机子里的otp是新装的 看了一下main 是在util:ensure_started一堆app的时候死讯了, 按照顺序是sasl crypto asn1 public_key ssl 发现是public_key,时报了crypto没启动,但是application:start(crypto)就没报错 后来发现是case的时候没有捕获{error,"no such file or directory","crypto.app"},只处理了依赖关系 全文搜索了下publ

对Erlang开发者的几点建议

* 确保没有任何编译警告 * Erlang中String采用list实现,32位系统中,其1个字符用8个字节的空间(4个保存value, 4个保存指针).因此string速度较慢,空间占用较大 * 在Server中,总是尽力书写尾递归(tail-recursive)的函数 * 使用'++'时,left list会被拷贝,然后添加到right list的头部,因此最好把length较短的list放在左侧 * 避免使用regexp,如果需要正则表达式,请使用re * timer模块的大部分函数实现,

Erlang 摘要

世界是并行的,Erlang程序反应了我们思考和交流的方式,人作为个体通过发送消息进行交流,如果有人死亡,其他人会注意到.Erlang里的模块类相当于OOPL中的类,进程相当于OOPL里的对象或类实例.并发编程可以用来提升性能,创建可扩展和容错的系统,以及编写清晰和可理解的程序来控制现实世界里的应用. 并发程序是以一种并发编程语言编写的程序,并发编程语言拥有用于编写并发程序的语言结构.Erlang的并发程序是由互相通信的多组顺序进程组成,一个进程就是一个轻量级的虚拟机,可以执行单个的Erlang函

Erlang 杂记

学习Erlang的时候在书的留白处随手记录了一些东西,还有一些记录在了demo的注释里面,今天抽时间整理出来了一部分,分享一下. Erlang的设计哲学是为每一个独立的事件创建一个新进程. Erlang的容错处理:如果不能完成一个任务就死掉 让其它正常的进程来善后.link函数就是用来建立这种进程间的双向连接来监测非正常退出,并做出处理. BIFs是built-in functions的缩写代表这些方法是Erlang运行时系统的一部分 side-effect-free无副作用,其中一种定义是说:

erlang 内存问题

1.生产上的服务器,运行到2天左右就会产生core文件,在家折腾了两天,把一些过程记录下来, 希望能帮上有需要的人. gdb  /usr/lib64/erlang/xxx/xxx/beam.smp  core.3435 .... gdb) bt #0  0x0000003b5bc30265 in raise () from /lib64/libc.so.6 #1  0x0000003b5bc31d10 in abort () from /lib64/libc.so.6 #2  0x0000000