异端魔法 --- Elixir 尝鲜

作者: 人世间 | 来源:发表于2015-03-29 00:28 被阅读1435次

    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)

    相关文章

      网友评论

      本文标题:异端魔法 --- Elixir 尝鲜

      本文链接:https://www.haomeiwen.com/subject/jjvnxttx.html