神奇的仙丹,性感的Elixir


clipboard.pnggit

在IT世界里,没有银弹,但却有神奇的仙丹(Elixir)。我不知道是什么灵感刺激这门语言的创造者José Valim想到了这么酷的命名,但这枚仙丹确实经由多种神奇的灵药炼制而成,这些灵药包括Erlang、Ruby、Clojure、Haskell。程序员

品尝这枚仙丹确实使人飘飘欲仙,至少,我在浅尝Elixir时,这种奇妙的感受一直萦绕在我心间,怦然心动于是不舍离去。或许如Erlang之父Joe Armstrong所说,是“一种先行于逻辑的心里感性的感受”;又或者如Dave Thomas形容的,那是让人“坠入爱河”的感受。github

大爱Elixir。算法

我之因此爱上Elixir,大约仍是由于Ruby的缘故。我并不是Ruby的狂热追随者,甚至没有从事太多Ruby相关的项目,但我至今在编写脚本时,Ruby依旧是个人首选。在动态语言中,我甚喜好Ruby相对简洁的语法。当我看到Elixir时,那种似曾相识的感受让我心动。数据库

虽说Elixir的炼制来自各位前辈留下的丹方灵药,然而从成丹之日起,Elixir就是Elixir,她已经具备了完整的语言性格。就我看来,Elixir真正称得上是“性感”。固然,这一大半要归功于Erlang美丽的英伦风情(Erlang之父Joe Armstrong是英国人),就Erlang的高颜值打底,只需再加上几点妩媚,几分妖娆,风采就变得性感撩人了。编程

并发与分布式

Elixir对并发与分布式的支持,就是正宗的英伦风情,这是从Erlang延续下来的最强悍基因。Elixir创建在Erlang虚拟机(BEAM)之上,使用Erlang的进程,如原生进程那样在全部的处理器中运行,然而开销却很是小。与Erlang同样,Elixir能够经过spawn轻松地建立进程:服务器

spawn fn -> 1 + 2 end

Elixir或者说Erlang的进程依靠消息传递完成通讯。进程接收到的消息其实是获取的一份消息副本,这就使得接收方可以与发送方解耦,接收方对消息的任何操做不会影响接收方。数据结构

send self, {:hello, "world"}

receive do
  {:hello, msg} -> msg
  {:world, msg} -> "won't match"
end

Elixir的核心继承自Erlang,天然就继承了对OTP(Open Telecom Platform)的支持。OTP是一个很大的课题,包括进程连接、监控以及分布式支持(我正在学习《Erlang/OTP并发编程实战》,但愿从Erlang根源上理解OTP)。Elixir对OTP的支持包括Agent、Task、GenServer以及Supervisor与Application。其中,Agent与Task是Elixir对OTP特性的抽象,而GenServer则更加通用。并发

在Elixir建立OTP服务器很是简单,只须要use GenServer便可。它主要的方法为handle_call(request, from, state)与handle_cast(request, state)。若是客户端发送的请求须要响应时,则消息形式为call,若是为单向调用,则形式为cast。框架

考虑进程的健壮性问题,在编写OTP应用时,可能还须要对进程进行监督。基于Actor模型,父进程将负责监督由其建立的全部子进程,下面的代码是Elixir官方提供的Supervisor代码:

defmodule KV.Supervisor do
  use Supervisor

  def start_link do
    Supervisor.start_link(__MODULE__, :ok)
  end

  def init(:ok) do
    children = [
        worker(KV.Registry, [KV.Registry]),
        supervisor(KV.Bucket.Supervisor, [])
    ]

    supervise(children, strategy: :rest_for_one)
  end
end

KV.Supervisor为监督进程,其子进程分别为KV.Registry与KV.Bucket.Supervisor,监督策略为rest_for_one。

至于分布式支持,在Elixir实际上是水到渠成的事情,由于它的核心是进程间通讯,而进程所在的节点位置,对于用户而言是透明的。

模式匹配

模式匹配是Elixir最妖娆的部分,虽然不少函数式语言都有模式匹配,但Elixir却把模式匹配融入到其血肉之中(实际上是延续了Erlang的模式匹配特点)。即便是一个赋值语句,也是模式匹配的一部分。在Elixir中,=符号其实被称之为匹配运算符(match operator)。因此你能够写出违反程序员常规的1 = x:

iex> 1 = x
1
iex> 2 = x
** (MatchError) no match of right hand side value: 1

模式匹配在Elixir中被普遍地运用到解构(destructuring )复杂的数据结构,例如Tuple、List等。固然case进行的模式匹配更是它最多见的使用场景。

函数与模式匹配的结合才是体现妖娆性的关键点,若是再结合guard clause,那就真正让人销魂了。

大多数语言的函数定义是支持函数重载的,这取决于参数的类型、个数与顺序。在动态语言中没有类型,则与个数与顺序相关。这些参数在定义时皆为形参(部分语言支持默认参数值,Elixir也支持,甚至能够将表达式做为默认参数),在调用时才传入实参。

可是,Elixir则否则,由于Elixir没有赋值的概念,所以在传递参数时,并不是赋值的语义,而是匹配的语义。于是出现以下的函数定义,你不要感到诧异哦:

defmodule Factorial do
    def of(0), do: 1
    def of(n), do: n * of(n-1)
end

在Elixir语义中,这两个定义实则是同一个函数,当调用of函数时,传入的参数会与第一个定义进行匹配,若是匹配不成功,则匹配第二个定义。利用这种模式匹配,既能够规避实现上的if分支,又能够更好地体现递归的语义。

Meyer很是强调软件开发中对“契约”的遵循,在他设计的语言Eiffel中,前置条件与后置条件做为了语法糖中的一等公民被支持。Erlang的guard Cluase与Eiffel的前置条件很是类似,Elixir也保留了这一语法特性。例如在前面的阶乘算法中,咱们能够经过guard clause避免传入错误的负数:

defmodule Factorial do
    def of(0), do: 1
    def of(n) when n > 0, do: n * of(n-1)
end

管道运算符

让Elixir展示其妩媚一面的,是超级性感的管道运算符。她让整段代码瞬间变得可爱起来。有了她,咱们就不用再陷入可怕的函数嵌套地狱中了。Dave Long在博文Playing with Elixir Pipes中给出了一个很有对照意义的例子。代码功能是从conn取得Request的header,并判断它是否有效。若是有效就返回conn,不然终止,并返回Not Authorized。

若是没有管道运算符,就得承受嵌套函数调用的惊悚感:

signature = List.first(get_req_header(conn, "x-twilio-signature"))  
is_valid = Validator.validate(url_from_conn(conn), conn.params, signature)  
if is_valid do  
  conn
else  
  halt(send_resp(conn, 401, "Not authorized"))
end

这样的代码彻底违反人类直觉,由于你得从函数最里边阅读,而后再层层往外逃逸。是否有一种被牢牢捆绑了的感受呢?固然,在不少语言中咱们都无奈地接受了这一点,已经被虐得习觉得常了。尝试一下管道运算符,会怎么样?

signature = conn  
            |> get_req_header("x-twilio-signature")
            |> List.first
if conn  
   |> url_from_conn
   |> Validator.validate(conn.params, signature)
do  
  conn
else  
  conn |> send_resp(401, "Not authorized") |> halt
end

当你把管道运算符|>当作是goto的话,咱们就能直观地体会到conn在各个函数中流动的现象了。很是可爱,不是吗?

Elixir是纯正的函数式语言,本质上讲,Elixir中的一切皆为函数,因此if表达式其实也是函数。这就意味着validate后的布尔结果能够经过|>直接传递给if:

signature = conn  
            |> get_req_header("x-twilio-signature")
            |> List.first
conn  
|> url_from_conn
|> Validator.validate(conn.params, signature)
|> if(do: conn, else: conn |> send_resp(401, "Not authorized") |> halt)

这才是真正Elixir Style的编程范儿,够妩媚吧!

Joe Armstrong认为管道运算符来自Prolog语言的隐性基因DCG,相似Haskell中的monad。Prolog的儿子erlang没有体现这一点,孙子辈又隔代遗传上了。

工程支持

Elixir的创造者José Valim乃Rails的核心参与者,因此他把Rails社区(包括Ruby社区)中一套让人目眩的工程实践照般过来了。

脚手架

经过mix能够直接帮助咱们建立项目的脚手架(用过rails的童鞋感到亲切了吗?):

mix new myproject

执行这条命令,mix就会帮咱们建立项目的基本结构和相应文件:

包管理与依赖管理

经过Hex来管理包(记得GEM吗?)。在http://hex.pm中几乎能够找到全部你想要的elixir包;固然你还能够享受Erlang的福利,直接重用erlang包。

添加依赖也很是方便,只须要在项目的mix.exs文件中添加依赖便可。例如添加HTTPoison和JSX包的依赖:

defp deps do
    [
      {:httpoison, "~> 0.11.0"},
      {:jsx, "~> 2.8"}
    ]
  end

最棒的是,Elixir还支持直接对github repository的依赖。

环境配置

对开发环境、测试环境、生产环境的配置支持。在config目录下的config.exs文件中能够添加必要的配置项,还能够经过以下语句import不一样环境的配置:

import_config "#{Mix.env}.exs"
单元测试

还有不能忘记的单元测试,这但是敏捷社区的随身法宝啊;Elixir经过内嵌的ExUnit很好地支持了单元测试的编写:

defmodule MyprojectTest do
  use ExUnit.Case
  doctest Myproject

  test "sort ascending orders the correct way" do
    result = sort_into_ascending_order(fake_created_at_list(["c", "a", "b"]))
    issues = for issue <- result, do: issue["created_at"]
    assert issues == ~w{a b c}
  end
end

如此简单。要运行全部测试,只需运行mix test便可。

其余

Elixir还有不少酷炫的玩意儿,例如Protocol、Behavior,固然还有最棒的(固然也多是最使人费解的)宏(Macro)。Elixir对DSL的支持也很是友好,这来自它继承的部分Ruby血统。例如,让咱们看看ECTO(一个基于Elixir开发的支持数据库访问的框架)的一段客户代码:

defmodule Sample.App do
  import Ecto.Query
  alias Sample.Weather
  alias Sample.Repo

  def keyword_query do
    query = from w in Weather,
         where: w.prcp > 0 or is_nil(w.prcp),
         select: w
    Repo.all(query)
  end

  def pipe_query do
    Weather
    |> where(city: "Kraków")
    |> order_by(:temp_lo)
    |> limit(10)
    |> Repo.all
  end
end

由于没有大括号、括号以及分号的干扰,代码能够变得更接近领域逻辑,再加上性感的管道运算符,可读性直接爆表,帅呆了!

相关文章
相关标签/搜索