异端魔法 --- Elixir 尝鲜

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)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容