Elm语言

Elm是一个领域特定编程语言，用于声明式地创建基于web浏览器的图形用户界面。Elm是纯函数式的，开发它时强调了易用性、性能和健壮性。它宣传为“实际上没有运行时间异常”，Elm编译器的静态类型检查使之成为可能。

## 历史

Elm最初由Evan Czaplicki在2012年作为毕业论文《Elm：用于函数式GUI的并发FRP而设计的。Elm的首次发行带有很多例子和一个在线编辑器，使得易于在web浏览器中试验它。Evan在2013年加入Prezi从事Elm的工作，并在2016年转移到NoRedInk作为开源工程师，启动了Elm软件基金会。

Elm编译器的最初实现执行目标为HTML、CSS和JavaScript。核心工具集持续的扩展，现在包括了REPL、包管理器、时间旅行调试器和针对macOS及Windows的安装器。Elm还有一个生态系统，包括社区创建的库和Ellie，它是一个高级在线编辑器，允许保存工作和包含社区库。

## 特征

Elm有一个小集合的语言构造，包括传统的if表达式，let表达式用于局部状态，和case表达式用于模式匹配。作为函数式语言，它缺省的支持匿名函数，函数作为实际参数，和部分应用。它的语义包括不可变的值，无状态函数，和具有类型推论的静态类型。Elm程序通过虚拟的DOM呈现HTML，还可以使用“JavaScript作为服务”来与其他代码进行互操作。

### 不可变性

在Elm中所有的值都是不可变的，这意味着一个值不能在创建之后修改。Elm使用持久性数据结构来实现它的`Array`、`Dict`和`Set`库。

### 静态类型

Elm是静态类型的。类型标注（annotation）是可选的（由于有类型推论）但强烈鼓励。标注存在于定义之上的一行（不同于C家族语言，这里的类型和名字是夹杂在一起的）。Elm使用单一的冒号表达“拥有类型”。

类型包括原始类型如整数和字符串，和基本数据结构比如列表、元组和记录。函数拥有用箭头写成的类型，例如`round : Float -> Int`。定制类型允许编程者建立定制类型，以匹配特定问题领域的方式来表示数据。

类型可以推论出其他类型，例如`List Int`。类型总是首字母大写；小写名字是类型变量。例如，`List a`是未知类型的值的列表。它是空列表和给`List.length`的实际参数的类型，对于这个列表的元素而言它是不可知的。有一些特殊类型，编程者建立用来与Elm运行时进行交互。例如，`Html Msg`表示（虚拟）DOM树，其事件处理类型`Msg`的所有产生消息。

不再允许任何值是隐含的可空值（比如JavaScript的`undefined`或空指针），Elm的标准库定义了`Maybe a`类型。产生或处理一个可选值的代码不显式的使用这个类型，而所有其他代码得到保证声称了类型的值是实际上存在的。

Elm提供有限数目的内建类型类：`number`，它包括`Int`和`Float`，用来利用数值算符比如`(+)`或`(*)`；`comparable`，它包括数值、字符、字符串、可比较者的列表和可比较者的元组，用来利用比较算符；和`appendable`，它包括字符串和列表，用来利用`(++)`进行串接。Elm不提供将定制类型包括入这些类型类，或建立新类型类的机制（参见限制章节）。

### 模块系统

Elm拥有模块系统来允许用户将其代码分解到叫做模块的更小的部分之中。模块可以隐藏实现细节比如帮助函数，并组织有关的代码在一起。模块为可导入代码充当名字空间，比如`Bitwise.and`。第三方库（或包）构成自一个或多个模块，并可从Elm公共库中获得到。所有的库都采用语义版本号，这是编译器和其他工具所强制的。就是说，移除一个函数或改变它的类型只能在主要发行中进行。

### 同HTML、CSS和JavaScript的互操作

Elm使用叫做端口的抽象来与JavaScript通信。它允许值流入和流出Elm程序，确使了在Elm和JavaScript之间的通信。

Elm有一个叫做elm/html的库，编程者可以用来在Elm内书写HTML和CSS。它使用虚拟DOM方式来使更新有效率。

### 后端

Elm官方上不支持服务器端开发。核心开发团队不将它作为主要目标考虑，并且偏好聚焦于在增进前端开发体验上的开发。尽管如此，有一些独立的计划，它们尝试探索将Elm用于后端的可能性。这些计划主要系附于Elm版本0.18.0，因为更新的版本不支持“原生”代码和其他可利用特征。有两个尝试将Elm用在BEAM（Erlang虚拟机）之上。其中一个计划直接在这个环境上执行Elm，而另一个计划把它编译成Elixir。还有一个尝试通过Node.js下部结构为Elm建立后端框架。这些计划都没有准备好用于生产。

## Elm架构

Elm架构是建造交互式web应用的模式。Elm应用本质上以这种方式来构造，但是其他项目可能发现这个概念很有用。

Elm程序总是分解成三个部分：

- 模型：这个应用的状态。
- 视图：一个把模型转变成HTML的函数。
- 更新：一个基于消息更新模型的函数。

这些是Elm架构的核心。

例如，想象显示一个数值和在按下时增加这个数值的一个按钮的一个应用。在这种情况下，所有我们需要存储的是一个数值，所以我们的模型可以简单的就是`type alias Model = Int`。`view`函数将用`Html`库来定义并显示这个数值和按钮。为了让这个数值被更新，我们需要能够向`update`函数发送消息，这是通过定制类型比如`type Msg = Increase`来完成的。`Increase`值被附着于在`view`函数内定义的按钮，使得在用户点击这个按钮的时候，`Increase`被传递到`update`函数之上，它可以通过增加这个数值来更新这个模型。

在Elm架构中，发送消息至`update`是改变状态的唯一方式。在更加复杂的应用中，消息可以来自各种来源：用户交互，模型初始化，来自`update`的内部调用，订阅的外部事件（窗口改变大小、系统时钟、JavaScript互操作等等）和URL变更及请求。

## 限制

Elm不支持高种类多态，这是同为函数式的语言Haskell和PureScript所提供的，Elm还不支持创建类型类。

这意味着，例如Elm没有跨越多种数据结构如`List`和`Set`的通用的`map`函数。在Elm中，这种函数典型的要限定上它们的模块名字来调用，例如调用`List.map`和`Set.map`。在Haskell或PureScript中，只有一个函数`map`。自从2015年这就是在Czaplicki的粗略路线图上的一个周知的特征要求。

另一个缺陷是在中到大型项目中有大量的样板代码，如《Elm in Action》作者在他们的单一页面应用例子中所展示的那样，具有几乎同样的片段被重复于更新、视图、订阅、路由解析和建造函数之中。

## 样例代码

下面的例子代码通过注释展示了Elm的基本特征：

```elm
-- 这是一个单一行注释。

{-
这是一个多行注释。
它是 {- 可嵌套的。 -}

-- 这里定义叫做greeting的一个值。类型被推论为String。
greeting =
    "Hello World!"

 -- 对顶层声明最好增加类型标注。
hello : String
hello =
    "Hi there."

-- 函数以相同方式声明，具有跟随在函数名字后的实际参数。
add x y =
    x + y

-- 再次的，最好增加类型标注。
hypotenuse : Float -> Float -> Float
hypotenuse a b =
    sqrt (a^2 + b^2)

-- 函数可以柯里化；这里我们柯里化乘法中缀算符于数2之上。
multiplyBy2 : number -> number
multiplyBy2 =
    (*) 2

-- If表达式用于在Bool值上的分支。
absoluteValue : number -> number
absoluteValue number =
    if number < 0 then negate number else number

 -- 记录用来持有命名字段。
book : { title : String, author : String, pages : Int }
book =
    { title = "Steppenwolf"
    , author = "Hesse"
    , pages = 237 
    }

-- 记录访问通过 . 来进行。
title : String
title =
    book.title

-- 记录访问 . 也可以用作一个函数。
author : String
author =
    .author book

-- 可以通过type关键字建立标签联合。
-- 下列值表示一个二叉树。
type Tree a
    = Empty
    | Node a (Tree a) (Tree a)

-- 可以用过case表达式检测这些类型。
depth : Tree a -> Int
depth tree =
    case tree of
        Empty ->
            0

        Node value left right ->
            1 + max (depth left) (depth right)
```

## 参见

- PureScript：一个强类型的、纯函数式的编译成JavaScript的编程语言。
- Reason：OCaml的语法扩展和工具链，也可以转译成JavaScript。
- 函数式反应式编程

## 基本资料

- 日语原文：Elm
- 编程范型：函数式
- 发行日期：2012年3月30日
- 软件版本周期：0.19.1
- 软件版本周期：2019年10月21日
- 受影响于：Haskell, Standard ML, OCaml, F#
- 施影响于：Redux, Vuex
- 类型系统：静态, 强类型, 类型推论
- 设计者：Evan Czaplicki
- 许可证：宽松许可证 (三条款BSD许可证)
- 文件扩展名：.elm