Osheep

时光不回头,当下最重要。

Elixir初体验

在京东上搜Elixir,会发现Elixir是一种琴弦的品牌,然而我今天想讲的Elixir是一种编程语言。它的Logo如下

《Elixir初体验》

Elixir

早就有学习这门语言的打算,只是事情太多,忙着忙着就忘了。公司最近的技术分享中,明哥分享了一个关于Phoenix(基于Elixir的Web开发框架)跟Rails的比较的话题,重新燃起了我对这门语言的好奇心。接下来让我们一起简单的来看看Elixir这门语言。

1. Elixir是什么

我们先来看看官方的解释

Elixir is a dynamic, functional language designed for building scalable and maintainable applications.

Elixir 是一门动态的函数式编程语言,主要是用来构建可扩展,可维护的应用程序。好吧,把函数式去掉的话Ruby也可以做到。Ruby是一门动态的命令式编程语言,可以用来构建可扩展及可维护的应用程序。不过Ruby设计的初衷并不是为了解决什么问题,它的初衷是Happy Coding!

Elixir leverages the Erlang VM, known for running low-latency, distributed and fault-tolerant systems, while also being successfully used in web development and the embedded software domain.

Elixir运行在Erlang虚拟机上,以低负载,分布式和容错系统而闻名。已经成功应用在Web开发领域,以及嵌入式软件领域。这也算是编程语言的另一个发展方向吧,除了直接用系统级编程语言(如C,C++,Go)来开发编程语言,我们还可以在比较成熟的虚拟系统上构造我们想要的编程语言。比如JavaVM之上有人发明了Clojure,Scale这些函数式编程语言(身边的人好像比较喜欢Clojure),而有位Ruby英雄在ErlangVM上实现了Elixir。

2. 安装Elixir

安装教程没有能比官方文档更加详细的了,我这里就不重复说了,我就说说我在Mac上安装的时候遇到的比较尴尬的问题吧。

Elixir是运行在Erlang环境下的编程语言,当我们用HomeBrew安装Elixir的时候,它也会顺势帮你安装Erlang。

想想很久之前我们用HomeBrew安装了Elixir,以及配套的Erlang环境。然后我们卸载了Elixir,却留下了Erlang。几个月后我们重新安装Elixir这个时候,也配套安装了最新版本的Erlang,于是事故发生了。

《Elixir初体验》

两个不同版本的Erlang
> erl
Erlang/OTP 19 [erts-8.3] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Eshell V8.3  (abort with ^G)
1>

我们的系统同时保留着两个版本的Erlang,以及新版本的Elixir。而系统还默认引用这旧版本的Erlang,这表明了我们会把新版本的Elixir运行在旧版本的Erlang上面,你可能就会得到如下错误。

> iex
2017-11-02 21:54:29 Loading of ~ts failed: ~p

    "/usr/local/Cellar/elixir/1.5.2/bin/../lib/iex/ebin/Elixir.IEx.CLI.beam"
    badfile
2017-11-02 21:54:29 ~s~n
    "beam/beam_load.c(1287): Error loading module 'Elixir.IEx.CLI':\n  mandatory chunk of type 'Atom' not found\n\n"
2017-11-02 21:54:29 crash_report
    initial_call: {supervisor_bridge,user_sup,['Argument__1']}
    pid: <0.47.0>
    registered_name: []
    error_info: {exit,{undef,[{'Elixir.IEx.CLI',start,[],[]},{user_sup,start_user,3,[{file,"user_sup.erl"},{line,100}]},{user_sup,init,1,[{file,"user_sup.erl"},{line,49}]},{supervisor_bridge,init,1,[{file,"supervisor_bridge.erl"},{line,80}]},{gen_server,init_it,6,[{file,"gen_server.erl"},{line,328}]},{proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,247}]}]},[{gen_server,init_it,6,[{file,"gen_server.erl"},{line,352}]},{proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,247}]}]}
    ancestors: [kernel_sup,<0.34.0>]
    messages: []
    links: [<0.35.0>]
    dictionary: []
    trap_exit: true
    status: running
    heap_size: 610
    stack_size: 27
    reductions: 145
2017-11-02 21:54:29 supervisor_report
    supervisor: {local,kernel_sup}
    errorContext: start_error
    reason: {undef,[{'Elixir.IEx.CLI',start,[],[]},{user_sup,start_user,3,[{file,"user_sup.erl"},{line,100}]},{user_sup,init,1,[{file,"user_sup.erl"},{line,49}]},{supervisor_bridge,init,1,[{file,"supervisor_bridge.erl"},{line,80}]},{gen_server,init_it,6,[{file,"gen_server.erl"},{line,328}]},{proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,247}]}]}
    offender: [{pid,undefined},{id,user},{mfargs,{user_sup,start,[]}},{restart_type,temporary},{shutdown,2000},{child_type,supervisor}]
2017-11-02 21:54:29 crash_report
    initial_call: {application_master,init,['Argument__1','Argument__2','Argument__3','Argument__4']}
    pid: <0.33.0>
    registered_name: []

这是个错误示范,可能你也会遇到类似的问题,报错信息不会完全一样,不过应该也差不多。究其原因就如上面所说Eixir版本跟Erlang版本不兼容,当我们想在老版本的Erlang上跑新版本的Elixir就出问题了。

解决方法有两个

1) 切换Erlang的默认版本,换成我们期待的最新版本的Erlang

我们通过HomeBrew的命令

brew switch erlang 20.1.3

只需要简单地把默认Erlang运行环境指定到我们需要的最新版本的,然后重新运行Elixir就能够进入我们期待的REPL环境了。

> iex
Erlang/OTP 20 [erts-9.1.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.5.2) - press Ctrl+C to exit (type h() ENTER for help)

2) 卸载Elixir,以及所有版本的Erlang,重新安装

这种方法简单粗暴,如果我们不需要多版本的Erlang的话我建议可以把他们都移除掉,毕竟一个Erlang包会占用掉系统200多MB的磁盘空间。

通过brew命令的uninstall指令,加上--force标识就能够移除所有版本的Erlang

brew uninstall --force erlang

如果单独移除Erlang而不移除Elixir的话就会有以下的异常信息

Error: Refusing to uninstall /usr/local/Cellar/erlang/20.1.3
because it is required by elixir 1.5.2, which is currently installed.
You can override this and force removal with:
  brew uninstall --ignore-dependencies erlang

前面也说了Elixir依赖于Erlang,当我们只想单独卸载被依赖的Erlang的时候就会有这个警告,我不建议用它的命令保留Elixir而单独移除Erlang,毕竟该清理的还是要清理干净,重新安装也耗费不了多少时间。我们只需要依次运行以下命令

brew uninstall elixir
brew uninstall --force erlang
brew install elixir

把Elixir以及所有版本的Erlang删除之后,再重新安装Elixir,我们就可以体验最新版本的Elixir,并开始美妙的Elixir之旅。

> iex
Erlang/OTP 20 [erts-9.1.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.5.2) - press Ctrl+C to exit (type h() ENTER for help)

3. 第一条Elixir程序

又到了大家喜闻乐见的活动了,第一条程序总是会让人热血沸腾。不过咱门今天就别上HelloWorld了吧,来简单写一个脚本。

《Elixir初体验》

First

(1) 编译模式

写习惯了解释性语言,已经很久没试过编译代码了。想想以前写C的时候运行之前都是需要先编译,然后执行生成的二进制文件。现在我们来看看Elixir是如何操作的。

Elixir有个约定,需要编译的文件以.ex后缀名结尾,而直接运行的脚本则.exs后缀名结尾,我们也依照这个约定吧(即便我们都知道在Unix操作系统里面后缀名是什么根本就无所谓)。这里我们创建一个文件math.ex

# math.ex

defmodule Math do
  def sum(a, b) do
    a + b
  end
end

这种编码风格跟Ruby很像,实际上我们定义了一个包含sum方法的模块Math,然后运行编译命令

> elixirc math.ex

编译成功后会生成名为Elixir.Math.beam的文件

> ls
Elixir.Math.beam math.ex

这个就是编译后的字节码文件,熟悉Java的人应该知道Java里面也有类似的存在。然后我们在当前目录下面运行iex进行REPL环境,就能直接使用这个Math模块了

> iex
Erlang/OTP 20 [erts-9.1.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.5.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Math.sum(1,2)
3

接下来再简单看看脚本模式。

(2) 脚本模式

每次都要编译那多麻烦啊,很好,这也是我不喜欢部分编译型语言的原因之一(只是部分)—它们甚至连REPL环境都没有。Elixir考虑到了这一点,按照习俗我们在新目录下创建一个math.exs的脚本文件

# math.exs

defmodule Math do
  def sum(a, b) do
    a + b
  end
end

PS: 内容跟编译模式中的文件一样,只是后缀名不同。

> ls
math.exs

然后我们可以直接用相关的命令运行对应脚本

> elixir math.exs

由于我们math.exs里面只是定义了一个简单的模块和方法,并没有更多的逻辑,所以这次执行也看不出什么效果。不过在Elixir机制中我们可以像下面这样操作,直接地把目标文件加载到REPL环境中

> iex math.exs
Erlang/OTP 20 [erts-9.1.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.5.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Math.sum(1,2)
3

可以看到运行结果跟之前编译模式一样的只是少了手动编译那一步,这有利于我们对源码文件进行简单地测试,而不需要每次测试的时候都手动编译一次。

官网的建议是平时开发业务逻辑代码都以.ex后缀名结尾,最后需要被编译成字节码(这可能是性能方面考虑吧)。而编写日常单元测试或者配置信息则以.exs后缀名来结尾。

4. 总结

对这门语言的学习,大概只是看了4-5天的入门指南。它是否真如文档所说能够用来构建可扩展性高的系统,以及它是否真的有传闻说的那样地高性能呢?这一点还有待考证。

《Elixir初体验》

剁手

不过经过这两天的学习,我觉得这是一门挺值得入手的语言,特别是对于Ruby系的朋友来说。如果说Haskell是有Python风格的纯函数式编程语言(他们大写的None让我印象深刻),那我觉得Elixir就是有Ruby风格的函数式编程语言了。不过即便它语法风格设计方面跟Ruby很像,但Elixir就是Elixir,相信你会从中感受到与Ruby不一样的编程体验。

Happy Coding and Writing!!

点赞