写点什么

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

  • 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:004635

评论 4 条评论

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

开源规则引擎——ice:致力于解决灵活繁复的硬编码问题

声网

开源 规则引擎 Dev for Dev

云效钉钉小程序上线啦!业务方请痛快一键三连

阿里云云效

阿里云 云原生 钉钉 研发 云效钉钉小程序

有哪些好用的杀毒软件?

InfoQ IT百科

如何在APP原型上写需求?

InfoQ IT百科

亚马逊云科技平台上的无服务器 WebSocket

亚马逊云科技 (Amazon Web Services)

Serverless websocket 亚马逊云科技 appsync

低代码让人人都是开发者,高校人才有了努力的新方向

一只大光圈

阿里 低代码 数字化 钉钉宜搭 浙江工商大学

md文件要用什么软件打开?

InfoQ IT百科

外包学生管理系统

流火

MongoDB Java 原生使用示例

Java mongodb 4月月更

Flutter 使用 Dio 的 Post 请求添加数据

岛上码农

flutter ios 安卓开发 4月月更 跨平台开发

Chrome如何启用隐身模式?

InfoQ IT百科

如何制定移动APP的加载与刷新策略?

InfoQ IT百科

APP、小程序、H5,如何选择不同的开发载体?

InfoQ IT百科

深度学习—人工智能的第三次热潮

云智慧AIOps社区

人工智能 机器学习 深度学习

APP访问用户的通讯录后,会得到通讯录上的信息吗?

InfoQ IT百科

银行App为什么都不怎么好用?

InfoQ IT百科

如何进行APP版本升级管理?

InfoQ IT百科

深入浅出 Ext4 块和 Inode 分配器的优化(下)

焱融科技

云计算 高性能 文件存储 文件系统

下载软件哪个好?

InfoQ IT百科

电脑上切换输入法的快捷键是什么?

InfoQ IT百科

预测猝死时间:AI与死神的争夺

脑极体

如何对APP进行数据分析?

InfoQ IT百科

如何清除WinRAR压缩文件历史记录?

InfoQ IT百科

怎么清理钉钉缓存的图片和文件?

InfoQ IT百科

常见的中文电脑输入法软件有哪些?

InfoQ IT百科

虎符交易所完成三月份HOO回购 生态板块持续扩展

区块链前沿News

Hoo 虎符交易所 回购

React Hooks 的实现必须依赖 Fiber 么?

云智慧AIOps社区

前端 大前端 React Hooks preact

抖音获客源码,蓝V思域运营,大热的X-Gorgon 0408和8408算法,今年的SaaS源码,编程语言需要变革吗?

yunluohd168

抖音短视频获客系统 抖音获客源码

2022年,我加入了微软MVP大家庭

不脱发的程序猿

开源社区 技术影响力 微软MVP

如何用WinRAR将大文件分割成多个小文件?

InfoQ IT百科

Chrome如何安装插件?

InfoQ IT百科

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