【AICon】探索RAG 技术在实际应用中遇到的挑战及应对策略!AICon精华内容已上线73%>>> 了解详情
写点什么

一文解决现代编程语言选择困难:响应式编程

  • 2021-03-29
  • 本文字数:11600 字

    阅读完需:约 38 分钟

一文解决现代编程语言选择困难:响应式编程

如果搜索“最佳编程语言”,结果会罗列一堆文章。这些文章涵盖各主流语言,并且大多对各语言优缺点的表述模棱两可,表述不到位,缺少实战借鉴意义。本文概述了当前再用的现代编程语言,按推荐程度从低到高依次列出。希望本文有助于读者选择合适的工具完成工作,降低开发工作量。鉴于原文篇幅过长,译文按设计用于命令式编程的 C 语言家族,以及设计用于响应式编程的 ML 语言家族,分为上下两篇提供。本文是下篇(上篇:《一文解决现代编程语言选择困难:命令式编程》)。

函数式编程

 

开篇先介绍函数式编程,然后继续对语言做排名。为什么要考虑函数式编程?因为函数式编程给开发者带来了和谐与安宁。

 

函数式编程可能听起来有些高大上,但实际上无需过于担心。简而言之,函数式语言吸取了其他一些语言的经验教训,实现了许多正确的设计决策。在很多情况下,函数式语言提供正确的功能,包括支持 ADT 的强大类型系统、无空值、无需异常的错误处理、对不可变数据结构的内建支持、模式匹配和函数复合(compose)运算符。

 

做为本文评判的加分项,函数式编程语言提供哪些共性的优势?

使用纯函数的编程

不同于主流的命令式语言,函数式编程语言鼓励使用纯函数(pure function)编程。

 

什么是纯函数?该理念非常简单,即给定相同的输入,始终返回相同的输出。例如,2+2 始终返回 4,因此加法运算符“+”是纯函数。

 

纯函数不允许与外界交互,不支持 API 调用,无法写入控制台,甚至不允许更改状态。这完全不同于面向对象编程所采取的方法,即其中任何方法都可以自由地变更(Mutating)其他对象的状态。

 

纯函数和非纯函数非常易于区分。函数是否不带参数,是否不返回值?如是,则为非纯函数。

 

下面例子给出的是非纯函数:

 

// 非纯函数,根据随后的调用,返回不同的值。// 要点:不带任何参数。Math.random(); // => 0.5456412841544522Math.random(); // => 0.7542151348966241Math.random(); // => 0.4534865342354886

let result;// 非纯函数,变更外部状态,即result变量。// 要点:不返回任何值。function append(array, item) { ​result = [ ...array, item ]}
复制代码

 

下面例子给出的是纯函数:

 

// 纯函数:不变更函数体外任何状态。function append(array, item) {  ​return [ ...array, item ]}

// 纯函数:同样的输入,总是返回相同的输出。function square(x) { return x * x; }
复制代码

 

此类方法看上去难以理解,需要一段时间才能习惯。我一开始也对此颇感困惑!

 

那么纯函数有什么好处?它非常容易测试,无需 Mock 和 Stub;易于推断,不同于面向对象编程,无需牢记整个应用的状态。开发人员只需关注当前正在操作的函数。

 

纯函数可以轻松复合(compose)。由于纯函数之间不存在共享状态,因此非常易于实现并发。纯函数的重构可谓是件快事,只需复制和粘贴,完全可不借助于任何复杂的 IDE 工具。

简而言之,纯函数让编程回归欢乐。

 

函数式编程鼓励使用纯函数。如果 90%以上的代码库是由纯函数组成时,这当然很好。但是一些语言也走了极端,完全禁止使用非纯函数,这并非总是好事。

不可变数据结构


本文下面列出的所有函数式语言,均内建对不可变数据结构的支持。不可变数据结构是持久的,更改后不必创建整个数据结构的深层拷贝。设想一下,如果反复地对一个超 10 万项元素的数组做镜像拷贝,那么性能一定好不了。

 

持久数据结构无需创建拷贝,仅在简单重用旧数据结构引用的同时,添加所需的更改即可。

代数数据类型(ADT)


ADT 是一种强大的应用状态建模方法,类似于合成类固醇的方法构建枚举类型(Enums on steroids)。只需指定构成某个类型的可能“子类型”,以及子类型的构造函数参数。例如:

 

type shape =    ​| Square(int   ​| Rectangle(int, int   ​| Circle(int)
复制代码

 

上例中,“shape”类型可以是 Square,、Rectangle 或 Circle。 Square 的构造函数使用单个 int 参数,即指定正方形的宽度;Rectangle 使用两个 int 参数,即指定长方形的宽度和高度;而 Circle 使用单个 int 参数,即指定圆的半径。

 

下面给出类似功能的 Java 实现代码:

 

interface Shape {}

public class Square implements Shape { ​private int width

​public int getWidth() ​return width

​public void setWidth(int width) ​this.width = width}

public class Rectangle implements Shape { ​private int width ​private int height

​public int getWidth() ​return width

​public void setWidth(int width) ​this.width = width ​public int getHeight() ​return height

​public void setHeight(int height) ​this.height = height}



public class Circle implements Shape { ​private int radius

​public int getRadius() ​return radius

​public void setRadius(int radius) ​this.radius = ra
复制代码

 

相比之下,谁又会抗拒使用函数式语言的 ADT?

模式匹配


所有函数式语言都对模式匹配提供了强大的支持。模式匹配通常可编写出更具表现力的代码。

下面给出一个布尔选项类型的模式匹配例子:

 

type optionBool =   ​| Some(bool   ​| None

let optionBoolToBool = (opt: optionBool) => { ​switch (opt) ​| None => fals ​| Some(true) => tru ​| Some(false) => fals};
复制代码

下面给出未使用模式匹配的相同功能代码:

let optionBoolToBool = opt => {  ​if (opt == None)     ​fals  ​} else if (opt === Some(true))     ​tru  ​} else     ​fals}
复制代码

 

毫无疑问,使用模式匹配的代码更具表现力,更为简洁。

 

模式匹配还确保提供编译时的详尽信息,以免开发人员忘记检查各种可能情况。非函数式语言没有提供此类保证。

空值


在函数式编程语言中,通常避免使用空引用。类似于 Rust 那样,使用 Option 模式替代。例如:

 

let happyBirthday = (user: option(string)) => {  ​switch (user)   ​| Some(person) => "Happy birthday " ++ person.nam  ​| None => "Please login first  ​}};
复制代码

错误处


通常并不建议在函数式语言中使用异常。同样,类似于 Rust,使用的是 Result 模式。例如:

 

type result('value, 'error) =  ​| Ok('value  ​| Error('error)

let happyBirthday = (user: result(person, string)) => { ​switch (user) ​| Ok(person) => "Happy birthday " ++ person.nam ​| Error(error) => "An error occured: " ++ erro ​}};
复制代码

 

对于函数式错误处理,在“Composable Error Handling in OCaml”一文中给出了很好的说明。

Pipe forward 操作符


如果没有 pipe forward 运算符,函数调用难免会存在嵌套,这会降低了代码的可读性。例如:

 

let isValid = validateAge(getAge(parseData(person)));
复制代码

 

函数式语言专门提供了管道运算符,简化了编程。上面代码可重写为:

 

let isValid =  ​perso    ​|> parseDat    ​|> getAg    ​|> validateAge
复制代码

Haskell



Haskell 完全可称为所有函数式编程语言的“鼻祖”。Haskell 已 30 多岁了,甚至比 Java 还要老。函数式编程中的许多最佳创意,都源于 Haskell。

 

语言家族:ML

 

👍 👍 类型系统

Haskell 的类型系统比其他任何语言都要强大。当然,Haskell 支持 ADT,也支持类型类(Typeclass)。其类型检查器几乎可完成所有推断。

 

👎👎 学习难度

非常难!众所周知,要有效地使用 Haskell,首先必须精通范畴学,这并非开玩笑。面向对象编程中需要多年的经验,才能写出良好的代码。而学习 Haskell 则需要投入大量的精力,才能富有成效。

 

即便是使用 Haskell 编写一个简单的“Hello world”程序,也需要了解 Monads,尤其是 IO Monads。

 

👎👎 社区


根据我自身的经验,Haskell 社区更具学术性。例如在 Haskell 软件库邮件列表中,一个最新帖子的开头是这样说的:

 

“私人通讯指出,元组函数\x->(x,x)实际上是对 biapplicative 及其相关结构所做的一种特殊单调(monadicially)对角化。”

 

该帖子讨论热烈,得到 39 个答复。

 

—— Hacker News上用户 momentoftop 的发言。


上面引用的内容,很好地评价了 Haskell 社区。Haskell 社区更喜欢开展包括范畴论在内的学术讨论,而非去解决实际问题。

 

👎 函数纯度


上文介绍过,纯函数优点多多,但也存在一些副作用。例如,与函数体外的互动,包括变更状态(mutating state)。这些副作用是导致程序出现大量错误的重要原因。作为纯函数式语言,Haskell 完全禁止使用这些副作用。这意味着函数永远不能变更任何值,甚至不允许与函数体外进行任何交互。从技术上来讲,甚至不允许记录日志等的操作。

 

当然,Haskell 也提供了与外界交互的解决方法。其运作机制是通过提供一组称为 IO Monad 的指令。此类指令可提出:“读取键盘输入,然后在某些函数中使用该输入,然后将结果打印到控制台。” 之后,语言运行时会接受此类指令并执行。开发人员永远不会执行直接与外界交互的代码。

 

不惜一切代价避免成功!

 

—— Haskell 的非官方座右铭


事实上,对函数纯度的关注,显著地增加了抽象的数量,进而增加了复杂性,降低了开发人员的生产效率。

 

👍 空值

和 Rust 类似,Haskell 不支持空引用。Haskell 使用 Option 模式声明可能不存在的值。

 

👍 错误处理

尽管一些函数可能会抛出错误,Haskell 代码的惯用模式类似于 Rust 中 Result 类型。

 

👍 不可变性

Haskell 对不可变数据结构提供一等支持。

 

👍 模式匹配

Haskell 提供很好的模式匹配支持。

 

👎 生态系统

Haskell 的标准库完全不成体系,尤其是默认的核心软件库 Prelude。Haskell 默认使用抛出异常的函数,而不是采用函数编程标准做法的返回选项值。更糟的是,Haskell 具有两个包管理器,Cabal 和 Stack。

评判



硬核(hardcore)函数式编程需要深度理解过多的高度抽象概念,因此永远不会成为主流。

 

—— David Bryant Copeland,《软件设计的四项良好原则(Four Better Rules for Software Design)》

 

我的确非常欣赏 Haskell,但不幸的是,Haskell 可能永远局限于学术界。Haskell 是最糟糕的函数式编程语言吗?各位自行判定,但我认为是。

OCaml



OCaml 是一种函数式编程语言。OCaml 是“Object Caml”的缩写,但讽刺的是,很少有人在OCaml中使用对象

 

OCaml 的历史几乎和 Java 一样长,“O”很可能是来自于那个年代“对象”这一潮流。OCaml 只是填补了Caml的空白。

 

语言家族:ML。

 

👍 👍 类型系统

OCaml 的类型系统可与 Haskell 相媲美。最大的缺点是缺少类型类(Typeclass),但支持高阶模块函子(functor)。

 

OCaml 是静态类型的。具有近乎 Haskell 的完美类型推断。

 

👎👎 生态系统

OCaml 的社区并不大。这意味着开发人员无法针对一些通常用例找到高质量的软件库。例如,OCaml 缺少适用的 Web 框架。

 

相比其他语言,OCaml 软件库的文档质量堪忧。

 

👎 工具

OCaml 的工具颇为混乱,并存在三种软件包管理器:Ppam、Dune 和 Esy。

 

OCaml 编译器错误信息非常不友好。虽然这并非致命因素,但的确令人沮丧,会影响开发人员的生产效率。

 

👎 学习资源

想要上手学习 OCaml 的话,这里推荐《Real World OCaml》一书。但该书自 2013 年后就再未更新,其中得许多例子已经不合时宜。该书中的操作已不适用于现代工具链。

 

相比其他语言,OCaml 教程可以说质量堪忧。大多只是学校课程的讲稿。

 

👎 并发

“多核随处可见(Multicore is coming Any Day Now™️)”这一口号,是 OCaml 并发历程的很好总结。OCaml 开发者多年来一直在苦苦等待适用的多核支持,但看来近期也不会添加到该语言中。OCaml 应该是唯一缺少良好多核支持的函数式语言。

 

👍 空值

OCaml 不支持空引用,使用 Option 模式声明可能不存在的值。

 

👍 错误处理

OCaml 代码惯用 Result 类型模式。

 

👍 不可变性

OCaml 对不可变数据结构提供一等支持。

 

👍 模式匹配

OCaml 提供很好的模式匹配支持。

 

评判



OCaml 是一种很好的函数式语言,主要缺点是对并发支持不好,社区不大,因此生态系统过小,缺少学习资源。

 

考虑到上述不足,我并不推荐在生产环境中使用 OCaml。

Scala



Scala 是为数不多真正的多重编程范式语言(Multi-paradigm programming language)。Scala 同时很好地支持面向对象编程和函数式编程。

 

语言家族:C

 

👍 生态系统

Scala 运行在 JVM 之上,因此可以接入 Java 的巨大生态系统。这极大地提高了开发人员后台的生产效率。

 

👍 类型系统

Scala 可能是唯一类型系统不健全(unsound)的有类型函数式编程语言,同时也缺少适当的类型推断。Scala 的类型系统比不上其他函数式语言。

 

好的一面,Scala 支持高级类类型( Higher-Kinded Types)和类型类(Typeclass)。

 

尽管存在不足,Scala 的类型系统依然值得肯定。

 

👎 代码简洁性和可读性

相比 Java,Scala 代码非常简洁,但可读性一般。

 

Scala 是为数不多属于 C 语言家族的函数式编程语言。C 语言家族设计为命令式编程,而 ML 家族语言设计为函数式编程。因此,使用 Scala 的类 C 语法进行函数式编程,时常看起来有些奇怪。

Scala 中没有适当的 ADT 语法,对代码的可读性产生了不利的影响:

 

sealed abstract class Shape extends Product with Serializable

object Shape { ​final case class Square(size: Int) extends Shap ​final case class Rectangle(width: Int, height: Int) extends Shap ​final case class Circle(radius: Int) extends Shap}
复制代码

 

而使用 ReasonML 的 ADT 编写为:

 

type shape =    ​| Square(int   ​| Rectangle(int, int   ​| Circle(int)
复制代码

 

在可读性上,ML 家族语言的 ADT 明显胜出。

 

👎 👎 速度

Scala 可能是本文所列出语言中编译速度最慢的。一个简单的 Hello World 程序,如果不使用最新的硬件,编译可能需要 10 秒。Scala 编译不是并发的,使用单线程,编译速度不佳。

 

Scala 运行在 JVM 上,这意味着程序启动所需时间更长。

 

👎 学习难度

Scala 具有很多特性,导致学习难度高。该语言中充斥着各种特性。

 

Scala 可能是第二难学的函数式语言,仅好于 Haskell。事实上,很多企业放弃 Scala 的原因之一就是非常难学。

 

👍 不可变性

Scala 使用案例类(Case Class),对不可变数据结构提供一流支持。

 

👌 空值

不好的一面,Scala 支持空值。好的一面,Option 模式是潜在缺失值的惯用方式。

 

👍 错误处理

和其他函数式语言一样,Result 模式是 Scala 的惯用错误处理方式。

 

👌 并发

Scala 运行在 JVM 上,并非完全设计用于并发。好的一面,其Akka工具集非常成熟,JVM 提供类似 Erlang 的并发。

 

👍 模式匹配

Scala 提供很好的模式匹配支持。

评判



我想要去喜欢 Scala,但确实做不到。Scala 想要做太多的事情,为了同时支持面向对象编程和函数式编程,其设计者不得不做出权衡。正如俄罗斯谚语所说:“追逐两个兔子的人,最终一无所获”。

Elm



Elm 是一种编译为 JavaScript 的函数式语言,主要用于前端 Web 开发。

 

Elm 的独到之处,是其承诺不会出现运行时异常。Elm 编写的应用非常稳定。

 

语言家族:ML

 

👍 很好的错误信息

Elm 编译器提供的一些错误信息,是我看到的最到位信息。这使得该语言即便是对完全小白也非常友好。

 

👍 错误处理

Elm 是纯函数式语言,没有运行时错误,也不支持异常。这意味着如果一个代码库是 100%由 Elm 实现的,那么就永远不会出现运行时错误。Elm 可能出现运行时错误的唯一情况,是与外部 JavaScript 代码交互时。

 

Elm 是如何处理错误的?和其他函数式语言一样,使用 Result 数据类型。

 

👎 函数纯度

和 Haskell 一样,Elm 是纯函数式语言。

 

Elm 是否因为消除了所有运行时异常而提高了生产率,还是因为在所有地方强制函数式纯度而降低了生产率?从我个人经验看,对 Elm 代码的任何显著重构都涉及大量“疏通工作”,完全是灾难性的。

 

具体取决于开发人员,但我还是要给 Elm 一个差评。

 

👎 过于自行其是(opinionated)



Elm 是一种自行其是的语言。即便是制表符的使用,也会报语法错误。

 

Elm 对“从不报错”的追求,会导致该语言走上末路。其最新版 0.19 引入了突破性更改,导致 Elm 几乎无法与 JavaScript 软件库互操作。当然,这一更改的目的是让开发人员使用 Elm 编写自己的软件库,推动自身生态系统的发展。但是,几乎很少企业能有资源使用 Elm 重新实现所有一切。这会导致人们担心进一步发展而弃用 Elm。

 

看上去 Elm 的设计者过于注重函数纯度,在“永不报错”理念上钻了牛角尖。

 

👎 非 React

不同于 ReasomML 等语言,Elm 使用自己的 Virtual DOM,不使用 React。这意味着开发人员不能访问针对 React 制作的软件库和庞大组件生态。

 

👎 👎 语言发展

即便是 Elm 的最新版本 0.19.1,也已经发行已经一年多了。不透明的开发过程,导致他人难以对 Elm 的发展做出贡献。Elm 的每个主版本都引入了突破性更改,导致部分开发人员无法继续使用该语言。我们近一年多没有听到 Elm 创建者的任何消息,甚至不知道他是否依然全职维护 Elm。可能该语言已经死亡。

 

👍 模式匹配

Elm 提供很好的模式匹配支持。

 

👍 不可变性

对不可变数据结构提供一等支持。

 

👍 空值

Elm 不支持空引用。和其他函数式语言一样,Elm 使用 Option 模式。

 

评判



Elm 是一种优秀的语言。不幸的是,Elm 看上去并没有什么未来。但可作为入门函数式编程的很好途径。

 

扩展阅读:

F#



F#可视为 OCaml for .NET,其语法非常类似于 OCaml,只有几处细微修改。F#在 2005 年首次推出,目前是一种非常成熟的语言,具有很好的工具和丰富的生态系统。

 

语言家族:ML。

 

👍 👍 类型系统

F#类型系统的唯一缺点是缺少高阶类型(Higher-Kinded Types)。尽管如此,其类型系统依然是非常可靠的,编译器几乎支持所有推断。F#对 ADT 有很好的支持。

 

👍 函数式,但并非纯函数

不同于 Haskell 和 Elm,F#非常务实(pragmatic),并不强制纯函数式。

 

👍 学习资源

F#具有可媲美 Elixir 的很好学习资源

 

👍 学习难度

F#非常易于学习,是可供上手的函数式语言。

 

👌 生态系统

F#社区规模相当小,根本没有可与 Elixir 等语言媲美的软件库。

 

👍 与 C#的互操作(interop)

好的一面是,F#可以接入整个.Net 和 C#生态系统。可与现有 C#代码互操作。这是很大的优点。

 

👌 并发

F#运行在 CLR 之上,无法与 Elixir 通过 Erlang VM 提供的并发支持相媲美。

 

👍 空值

F#代码中通常不使用空值。F#使用 Option 模式定义声明不存在的值。

 

👍 错误处理

F#代码惯用 Result 类型实现错误处理。

 

👍 不可变性

F#对不可变数据类型提供一等支持。

 

👍 模式匹配

F#提供很好的模式匹配支持。

评判



F#具有非常好的类型系统,是一种非常可靠的编程语言。F#几乎和本文稍后介绍的 Elixir 一样,可用于 Web API 开发。但是,F#的问题在于它所不具备的特性。相比 Elixir,Elixir 的并发功能、丰富的生态系统和令人惊叹的社区,要胜过 F#静态类型所提供的好处。

 

扩展阅读:

奖项



F#获得两项荣誉。

 

  • “金融科技最佳语言”奖。众所周知,金融领域是 F#的主要应用。

  • “最佳企业软件语言”奖。F#的丰富类型系统支持对复杂商业建模。推荐阅读《Domain Modeling Made Functional》一书。

ReasonML



ReasonML 是一种编译为 JavaScript 的函数式语言,主要用于前端 Web 开发。

 

ReasonML 并非一种新的语言,而是老旧语言 OCaml 的新语法。ReasonML 由 Facebook 支持。

借助于 JavaScript 的生态系统,ReasonML 避免了 OCaml 的缺点。

 

语言家族:ML

 

👍 非 JavaScript 的超集

ReasonML 的语法类似于 JavaScript,因此有 JavaScript 开发经验的人可轻松上手。但是不同于 TypeScript,ReasonML 甚至并未考虑做为 JavaScript 的超集。我认为这是件好事情,ReasonML 没有继承数十年来 JavaScript 的不好设计理念。

 

👍 学习难度

鉴于 ReasonML 并未考虑做为 JavaScript 的超集,因此语言上要比 JavaScript 简单很多。具有 JavaScript 函数式编程经验的人,可在一周内轻松上手。

 

ReasonML 的确可称为本文列出所有语言中最简单的。

 

👍 函数式,但并非纯函数

不同于 Elm,ReasonML 并非考虑做为纯函数式语言,也没有“永远不出现运行时错误”这一目标。这意味着 ReasonML 非常务实,聚焦于开发效率和尽快产出。

 

👍 👍 类型系统

ReasonML 本质上是 OCaml,其类型系统可与 Haskell 媲美。最大缺点是缺少类型类(Typeclass),但是支持高阶模块函子(functor)。

 

ReasonML 是静态类型的,其类型推断和 Haskell 一样优秀。

 

👍 👍 生态系统

和 TypeScript 一样,ReasonML 可使用整个 JavaScript 生态系统。

 

👍 与 JavaScript/TypeScript 的互操作

ReasonML 编译为 JavaScript,因此可在同一项目中同时使用 JavaScript、TypeScript 和 ReasonML。

 

👍 ReasonML 和 React:天作之合

前端 Web 开发人员常使用 React,但是很少有人知道 React 最初是用 OCaml 编写的,近期为了扩大使用才移植到 JavaScript。

 

由于 ReasonML 是静态类型的,因此无需操心 PropType。

 

回顾在 JavaScript 一节中给出的例子,看似无害却会导致性能灾难。

 

<HugeList options=[] />
复制代码

 

ReasonML 对不可变数据类型提供很好的支持,代码不会产生性能问题。

 

<Person person={    ​id: "0"    ​firstName: "John"  ​friends=[samantha, liz, bobby  ​onClick={id => Js.log("clicked " ++ id)/> 
复制代码

 

不同于 JavaScript,使用 ReasonML 不会产生不必要的重渲染。获得开箱即可用的良好 React 性能。

 

👎 工具

ReasonML 的成熟度无法与 TypeScript 等语言相比,并在工具上存在一些问题。例如,其官方推荐的 VSCode 扩展reason-language-server当前无法正常使用,虽然还有其他扩展可用。

 

ReasonML 本质上使用 OCaml 编译器,OCaml 的编译器错误消息非常难理解。尽管这并非致命,但的确令人沮丧,会影响开发人员的效率。

 

我期待随着该语言的日渐成熟,在工具上会有所改进。

 

👍 空值

ReasonML 没有空引用,使用 Option 模式声明可能不存在的值。

 

👍 不可变性

ReasonML 对不可变数据结构提供一流支持。

 

👍 模式匹配

ReasonML 提供很好的模式匹配。

评判



可以说 ReasonML 实现了 TypeScript 期望做到但尚未实现的特性。ReasonML 为 JavaScript 添加了静态类型,移除了几乎所有不好的特性,添加了一些的确有用的现代特性。

 

最佳前端语言奖



ReasonML 获“最佳前端语言奖”。毫无疑问,ReasonML 是前端 Web 开发的最佳选择。

Elixir



Elixir 可能是当前最广为使用的函数式编程语言。和 ReasonML 一样,Elixir 并非一种新语言,而是基于 Erlang 近三十年的成功之上。

 

Elixir 可称为 Go 的函数式近亲。和 Go 一样,Elixir 从一开始就是针对并发设计的,以利用多核处理器的优点。

 

不同于其他函数式语言,Elixir 是非常务实的,聚焦于产出。在 Elixir 社区中,不会出现长篇大论的学术讨论。Elixir论坛中满是对现实问题的解决方案干货,社区对新手也十分友好。

 

语言家族:ML。

 

👍 👍 生态系统

Elixir 生态系统是一个亮点。对于很多其他语言,是先有了语言,才形成生态系统,二者完全是两码事。在 Elixir,生态系统的核心框架就是 Elixir 团队构建的。Elixir 的创建者 José Valim 同时还是PhoenixEcto这两个 Elixir 生态中最酷软件库的主要贡献者。

 

大多数其他语言,存在很多解决近乎相同问题的不同软件库,例如多种 Web 服务器、ORM 等。在 Elixir 中,开发工作聚焦于数个核心软件库,给出卓越的软件库质量。

 

Elixir 的文档非常优秀,其中提供了丰富的例子。不同于其他语言,Elixir 标准软件库同样具有很好的文档。

 

👍 Phoenix 框架

Phoenix 框架的口号是“Phoenix,感觉好极了!”。不同于其他语言的框架,Phoenix 内建了大量功能,开箱既可用,支持 WebSockets、路由、HTML 模板语言、国际化、JSON 编码解码、无缝 ORM 集成(Ecto)、会话、SPA 工具包等。

 

Phoenix 框架的性能出奇的好,单机即可处理百万级并发连接

 

👍 全栈 Elixir

Phoenix 框架近期引入了LiveView,支持在 Elixir 内部构建丰富的实时 Web 接口。想想单页应用的实际场景。无需 JavaScript,也无需 React!

 

LiveView 甚至可以处理客户和服务器的状态同步。这意味着开发人员不用操心对 REST/GraphQL API 的开发与维护。

 

👍 数据处理

在很多数据处理任务上,Elixi 完全可替代 Python。对于编写 Web 爬虫任务,Elixir 代码更好,生态更好,完全胜出 Python。

 

Elixir 可使用Broadway等工具,构建数据接入和数据处理流水线。

 

👌 类型系统

在我看来,Elixir 的最大缺点是没有适合的静态类型。鉴于 Elixir 并非静态类型,在编译时,编译器和 dialyzer会大量报错。该问题一直存在于 JavaScript、Python 和 Clojure 等动态类型语言中。

 

👍 速度

Elixir 编译器是多线程的,提供刀锋般迅速的编译速度。相比 JVM,Erlang VM 启动更快,为 Elixir 用户提供很好的运行时性能。

 

👍👍 可靠性

Elixir 是基于 Erlang 的构建,利用了 Erlang 三十多年构建最可靠软件的经验。运行在 Erlang VM 上的一些程序,可达到99.9999999%的可靠性。没有任何其他平台能达到同样的可靠性。

 

👍 👍 并发

大多数编程语言在设计上并未考虑并发。这意味着难以编写使用多线程、多处理器内核的代码。这些编程语言使用线程执行并行代码,以及进程读取和写入的共享内存。线程类方法通常易于出错,易于发生死锁,导致复杂性呈指数级增加。

 

Elixir 构建于 Erlang 之上,具有优秀的并发特性。它采用称为Actor模型的完全不同方法实现并发。使用 Actor,进程(Actor)间不存在任何共享。每个进程维护自己的内部状态,收发消息是进程间的唯一通信方式。

 

Actor 模型的创建者 Alan Kay最初尝试了面向对象编程,对象间不做任何共享,只是通过消息传递通信。

 

下面概要比较一下 Elixir 和它的命令式编程表亲 Go。不同于 Go,Elixir 设计上完全考虑了容错。每当 goroutine 崩溃时,整个 Go 程序都会关闭。在 Elixir 中,一个进程死亡时,并不会影响程序的其余部分。更好的是,失败的进程将由其监管进程自动重启,支持失败进程重试失败的操作。

Elixir 进程非常轻量级,开发人员可在单机上轻松生成成百上千的进程。

 

👍 👍 扩展

再次与 Go 对比。Go 和 Elixir 的并发都使用并发进程间的消息传递。在单机上,Go 程序的首次运行速度更快,因为 Go 编译为原生代码。

 

一旦扩展到多台机器,Go 程序性能开始下降。原因何在?因为 Elixir 设计完全考虑了在多台机器上运行。Elixir 运行基于 Erlang VM,表现非常出色一旦面对分布式和扩展时。Erlang VM 无缝地承担了大量繁琐事项,包括集群、RPC 功能和联网等。

 

从某种意义上说,在微服务横空出世之前,Erlang VM 就已经开始实现微服务了。每个进程都可以视为微服务。和微服务一样,进程间彼此独立。在语言内置通信机制的情况下,进程通常可在多台计算机上运行。

 

避开 Kubernetes 的复杂性而使用微服务。这正是 Elixir 的设计目标。

 

👍 错误处理

Elixir 采用了一种独特的错误处理机制。Haskell、Elm 等纯函数式语言在设计是考虑错误最小化,而 Elixir 假定错误的发生是不可避免的。

 

尽管 Elixir 支持抛出异常,但通常并不推荐捕获异常。监管进程会自动重启失败的进程,确保程序的运行。

 

👌 学习难度

Elixir 是一种简单易学的语言,新手可在一两个月内上手。OTP 是学习中的难点。

 

OTP 是 Erlang 提供的一组工具和软件库,是一个杀手级特性。Elixir 基于 OTP 构建,是成功地极大简化构建并发和分布式程序的秘方。

 

尽管 Elixir 本身非常简单,但理解OTP确实需要花费一些功夫。至少对我如此。

 

👍 学习资源

作为最广为使用的函数式编程语言,Elixir 具有丰富的学习资源。Pragmatic Programmers上提供了数十本很好的 Elixir 数据。这些学习资源大多对初学者非常友好。

 

👍 模式匹配

Elixir 具有很好的模式匹配支持。

 

👎 数字密集型运算

Elixir 在计算密集任务上表现不佳。应该选择 Go、Rust 等编译为原生码的语言。

和 Erlang 相比,孰能胜出?

从各方面看,Elixir 和 Erlang 师出同门。Erlang 是一种具有独到语法的强大语言。Elixir 可以认为是一种语法更好的、更先进的,并且具有很好生态系统和社区的 Erlang。

 

评判



Elixir 可能是所有函数式语言中最强大的,运行在针对函数式编程的虚拟机上,完全针对并发设计,非常适合现代多处理器。

 

更多信息,可观看Elixir Documentary短片。

奖项



Elixir 荣获两项荣誉。

 

  • “构建 Web API 最佳语言”奖:归功于其所体系的弹性、函数式优先方法以及很好的生态系统。

  • “构建并发和分布式软件最佳语言”奖:归功于 OTP 和 Actor 模型。不同于命令式编程表亲 Go,Elixir 编写的软件可水平扩展到数千台服务器,提供开箱即可用的容错。

做事选用正确的工具


照片来自由Unsplash照片共享网站的Haupes Co

 

你会用螺丝刀去钉钉子吗?当然不会。同样,我们也不会使用一种编程语言完成所有的任务。每种语言都有其用武之处。

 

Go 是系统编程的最佳语言。ReasonML 无疑是前端开发的最佳选择,它满足优秀编程语言的绝大多数要求。Elixir 是 Web API 开发上的绝对赢家,其唯一缺点是缺少静态类型系统,但其具有强大的生态系统、社区、可靠性和并发功能。对于任何类型的并发和分布式软件,最优选择还是 Elixir。

 

对于数据科学,也许 Python 是不二的选择。

 

真心希望此文章有所裨益。比较编程语言绝非易事,我尽力而为之。

 

原文链接: These Modern Programming Languages Will Make You Suffer

2021-03-29 09:003937

评论 4 条评论

发布
用户头像
没有clojure。。。
2021-04-01 11:40
回复
用户头像
不同意作者关于不可变对象比可变对象效率高的论断,因为一般来讲,如果有十万个元素的数组,内容不变时,可变对象编程也不需要拷贝,当你需要改变其中一个元素时,需要拷贝整个数组的反而是不可变编程模式...
2021-03-30 06:28
回复
我看过点clojure入门书,Clojure的不可变数据结构在底层会共享的相同数据,只拷贝不同不部分,不会去拷贝整个数组。
2021-05-07 20:39
回复
用户头像
这篇文章词句尾部漏字的情况比较严重。
2021-03-29 15:32
回复
没有更多了
发现更多内容

2021金九银十阿里Java岗7轮技术面经历,险幸上岸

Java 程序员 架构 面试 计算机

Android 资源溢出崩溃轻松解

字节跳动终端技术

字节跳动 移动开发 Mars 火山引擎 MARS-APMPlus

会声会影和剪映在音频处理功能上的比较

懒得勤快

为什么网络 I/O 会被阻塞?

编程 架构 操作系统 计算机

马萨卡!阿里大佬珍之若宝的最强高并发pdf,竟然被上传GitHub开源

Java 架构 面试 编程语言

GitHub上首本IntelliJ IDEA操作手册,标星果然百万名不虚传

Java 架构 面试 程序人生 编程语言

阿里P8手抄本惨遭泄露,并出现病毒式传播,致28人斩获大厂offer

收到请回复

Java 面试 阿里 大厂Offer

EMQ X VS RabbitMQ:两大消息服务器 MQTT 性能对比全解(下)

EMQ映云科技

RabbitMQ 物联网 IoT mqtt emq

封神总结!蚂蚁金服+滴滴+美团+拼多多+腾讯15万字Java面试题

收到请回复

Java 程序员 面试 微服务 大厂Offer

JS的深浅复制,原来如此!

华为云开发者联盟

js 序列化 深复制 浅复制

相约 DTCC 2021 | Tapdata 受邀分享:如何打造面向 TP 业务的数据平台架构

tapdata

Java集合核心内容之葵花宝面,搞定90%以上的技术面!建议收藏

程序员小呆

Java 程序员 架构师

J2PaaS 低代码平台,正式发布开源版!

J2PaaS低代码平台

低代码 零代码 低代码开发 低代码开发平台 无代码平台

极客架构营2期模块5作业

Ping

程序员常用的工具软件推荐

程序员小呆

Java c++ 程序员 架构师 Go 语言

手把手带你做LiteOS的树莓派移植

华为云开发者联盟

树莓派 系统 LiteOS arm 树莓派移植

惊了!网易架构大牛熬夜手敲千页网络协议笔记,竟在Github上标星百万!

Java 架构 面试 程序人生 编程语言

保持高效学习的 7 个方法

Phoenix

学习方法

网易云信 NERTC 高清画质体验之 H.265的工程实践 | 体验共享技术专题

网易云信

Java 测试 音视频 视频

Kubernetes 中的应用参数配置案例详析

Zilliz

数据库 Kuber k8s Helm

惊!HUAWEI高工熬夜赶出这本20W字的图解计算机操作系统指南手册,竟被我偶然发现!

Java 架构 面试 程序人生 编程语言

我凭借这份pdf拿下了蚂蚁金服、字节跳动、小米等大厂的offer

Java 编程 程序员 架构

理论+实例,带你掌握Linux的页目录和页表

华为云开发者联盟

Linux 内存管理 寄存器 页目录 页表

2022年最新Java小白学习路线总结,从零基础跟着学习不掉队(PDF+视频分享篇)

Java 编程 程序员 计算机 java面试

这还不够全?阿里P8架构师耗时八年时间才整理出来这“Java核心知识PDF(Java高岗)

Java 程序员 架构 面试 后端

英特尔举办第十四届物联网峰会,携手中国生态伙伴迈向融合边缘新时代

科技新消息

Qcon 免费报名 | 融云「实时通信技术专场」议题抢鲜看

融云 RongCloud

开发者 通信云 场景化

从简历被拒到收割8个大厂offer,我用了3个月成功破茧成蝶

收到请回复

Java 程序员 面试

网络安全产品之堡垒机应用于金融行业案例讲解

行云管家

云计算 网络安全 等保 堡垒机

Zookeeper 集群部署的那些事儿

牧小农

zookeeper

从互联网“后来者”到“引领者”:这场IPv6大会上,我读懂了中国式创新

脑极体

一文解决现代编程语言选择困难:响应式编程_语言 & 开发_Ilya Suzdalnitski_InfoQ精选文章