异端魔法 --- Elixir 尝鲜

字数 1914阅读 2059

Elixir,一个巴西波兰帅哥José Valim写出来的,José 是谁?rails 的核心开发者。接触web以来发现,Ruby社区一直是一个很有才华的社区。创造性的东西层出不穷。新的理念和方式,巧夺天工。以致于对于其他社区,这些奇淫巧计更像是魔法,异端黑魔法。

程式语法发展了这么多年,OOP的方式貌似成为了工业标准。大规模网络应用的出现,高并发的应用场景越来越多,古老的函数式语言,仿佛尘封已久的史前怪兽,解除了封印重现人间。当然这么说有点夸张,不过现在函数式语言被人们的关注度越来越高。

大学期间阅读了《黑客与画家》,作者动情的描述了Lisp的神奇。怀着好奇的心态一边看《魔法书》一边学了Scheme。领略了函数式思想的皮毛,就暂且搁置了。前段时间无聊,又折腾了一下Haskell,很多数学问题用Haskell很顺手,当然好多东西都一知半解。

于是考察了下热门的所谓下一代编程语言 Clojure,Erlang,Rust,Go。Clojure是lisp方言,生态圈却是java社区,java很好很强大,但我不喜欢。Go很火,项目很多,好像没有带给我新的思想。Rust升级太猛烈,我这样的小民还是先观望。Erlang 的语法对于我实在太诡异。寻寻觅觅。每当你等得绝望的时候,公共汽车才会缓缓开来。没错,我的救命公车就是 Elixir。

Elixir,一个基于Erlang虚拟机的玩意,却不是玩具,而是一个强大又有趣的编程语言。用老爷子的话就是 Power of Erlang,Fun of Ruby。没错,具有Erlang强大性能,抽象能力,已经甜甜的语法糖,像Ruby一样有趣。

刚接触Elixir不久,已然被吸引。并且帮助我解决了以前的疑惑,给人眼前一亮的感觉。Python也是我喜欢的,下面就用 Python 和 Elixir 来简单的对比下递归思想。

据说计算机的两大思想,一是指针,其二就是递归。这些概念虽然常常听到,用到,甚至用了都不知道。多数时候,又很难说出所以然来,大概是修炼未到家,参悟不够。

Exlir 的文档上有个例子,

重复输出一个值

这个问题太简单了,任何一个开发者都能想到循环,用Python大概如下:

def repeat(msg, times):
    for _ in range(times):
        print msg

repeat("hello python", 5)  # 打印五行 hello python

当然,你也可以这样

def repeat(msg, times):
    while times:
        print msg
        times -= 1

repeat("hello python", 5)  # 打印五行 hello python

这两段代码都使用了常见的循环控制。核心就是通过times直接或间接的改变某个循环变量,来做计算。

那么,Elixir又是如何做的呢?

defmodule Repeat do
    def print(msg, times) when times <= 0 do
        IO.puts msg
    end

    def print(msg, times) do
        IO.puts msg
        print(msg, times - 1) 
    end
end

Repeat.print('hello elixir', 5)

对于 Exlir 的实现,我们并没有用循环,而是在函数内部调用了函数,即递归。简单设想一下,我们的函数执行的功能就是打印输出,给了一个5,就说明是要打印5次,也就是函数调用五次了。通过这样的方式思考递归,会更加自然。对此,格雷厄姆在《ANSI Common Lisp》对递归有很好的解释

起初,许多人觉得递归函数很难理解。大部分的理解难处,来自于对函数使用了错误的比喻。人们倾向于把函数理解为某种机器。原物料像实参一样抵达;某些工作委派给其它函数;最后组装起来的成品,被作为返回值运送出去。如果我们用这种比喻来理解函数,那递归就自相矛盾了。机器怎可以把工作委派给自己?它已经在忙碌中了。
较好的比喻是,把函数想成一个处理的过程。在过程里,递归是在自然不过的事情了。日常生活中我们经常看到递归的过程。举例来说,假设一个历史学家,对欧洲历史上的人口变化感兴趣。研究文献的过程很可能是:

  • 取得一个文献的复本
  • 寻找关于人口变化的资讯
  • 如果这份文献提到其它可能有用的文献,研究它们。

过程是很容易理解的,而且它是递归的,因为第三个步骤可能带出一个或多个同样的过程。

上面这个小例子,当然仅仅是递归,用Python也一样可以实现。再来看第二个问题。求一个列表的数据之和

我们先用python来实现,大概如下:

l = [1, 2, 3, 4, 5]

def sum(l):
    s = 0
    for i in l:
        s += i
    return s

print sum(l)

当然,上面的还是一个循环的问题,我们也用python的递归来实现一遍:

def sum(l):
    if len(l) <= 1:
        return l[0]
    else:
        return l[0] + sum(l[1:])

接下来,再用 Elixir写一个:

defmodule Math do
  def sum(l) when (length l) <= 1 do 
      hd l
  end

  def sum(l) do 
      sum(tl l) + hd l
  end
end

IO.puts Math.sum(l)

核心思想都是,取一个数和下一个数相加的和再和下一个数相加。函数的功能就是把 sum 和下一个数相加。当然,Elixir有更优雅的语法糖,下面两种都是使用了 Elixir的模式匹配特性

defmodule Math do
  def sum([head | tail], s) do
      sum(tail, head + s)
  end

  def sum([], s) do
      s
  end
end


defmodule Math do
    def sum([head | tail]) when tail == [] do 
        head
    end

    def sum([head | tail]) do 
        sum(tail) + head
    end
end

IO.puts Math.sum(l)

上面的小例子中,使用列表做参数,每次削减一点列表的递归方式,称为“递减”算法,是函数式编程的核心。当然,递归还可以处理其每个元素的方式,称为“映射(map)”算法。再看第一个问题,给一个列表,并把列表的数值加倍。

首先,还是用 python来实现,python丰富的语法,实现这个多种多样

l = [1, 2, 3, 4]

# 列表解析的方式
def double(l):
  return [x * 2 for x in l]

double(l) # [2, 4, 6, 8]

# map算法
def double(l):
  return map(lambda x: x * 2, l)

在python中,我们可以直接调用 map 内置函数来作计算,可是这个 map又是如何实现的呢?上面已经说了,处理每个元素的递归,是为 映射。因为我们用python的map递归来处理。

def double(l):
  if not l:
      return []
  return [l.pop(0) * 2] + double(l)

# 当然,也可以写成一句话
def double(l):
  return [l.pop(0) * 2] + double(l) if l else []

现在再来看看Elixir的实现:

l = [1, 2, 3, 4]

defmodule Math do
    def double(l) when l == [] do
        []
    end 

    def double(l) do
        [(hd l) * 2] ++ double(tl l)
    end
end

defmodule Math do
    def double([]) do
        []
    end

    def double([head | tail]) do
        [head * 2 | double(tail)]
    end
end

Elixir的第一个实现中,算法的描述,就是取出每个列表元素来处理。而第二个算法则用了 | 这个运算符语法糖。

尝试了上面几个例子,Elixir用递归的方式来处理通常的循环计算。虽然python也可以写出类似的代码,可是python的解释器对深层次的递归并没有优化,性能不是特别好,迭代比递归更合适。当然即使是python实现的递归算法,代码都没有Elixir的美观、优雅。Elixir的递归让整个处理过程看起来是那么的透明清晰。抽象能力相当强,上面的例子仅仅是Elixir的冰山一角,更多的特性有待

Elixir是一个迷人的东西,就像一个谜。

(文中的code仅仅为了描述算法概念,未做优化。)

推荐阅读:
Recursion
颠覆者的游戏:程序语言
《ANSI Common Lisp》 ---递归 (Recursion)

推荐阅读更多精彩内容