V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
ChristopherWu
V2EX  ›  程序员

推荐一下 基于 erlang 虚拟机的 Elixir(函数式)语言

  •  1
     
  •   ChristopherWu · 2018-08-31 15:30:52 +08:00 · 3238 次点击
    这是一个创建于 2303 天前的主题,其中的信息可能已经有所发展或是发生改变。

    分享一下我的旧文: https://yonghaowu.github.io/2018/04/05/why_u_need_elixir/ 萌新一个,抛砖引玉,欢迎 v 友指点。

    为什么推荐学习 Elixir ?

    Elixir 是一门基于 erlang 开发的新语言,复用了 erlang 的虚拟机以及全部库(站在已经生存了 20 多年巨人的肩膀上),定义了全新的语法以及构造了现代语言必不可少生态环境—包管理器,测试工具,formatter 等。使用 Elixir,你可以方便的构建可用性高达 99.9999 以及天然分布式的程序(代码随手一写就是稳定的分布式),可以秒开成千上万 Elixir 里专属的进程(比起系统的进程更轻量级),处理高并发请求等等。

    Elixir 是怎么样的语言?

    Elixir 是函数式语言,与 java,C++等过程式语言不通,没有变量。或者说,变量全都 imutable(不可改变)。通过学习 Elixir, 你可以学习多一种编程范式。

    python 中你是这样子处理列表的:

    mylist = []
    mylist.append('Google')
    mylist.append('Facebook')
    print mylist #结果是['Google', 'Facebook']
    

    Elixir 中是这样子的:

    myList = []
    myList = List.insert_at(myList, 0, "Google")
    myList = List.insert_at(myList, 1, "Facebook")
    IO.inspect myList 
    

    elixir 中这不是正常的写法,不过我只是用来介绍异同点。注意到,在面向对象思维的语言中,处理列表,是 用对象的方法,mylist.append: 对象.动作来处理; 而函数式因为变量是不可变的,是要 List.append(mylist, xx), 对象模块.动作(哪个对象)来处理,同时会返回修改后的新对象。

    数据不可变,好处就是在高并发中,并不会因为状态多且不断变化,引致 debug 异常困难——本来人的大脑就不适应多线程。

    不可变,就意味着 for 与 while 循环用不了,因为不存在变量 不断 变化,达到某值就中止循环~因此,你只能用递归来实现 while。

    但是不怕,Elixir 提供了强大无比的抽象, each 函数,map 函数,reduce 函数,all?函数(判断列表所有值是否满足此条件),group 函数(类似数据库的 group ) 等等,只有你想不到。相比之下,golang 真的是乏善可陈。

    管道

    是的,类似 linux 的管道 |,把处理结果传递给下一个函数。

    1..100
    |> Enum.map(fn x-> x+1 end)
    |> Enum.filter(fn x-> rem(x, 2)==0 end)
    |> Enum.filter(fn x-> rem(x, 3)==0 end)
    |> Enum.filter(fn x-> rem(x, 5)==0 end)
    |> IO.inspect
    

    与以下的 代码相比,python 是否相形见绌?

    numbers = range(1, 100)
    numbers = map( (lambda x: x+1), numbers )
    numbers = filter( (lambda x: x%2 == 0), numbers )
    numbers = filter( (lambda x: x%3 == 0), numbers )
    numbers = filter( (lambda x: x%5 == 0), numbers )
    print(numbers)
    

    再来一个例子,来自 Dave Long 的博客 Playing with Elixir Pipes

    代码的作用是:取出请求的头部x-twilio-signature 签名,并且校验是否有效。

    没有管道时,代码是这样子的:

    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  
    

    逻辑就非常清晰了。还可以这样子写:

    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)
    

    进程 Actor Model

    轻量级的进程

    在 Elixir 里,Elixir 进程(以下简称进程,与系统进程区分开)是轻量级的进程,与操作系统的概念相差不多,只不过 Elixir 进程运行在虚拟机中。那为什么 Elixir 进程更快呢?

    • Erlang 进程的堆栈是动态分配、随使用增长的,新建一个 Erlang 进程的开销远比系统进程 / 线程小得多,开销就像在 OO 语言中建立一个新对象般简单。
    • 普通进程 / 线程的内存管理是基于页的,而页对于一个函数 + 一点点零碎来说都太大了。而实际中 OS 分配给普通进程的初始栈可以达到 Megabytes 级别。
    • Erlang 进程之间是隔离的,没有共享状态,所有的消息都是异步的,不会继承大量的已有状态。
    • Erlang 进程的调度是在 Erlang VM 内发生的,跟 OS 层没啥关系,无需普通进程 / 线程切换时的各种开销
    • Erlang 进程的切换是一种类似直接 “跳转” 的方式,以 O(1) 复杂度实现。Erlang 调度器会管理这些切换,大概只需要几十个指令和数十纳秒的时间。普通线程的切换会需要数百上前纳秒,OS 调度器的运作复杂度可能是 O(logn) 或者 O(log(logn))。如果有上万个线程,这个时间将会大幅提升。来自知乎

    像指挥交响乐队一样,指挥你的 Elixir 进程

    对于 Elixir 进程,你可以方便的用一个进程( supervisor )去管理子进程,supervisor 会根据你设定的策略,来处理意外挂掉的子进程(这种情况不多的是,错误处理稍微做不好就会挂) , 策略有:

    • one_for_one:只重启挂掉的子进程
    • one_for_all:有一个子进程挂了,重启所有子进程
    • rest_for_one:在该挂掉的子进程 创建时间之后创建的子进程都会重启。

    老夫敲代码就是一把梭!可不,只要重启就行。

    实质上,这是有论文支持的: 在复杂的产品系统中,几乎所有的故障和错误都是暂态的,对某个操作进行重试是一种不错地解决问题方法——Jim Gray 的论文中指出,使用这种方法处理暂态故障,系统的平均故障间隔时间(MTBF)提升了 4 倍。

    因此,你就可以创建一课监控树,根节点就是啥事都不做,只负责监控的进程。其他都是它的子进程,如果不是 coredump (几乎不发生),那么根节点就不可能会挂;因此其他子进程就会正确的被处理。

    当然,这有前提:5 秒内重启超于 3 次,就会不再重启,让进程挂掉。为什么呢?因为重启是为了让进程回到当初启动时的稳定态,既然稳定态都不稳定了,重复做重启是没有意义的,这时迫切需要人来处理。

    方便的通信

    一切皆消息。

    进程间通信,就像微风一样自然。 你所监管的进程而来的信息,调用的库的消息,全部都可以自己来 handle 并作相应处理。甚至还有抽象好的 GenServer 来让你专门处理消息与状态逻辑。

    定时器?不需要的,我们甚至可以自己发送消息来实现更好的定时器:

    Process.send_after 会在 xx 秒后发消息到指定的进程,通过这个功能,不断往自己发消息,从而实现定时器的功能。请看实现:

    defmodule Periodically do
      require Logger
      use GenServer
    
      def start_link do
        GenServer.start_link(__MODULE__, %{})
      end
    
      def init(state) do
        schedule_work(:do_some_work)
        {:ok, state}
      end
    
      def handle_info(:do_some_work, state) do
        doing_now()
        schedule_work(:do_some_work)
        {:noreply, state}
      end
    
      defp schedule_work(update_type) do
        Process.send_after(self(), update_type, 30*1000)
      end
    end
    

    相较于 setTimeOut 之类的,好处是什么?

    Elixir 自带工具,可以查看所有进程的状态并管理,上面把Periodically作为一个进程启动起来了,自然可以管理他:P

    模式匹配与宏

    这个相较于平常我们的赋值语言比较新颖,介绍的篇幅过长。

    请看 http://szpzs.oschina.io/2017/01/30/elixir-getting-started-pattern-matching/ 以及 https://elixir-lang.org/getting-started/pattern-matching.html

    通过模式匹配,我们可以避免 if else 的嵌套地狱;可以利用语言自己的匹配来做 搜索,

    宏可以让你实现自定义的 DSL (当然太强大的功能自然导致滥用出 bug ),可以屏蔽掉很多不优雅的细节。

    以上就是 Elixir 的简单介绍,建议诸位学习一下 Elixir,洗刷一下自己的 OO 以及过程式编程思维。

    第 1 条附言  ·  2018-08-31 18:45:33 +08:00
    19 条回复    2018-09-01 09:11:43 +08:00
    tt67wq
        1
    tt67wq  
       2018-08-31 15:46:56 +08:00
    supervisor 真的是不错,最近撸了个网站 demo,稳得一匹
    glues
        2
    glues  
       2018-08-31 15:50:00 +08:00
    LZ 是阿里的?阿里也用 Elixir 了吗?
    ChristopherWu
        3
    ChristopherWu  
    OP
       2018-08-31 15:50:52 +08:00
    @glues 阿里游戏的一个小组用...
    BigNerd
        4
    BigNerd  
       2018-08-31 15:59:44 +08:00
    Elixir 非常优秀,可惜暂时不是主流
    583376938
        5
    583376938  
       2018-08-31 16:11:53 +08:00 via iPhone
    M 住,稍后看
    SuperMild
        6
    SuperMild  
       2018-08-31 16:18:04 +08:00
    函数式很难火起来,如果不是数学抽象思维很好的人,用函数式干活太烧脑了。
    583376938
        7
    583376938  
       2018-08-31 16:19:47 +08:00 via iPhone
    @tt67wq 可以分享一下吗?
    ChristopherWu
        8
    ChristopherWu  
    OP
       2018-08-31 16:20:24 +08:00
    @SuperMild 感觉这个论断站不住脚,也许是我们熟悉了过程式语言,才对函数式语言不习惯而已。
    用 for 双重循环,0 下标,其实刚开始学也不好懂。
    tt67wq
        9
    tt67wq  
       2018-08-31 18:33:53 +08:00 via Android
    @583376938 这个没放出去,就是用 phx 实现了一些常见的 web 后端东西,没有具体业务,就是自己撸的一个玩具
    tt67wq
        10
    tt67wq  
       2018-08-31 18:35:45 +08:00 via Android
    @SuperMild 习惯了还好,我现在写其他语言没模式匹配反而不习惯
    tt67wq
        11
    tt67wq  
       2018-08-31 18:36:45 +08:00 via Android
    @583376938 我在 gayhub 上维护了一个用 elixir 解释
    tt67wq
        12
    tt67wq  
       2018-08-31 18:38:05 +08:00 via Android
    @583376938 我在 gayhub 上维护了一个用 elixir 解欧拉计划的仓库,有兴趣可以品鉴一二,妈的 v2 怎么发错了也没修改的
    ChristopherWu
        13
    ChristopherWu  
    OP
       2018-08-31 18:47:07 +08:00
    @tt67wq 用函数式语言,解算法题,其实不好做。。
    tt67wq
        14
    tt67wq  
       2018-08-31 18:50:36 +08:00
    @ChristopherWu 确实不好做,主要没列表,全是链表,纯粹是为了练习 iex 找虐
    ChristopherWu
        15
    ChristopherWu  
    OP
       2018-08-31 18:54:37 +08:00
    @tt67wq 感觉不如写个真实的应用学得快。毕竟核心是 进程通信,而不是 函数式。
    tt67wq
        16
    tt67wq  
       2018-08-31 18:58:08 +08:00
    @ChristopherWu 对的,我不过是无聊,一直想在公司劝他们弄个模块用 elixir 写,没人鸟我。。
    sagaxu
        17
    sagaxu  
       2018-08-31 20:52:06 +08:00 via Android
    坊间传说 beam 性能比 hotspot 和 v8 差太多,不知道是不是谣言
    pythonee
        18
    pythonee  
       2018-09-01 00:21:26 +08:00
    语言太多学不过来怎么办
    PythonAnswer
        19
    PythonAnswer  
       2018-09-01 09:11:43 +08:00
    我喜欢过程+函数

    不喜欢写 class 麻烦
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2793 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 02:55 · PVG 10:55 · LAX 18:55 · JFK 21:55
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.