为什么选择 Elm?(以及如何开始使用它)

Avatar of James Kolce
James Kolce

DigitalOcean 为您旅程的每个阶段提供云产品。开始使用 200 美元的免费积分!

在任何关于 Web 开发最大问题的讨论中,毫无疑问复杂性都会被提及。在使用数十种工具、依赖项以及语言本身固有的复杂性之间,复杂性是工作的一部分。

现代 Web 应用程序需要我们重新思考如何处理流程。在没有严格顺序或在意外时刻(例如点击按钮或传入网络消息)执行函数的问题,迫使我们使用不同的技术,从回调和 Promise 到更复杂的工具,如 Reactive Extensions

系统由数千行代码组成的情况并不少见,并且在如此规模下,事情很容易失控。即使是最细微的更改也可能产生意想不到的后果。运行时错误在 JavaScript 中非常常见,如果不谨慎,我们可能会发现自己试图进行不可能的计算,例如调用不存在的内容或尝试处理不再是您认为的内容。

文章系列

  1. 为什么选择 Elm?以及如何开始使用它(您当前所在位置!)
  2. Elm 架构简介以及如何构建我们的第一个应用程序
  3. Elm 应用程序的结构

(一些)函数式编程的优势

受控状态

在像 JavaScript 这样的语言中,我们可以对程序的状态做几乎任何我们想做的事情。对状态缺乏控制是导致 bug 的常见先兆。例如,尝试使用以意外方式更改的值。

虽然状态无法消失,但它可以得到控制;函数式语言试图提供以更组织的方式使用它的工具。其中一些,如 Clojure,提供了不可变数据结构等特性,但其他一些则走向极端,成为纯函数式语言,如 Haskell,其中必须仔细考虑状态的每一个细节。

可预测性

纯函数是函数式编程的核心概念,它们非常有用。当一个函数在给定相同输入时始终返回相同的结果并且不产生副作用时,我们称之为纯函数。

因为函数除了获取值、处理该值并返回值之外,不能做任何其他事情——我们可以获得更可预测的程序。

编写做什么,而不是如何做

函数式语言是声明式语言,这意味着我们可以描述一个过程,而不必担心该过程是如何完成的所有细节。对代码目的有清晰认识的后果可能是非常积极的,因为您可以更容易地理解程序的功能,而无需实现方式的干扰。

模块化和可组合性

“分而治之”的原则在函数式编程中是一种常见的实践。我们将复杂的问题分解成几个简单的问题,为每个问题创建一个解决方案,然后将它们组合起来解决整个问题;通过将我们的程序分解成小的函数,我们可以提高可重用性和可维护性。

纪律

纯函数式语言促使程序员更好地思考他们正在构建的程序。从简单的 类型注解 和控制副作用的机制到完整的架构,例如在 Elm 中。

虽然这种限制可能会增加最初的开发时间,但可维护性的优势可以弥补这些努力。

Elm:一种用于 Web 的函数式编程语言

Elm 是一种相对较新的用于构建 Web 应用程序的编程语言,由 Evan Czaplicki 于 2012 年创建,作为其关于 用于函数式 GUI 的并发 FRP 的论文的一部分。

因为它面向 Web——并且浏览器只理解有限的语言集——因此您用 Elm 编写的代码会被编译成 HTML、CSS 和 JS。

天生不可变

Elm 上的所有值都是不可变的,这意味着一旦您拥有某些东西,它将保持不变,除非您创建新的某些东西作为副本。

最简单的优势——因为我们不必记住变异——阅读代码变得容易得多;当我们看到一个值时,我们可以确定它会保持不变。此外,您不必担心由于变异而导致的与意外值相关的运行时错误。

静态类型

静态类型是记录系统的一种极佳方式。您只需查看类型定义,就可以快速了解函数的功能及其域。类型系统允许我们准确定义函数接收和返回的值类型。

但是类型不仅在开发人员阅读时有用,它们还允许使用更好的工具(如调试器和测试器)来提高代码的健壮性。此外,它们可以防止我们进行不可能的计算,从而避免许多潜在的运行时错误。

Elm 具有一个推断类型系统。这意味着您不必从一开始就编写类型,编译器会尝试为您的函数推断合适的类型,因此您不会像其他主流类型语言那样产生任何生产力损失。

-- Function with its type annotation
square : Int -> Int
square n = n^2

-- This also works, because types are inferred
square n = n^2

纯函数

纯函数是函数式编程的核心概念,在构建可靠且易于维护的系统方面具有巨大优势。Elm 函数是纯函数,它们接收输入并生成新的值作为输出,并且对于每个输入始终返回相同的结果,而不会产生副作用。

订阅

异步系统需要不同的编程方法,Elm 通过使用订阅来处理这种情况。因为有些事情我们不知道何时发生——例如用户交互或网络响应——我们可以订阅这些事件,以便在我们能够立即采取行动,而无需担心实现细节。

受控副作用

纯函数很好,我们可以在任何地方尝试使用纯函数,但在某个时刻,我们必须处理程序与外部世界的交互,我们可以使用命令以受控的方式执行此操作。

因为输入和输出也是副作用,如果我们在任何地方都严格使用纯函数,我们将什么也做不了。相反,我们会识别副作用,并将其明确化,以便 Elm 可以以正确的方式为我们处理它们。

与现有项目兼容

您不必构建或移植一个完整的项目到 Elm——尽管那会很好——您可以在现有项目中包含用 Elm 编写的某些组件。

此外,可以在 Elm 应用程序中使用现有的 JavaScript,同时保留该语言的所有优点和保证。

易于测试

静态类型给我们带来了什么?很棒的工具,包括随机测试生成器,其中函数可以根据函数的域(由类型定义)针对随机输入进行测试。

但也许这个领域中最令人兴奋的 Elm 特性之一是,我们可以导出程序的完整状态,并且使用它,我们可以做以下事情:

  • 将其发送给库开发人员以重现错误。
  • 在另一个浏览器或设备中加载它以查看事物如何运行。

所有这些都是在安全的方式下完成的,包括网络调用,这要归功于使用命令的受控副作用。

从一开始就拥有更好的架构

当人们开始使用 Elm 构建项目时,他们注意到每次都会出现一种模式。这就是 Elm 架构的开始,构建 Elm 应用程序的事实上的方法

拥有一个可靠的架构来构建我们的项目将确保我们能够控制一切,即使我们的程序变得越来越复杂。此外,我们还有一个优势,即每次我们接手一个新的现有项目时,我们都不会在理解代码组织方面遇到太大问题。

合理的包管理器

您是否曾经更新过 JavaScript 包,并在启动应用程序后发现某些内容无法按预期工作或根本无法工作?好吧,在 Elm 中不存在这个问题;包管理器会根据所做的更改自动处理版本号,因此您将永远不会再遇到次要版本中的重大更改。

对初学者友好的编译器

我们都知道编译器通常会生成难以理解的消息,但 Elm 并非如此。每次您犯错误时,它都会指出问题所在,并说明如何解决。

Elm 编译器消息

您可以在 Elm 官方博客中查看更多示例

设置我们的开发环境

读者 Scott Phillips 写信说:“Elm 0.19 有重大更改……但文章的要点仍然有效。”我们的编辑建议是参考官方文档以获取安装和特定功能的信息,并享受本系列文章,将其作为了解您可能为什么要使用 Elm 以及如何用 Elm 式思维思考的入门。

安装 Elm 平台

安装 Elm 非常简单。在安装 NodeJS之后,您可以使用 NPM 直接安装 Elm,就像其他 NodeJS 包一样。

npm install -g elm

或者,您也可以使用 Mac 或 Windows 的安装程序安装 Elm。链接可在Elm 入门网站的“安装”部分找到。

获取文本编辑器

尽管 Elm 是一种相对较新的语言,但已经存在用于处理它的优秀工具,特别是对于使用AtomElmjutsu包。

但 Atom 不是唯一的,以下编辑器也受支持:

您可以选择您最喜欢的那个。这些编辑器的安装过程不在本文的讨论范围内;我建议您查看其各自的网站。

Elm 平台简介

Elm 平台:四个对 Elm 项目开发至关重要的工具。一个REPL用于测试代码的小片段。Reactor,一个服务器,它将允许我们在浏览器中立即查看我们的项目。Make,一个用于构建项目的工具,以及包管理器,它将为我们提供对其他人编写的模块的访问权限。让我们来看看这些工具,看看我们可以用它们做什么。

读取-评估-打印循环

Elm REPL 是一个在命令行中评估简单 Elm 代码的工具,无需创建文件。顾名思义,它首先读取输入,然后对其进行评估,最后打印结果。此工具对于探索语言或快速执行一小段代码非常有用。

要开始使用 REPL,只需打开一个终端并执行以下命令:

elm-repl

# Update: `elm repl` now

你应该得到类似这样的结果

---- elm-repl 0.18.0 -----------------------------------------------------------
 :help for help, :exit to exit, more at <https://github.com/elm-lang/elm-repl>
--------------------------------------------------------------------------------
>

我们可以在>符号旁边编写Elm代码。作为一个简单的测试,让我们尝试构建一个square函数来获取数字自乘的结果。我们可以通过编写以下内容并按Enter键来实现。

square n = n * n

现在,我们有一个square函数可用于当前环境,我们可以在未来的评估中使用它,例如,尝试键入

square 5

它应该返回25 : number。第一部分是我们的结果,其余部分是结果的类型,它是一个number

你还可以编写更复杂的代码。让我们编写一个简单的阶乘函数

factorial number = \
  if number == 0 then 1 \
  else number * factorial(number-1)

要编写多行代码,你必须在每一行的末尾添加一个\,以告诉解释器你想继续编写。

在键入第一行并按Enter键后,将出现一个|字符,然后你可以编写代码的下一行。注意缩进,如果不添加缩进,将会返回错误;你必须至少添加一个空格。

当REPL处于活动状态时,你可以直接使用四个命令

  • :help 将显示所有可用的命令。
  • :flags 你可以修改Elm编译器的选项。 更新:flags不再存在。
  • :reset 重启当前会话,无需关闭并重新打开REPL。
  • :exit 关闭REPL,无需关闭命令行窗口。

Elm Reactor

Elm-reactor 是一款交互式开发工具,使开发Elm程序变得更加容易。它将创建一个服务器,该服务器会动态编译你的Elm代码,并且你将能够直接在浏览器中看到结果。

让我们看看如何使用Reactor和一个示例项目。为此,你可以使用Git克隆Elm Architecture Tutorial,并在进入项目目录后,执行以下命令

elm-reactor

# Update: `elm reactor` now

将创建一个服务器,你将能够在Web浏览器中通过https://127.0.0.1:8000访问它。

你将看到一个导航页面,其中包含项目目录中的文件。转到examples/01-button.elm并等待片刻,直到文件编译完成。完成后,你将能够看到一个小部件,它使用两个按钮来增加和减少一个数字。

这就是我们如何在不生成最终文件的情况下查看项目的方式,这对于开发过程非常有用。

可以使用两个有用的标志与elm-reactor命令一起使用,要查看完整列表,你可以运行elm-reactor --help

  • -a 为服务器设置自定义地址。
  • -p 为服务器设置不同的端口(默认情况下为:8000)。

例如

elm-reactor -a 0.0.0.0 -p 3000

# Update: `elm reactor` now

尽管Reactor是开发Elm应用程序的一个非常有用的工具,但最终你将到达需要将项目投入生产的阶段,Elm平台中包含了一个用于此任务的工具:Make。

Elm Make

一旦你的项目准备就绪,你将不得不将Elm代码转换为Web浏览器可以理解的内容,即HTML、CSS和JavaScript。我们使用一个名为elm-make的工具来完成此任务,它将获取Elm文件并生成用于Web的本地文件。

例如,让我们尝试编译上一节中使用的示例。转到Elm Architecture Tutorial项目中的examples目录并执行以下命令

elm-make 01-button.elm --output=01-button.html

# Update: `elm make` now

当你执行此命令时,它会要求你批准安装一些包,输入Y并按Enter键,现在让我们等待编译完成。

这会将我们的01-button.elm文件构建成一个01-button.html,可以直接在浏览器中打开。如果你转到01-button.elm文件所在的目录,你还会找到新的HTML文件。你可以使用浏览器打开该文件,你将看到与上一节相同的结果,但这次,我们无需使用任何Elm工具即可看到它;它只是普通的HTML和Javascript。

现在,我们可以按照我们想要的方式分发该文件,任何Web浏览器都能够打开它。

Elm 包管理器

当你开始构建非简单的应用程序时,你会很快发现自己编写所有内容是不切实际的。通常,对于最常见任务,你会发现其他人已经编写的包,你可以在你的项目中使用它们。为此,我们使用Elm包管理器,这是一个用于安装和发布Elm包的工具。

正如我们在上一节中尝试编译Elm文件时看到的,编译器会询问我们是否要安装缺少的包,这实际上是包管理器的作用。

此工具在你的命令行中可用作elm-package,要安装包,你可以执行以下命令

elm-package install 

# Update: package manager integrated into `elm` command now

其他命令包括

  • publish:将包添加到中央目录。
  • bump:根据更改更新包的版本号。
  • diff:一个用于查看包版本之间差异的实用程序。

Elm Format

Elm Format 不是Elm平台的一部分,但它是一个非常有用的工具,值得一提。它将根据标准样式指南格式化你的源代码,你还可以设置你的编辑器,以便在每次保存.elm文件时自动执行此操作。

你可以在其Github 存储库中查看Elm format。

体验 Elm

在你的电脑上创建一个新的文件夹,并使用你的IDE或文本编辑器打开它。我们将创建一个first-application.elm文件,在其中我们将包含以下代码

import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)

main =
  Html.beginnerProgram { model = model, view = view, update = update }

-- MODEL

type alias Model = Int

model : Model
model =
  0

-- UPDATE

type Msg = Increment | Decrement

update : Msg -> Model -> Model
update msg model =
  case msg of
    Increment ->
      model + 1

    Decrement ->
      model - 1

-- VIEW

view : Model -> Html Msg
view model =
  div []
    [ button [ onClick Decrement ] [ text "-" ]
    , div [] [ text (toString model) ]
    , button [ onClick Increment ] [ text "+" ]
    ]

暂时不要担心这段代码的含义;我们只是用它来测试我们的设置。

我们应该检查的第一件事是我们的编辑器是否启用了语法高亮显示,如果你只看到纯文本,请再次检查本文的相应部分。

接下来,我们将使用Reactor测试Elm代码的编译。在终端中,转到项目目录并执行以下命令

elm reactor

应该会显示一条类似这样的消息

elm-reactor 0.18.0                                                                                                                                                                   
Listening on https://127.0.0.1:8000/  

这意味着它正在工作,并且我们有一个在localhost的端口8000上监听的Web服务器。如果我们在Web浏览器中打开该地址,我们应该会看到一个文件导航器,点击first-application.elm文件,它将构建我们的项目。准备就绪后,我们将看到一个页面,其中包含一个小部件,该部件带有一个数字和两个按钮。这就是我们代码的结果。

Elm 示例:数字计数器

结论

我们探索了函数式编程和Elm语言的一些优势,并且现在我们对将用于构建项目的平台更加熟悉。

要获取有关Elm的更多详细信息,你可以查看Evan Czaplicki撰写的书籍Elm 入门