前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >elixir:灵丹妙药?or 徒有其名?

elixir:灵丹妙药?or 徒有其名?

作者头像
tyrchen
发布2018-03-28 15:22:03
1.4K0
发布2018-03-28 15:22:03
举报
文章被收录于专栏:程序人生程序人生

13年的时候正在追Erlang,有天看见Joe老爷子的一篇博客介绍Elixir [1],才第一次听到这个语言。

This has been my first week with Elixir, and I’m pretty excited. Elixir has a non-scary syntax and combines the good features of Ruby and Erlang. It’s not Erlang and it’s not Ruby and it has ideas of its own. It’s a new language, but books are being written as the language is being developed. The first Erlang book came 7 years after Erlang was invented, and the first popular book 14 years later. 21 years is too long to wait for a decent book. Dave loves Elixir, I think it’s pretty cool, I think we’re going to have fun together.

这评价从Joe老爷子嘴里吐出实属不易 —— 颇有点拍拍 Jose Valim 的肩膀,说「小伙子,努力干,偶看好你噢」的赶脚。噢,我忘了介绍,Joe老爷子是Erlang的创始人,Jose是Elixir的创始人(之前是Rails的core member),两人看上去相差三十岁。去年的文章我曾经讲过程序员不得不看的几本编程书,老爷子的 "Programming Erlang: Software for a Concurrent World",就是其中不得不看的一本(现在有2nd edition了)。好的编程书重在讲语法背后的思想,设计背后的初衷,如果单纯是要了解语法那些肤浅的东西,看看 "Learn X in Y minutes" 就好。所以大家看编程书的话,最好看语言作者的书,实在没有,也要看社区里的大牛的 —— 因为他们聊的不是语法(当然也不是寂寞 ^_^)!

扯远了,那时候把玩了一下Elixir,就像『七周七语言』那样,玩两周就算了,没有深入。唯一的感觉是:哇,BEAM [2] 上总算有一个让人好好写代码的语言了 [3]。

两年多的时光弹指过去,Elixir在最近终于发布了1.0.x版本,而Jose本人又频频上镜,到处布道Elixir,我才重新关注起这门语言。一门社区驱动的语言(或者框架),在没有到1.0之前,都意味着语法和库函数的极不稳定。1.0以后,起码意味着你可以拿它写点什么,而不至于写下的代码半年后就完全没法工作 [4]。所以我就重新拾起Elixir的文档,边啃边写。

差不多一个月下来,写了二三十个小项目,从ip packet的parsing,到http reverse proxy,都是几百行以内,一两个小时顶多到一整天能搞定的东东。借着这股兴奋劲,我来讲讲自己对Elixir的浅显认知。

惊艳的语法

Elixir的语法在向Ruby致敬,同时透着Erlang和Prolog的灵气。任何语言语法的设计都和其创始人的偏好和目标分不开,Ken Thompson/Rob Pike的golang看上去很C,Jose Valim的Elixir自然就很Ruby。当然,植根于Erlang的Elixir,又有有很多自己的特点。

最让人爱不释手的是pipe |>,它让你把一层层的逆着你的思维的函数调用变成了更直观的表现,比如说我们常常这么写代码:

代码语言:javascript
复制
IO.puts(tabularize(to_map(Store.get_host(host))))

或者

list_data = Store.get_host(host)
map = to_map(list)
formatted_output = tabularize(map)
IO.puts(formatted_output)

这样的代码在Elixir中可以被写成:

代码语言:javascript
复制
host
|> Store.get_host
|> to_map
|> tabularize
|> IO.puts

非常清晰 - 最重要的是,它更符合你的思维模式,让代码更容易在指尖流淌。我们写代码的时候,基本就是一个不断「分治」的过程:把大问题分解成小问题,小问题分解成更小的问题,最终解决问题。而Elixir让你的代码和你的思路高度一致。

这个语法特点来源于Prolog,遗憾的是,继承自Prolog的Erlang没有将其捡来,却把它遗给了继承于Erlang的Elixir。

看到这里,有同学也许会问?这不是object chaining么?老娘/老子在Ruby里,或者在jquery中,经常这么写代码。。。

虽然pipe和chaining表述代码的方式有些类似,但背后的思想不太一样。chaining是在对象上不断执行其方法,类似于语法糖,而pipe是把上一次的执行结果传递给下一个函数的第一个参数,和unix的pipe类似。chaining的限制很大,为此你要牺牲方法的特性 [5],而pipe非常灵活,你可以一边组织思路一边组合函数,有点搭积木的节奏。

其它的语法细节,如函数式编程,sigils,first class doc等等,就不提了,感兴趣的可以自行了解。

Pattern matching

我们知道,Erlang在concurrency以外的另一大特点是pattern matching,它能让你把绕来绕去的if/else变得简单明了。如果你不幸在工作中遇见if/else hell,再看过支持pattern matching的语言,你一定会泪流满面。

那么问题来了,当pipe遇见pattern matching是什么光景?看下面的代码:

浅显易懂,还很难有逻辑错误。这个代码里同一个 run 被定义了很多次,根据参数的不同,会调用不同的函数。我们再看一个例子:

除了pipe,里面用到了pattern matching + recursion,这里还是分治的思想。它是Elixir下写代码的一个很自然的模式:任务不断拆解,每个函数专注只干一件事。当然,几乎所有的语言都希望开发者这么做,但不少都没有提供正确的工具让开发者自然而然这么做。

使用pattern matching取代大部分条件分支是件相当伟大的事情:代码的简洁自不必说,其效率还有可能进一步优化。ifelse是一种顺序执行的逻辑,因为其语法结构的灵活(if的条件里是个函数这事大家都干吧),顶多是对一些特殊的情况使用跳转表优化,大多数情况是O(N),而且很难并行处理。而pattern matching由于其语法上的限制,很多情况可以被优化成decising tree,时间复杂度是O(logN),而且未来还有并行处理的优化空间。

当pattern matching遇见macro

当然以上的好处也是erlang的好处,但Elixir在此基础上做了一件也许是跨时代的事情:支持macro。Ruby也支持macro,任何从lisp演进或者接受lisp思想的语言也支持macro,为什么Elixir支持macro如此特殊?目前已有的支持macro的语言,macro更多地被用作突破语法的极限 —— 要么用于定义DSL让代码简洁,如rails;要么用于生成繁杂的接口代码而不必手工撰写。但Elixir在BEAM上支持macro,不管是有心还是无心,跟pattern matching一配合,带来了无穷的想象空间。

Elixir的unicode的大小写转换不必再提,我在「颠覆者的游戏」一文已经介绍过。类似的问题都可以这么处理。比如说我昨天做了一个中文简繁转换的模块:把wikipedia的最新词库导入,使用macro在编译时生成近10,000个按词进行正向最大匹配的递归函数,代码却仅需200行(见 github.com/tyrchen/chinease_translation,如果觉得好,请在github点赞)。以此类推,中文短句的slugify也就是同等规模的问题。

还有数据清洗和数据过滤。比如说众所周知的敏感词过滤。敏感词词库一更新,只需要重新编译出新的代码,加载即可(BEAM支持hot code reload)。

再讲一些做系统的新思路:

  • ✓ 用户名保留。使用一个文本字典,记录要保留的用户名。用macro生成pattern matching的代码。
  • ✓ 弱密码防护。把黑客用于攻击的常见字典编译出一个密码黑名单的pattern matching的代码。
  • ✓ 文本分析。对于格式各异的日志文件,定义抓取范式,然后通过这些范式生成pattern matching的代码。

等等。它们共同的特点是把原来依赖于数据库才能完成的事情,交给了编译时完成。花了很小的代码,我们就享受运行时的高效,还有组件化,没有外部依赖等等好处。我还没有具体测试过对于某种pattern,生成的函数超过10k级别的时的BEAM的处理效率,但在10k及以下的pattern,效率非常非常高。

天生的concurrency支持

这个就不多说了,Erlang的基于actor的并发模型,let it crash的处理思想,supervision tree,error kernel,都是在二十多年来与并发作斗争过程中不断总结出来的best practice,无论在思想上,还是实操上,在可预见的未来,没有语言能够超越它。Elixir站在巨人的肩膀上,坐享其成。

服务周到的工具链

进入21世纪以来,新兴的语言都在工具链上卯足了劲,工具链(几乎)成为语言的一部分(一起ship),而非附属品。从产品设计的角度上,这是非常英明的 —— 语言间的竞争如此激烈,光盘儿正条儿顺是不够的,得舍下身段做丫鬟 —— 谁把程序员哄的开心,谁胜出的几率要更大些。

你仔细想想golang的 go xxx(test/fmt/get),nodejs的npm,就大体知道我想讲什么了。作为一个C程序员,为了使用一个lib所犯下的折腾,让我感觉自己活在史前文明;而作为一个python(java/ruby)程序员,过度繁荣(各有优劣)的工具链市场又让我在适配上花太多时间 —— 三宫六院七十二妃,就是不如一个琴操姑娘好啊。

Elixir自身携带了mix —— 从项目的创建和scaffolding(mix new),编译(mix compile),到测试(mix test),到文档(mix doc),到依赖管理(mix deps.xxx),全部包圆,还有其它语言自带的工具如此全面么?据说以后还要把release和deployment管理起来,嗯,很好很强大。

总结

做硬件的兄弟总是嘲笑我们这些写软件的笨蛋们 —— 当他们做的硬件能够不断以搭积木的方式自我累积,数十亿个晶体管组成的复杂系统可以bug free时,我们写的软件却糟糕得一塌糊涂。原因有多方面的,如果从语言本身出发找原因,那就是语言本身并未让你以搭积木的方式组织系统。

那么,什么样的语言更容易贴近搭积木的组织方式呢?

  • ✓ 提倡使用递归(递归就是以自身为积木)
  • ✓ 以pattern matching的方式组织代码(每个代码快尽可能小,只处理一件简单的事情)
  • ✓ 语言层面提供解耦的工具(如erlang的process,golang的chan,scala的actor)
  • ✓ 系统的一部分损坏,并不影响未损坏的部分

嗯,Elixir是灵丹妙药,还是徒有其名?让时间来说明一切。就这么多(真没想到这篇文章断断续续写了快一周了)。


1. 见:http://joearms.github.io/2013/05/31/a-week-with-elixir.html

2. Erlang的VM

3. 初学者在Erlang的世界里很容易找不到北,这个,走过这段路的人都有感受

4. 这一点,我在meteor下吃了大亏,我的teamspark写于0.5.x,然后每一次版本升级,就各种crash…

5. 比如说本来可以返回一个结果,却不得不返回自己,而把结果存储在对象中

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2015-03-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序人生 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 惊艳的语法
  • Pattern matching
  • 当pattern matching遇见macro
  • 天生的concurrency支持
  • 服务周到的工具链
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档