乐天榆树
指数
我们喜欢 Elm 的哪些方面
我们不喜欢 Elm 的地方
结论
其他证词
在乐天团队中,我们已经在生产环境中使用 Elm 1近两年了。这篇文章讲述了我们的故事、我们学到的经验教训以及我们的喜好。
这篇文章很长,所以如果您想看概述,请随意跳到索引。
一切始于 2017 年夏天乐天柏林分公司。当时我们正在维护一个用 Vanilla JavaScript 编写的中型单页应用程序,但事情开始失控。
“我们的 JavaScript 应用程序到处都有全局变量,调试简直是一场噩梦。”
在一个地方修复问题,可能会破坏其他几个地方的代码。我们到处都有全局变量,调试简直是一场噩梦。
我们决定加强一些规范,开始用纯粹的2 级风格重写函数,以重新获得对应用程序的控制。代码变得更好,更独立,也更易于理解。
我们当时想:“要是能找到一个工具来强制执行这些规则就好了,这样我们就不用再依赖自律了……”然后,我们在 css-tricks.com 网站上看到了一篇名为《Elm 架构简介及如何构建我们的第一个应用程序》3 的文章。我们对这篇文章一见钟情。
“一种没有运行时异常的令人愉快的语言。”
-- 榆树网站4
Elm 承诺我们所有的功能在设计上都是纯粹的,并且在运行时5不会再出现任何错误。
此外,Elm 语言中包含的Elm 架构似乎是构建应用程序的绝佳方式。它最终成为前端领域最具影响力的 Elm 理念之一,稍后我们会详细介绍。
Elm 架构,插图由Kolja Wilcke绘制,根据CC BY 4.0获得许可。
于是,我们开始学习 Elm,构建了一些原型,初步成果颇为可观。但当时的技术栈仍然严重依赖服务器端,主要使用 PHP,因此推广进展缓慢。
🕒 一年后
快进一年(向右移动 9,000 公里),在东京的乐天总部,有一个 Elm 可以成功扎根的环境。
一些工程师已经在推动一种更具功能性的编写代码的方式,而在一个严重依赖后端 API 的部门中,迫切需要一种解耦的方式来编写用户界面。
🕒 两年后
再过几年,我们就有了几个用 Elm 构建的生产应用程序,总共约有 10 万行代码。6
这些是我们用 Elm 制作的一些公共项目:一个高度可定制的身份验证和注册系统,用于不同的乐天服务(例如乐天台湾和乐天体育),具有不同的用户旅程要求,一个用于构建此类内容的UI 库,一个HTTP 库,乐天开源网站(源代码),一个简单但无限可爱的404 错误页面,一个关于安全的信息页面(日语)。
使用 R10 库制作的信用卡表格示例。
指数
我们喜欢 Elm 的哪些方面
没有特别的顺序。
- 担保
- 受控状态
- 最小惊讶原则
- “让不可能的事情变得不可能”
- 做事的方法之一
- 稳定
- 函数式编程
- 强制纪律
- 易学性
- 编译器作为助手
- Elm 作为影响者
- Elm 架构
- Elm 调试器
- Elm-UI,CSS/HTML 的替代品
- 可读性和 Elm 语法
- 重构
- 招聘
- 性能快,资源少
- 内容驱动的静态网站
我们不喜欢 Elm 的地方
没有特别的顺序。
我们喜欢 Elm 的哪些方面
1. 保证
这些可能是 Elm 提供的最客观、最重要的保证,在其他框架中很难(不可能?)找到。
- ⛔ 没有运行时异常。
- 🗿 100%不可变的数据。
- 💧 100%纯函数,也包含所有依赖项。
- ♻️ 100% 类型推断。
做出类似上述选择时,需要权衡利弊。例如,无法直接从 Elm 调用 JavaScript 函数。如果这对您至关重要,那么 Elm 并非正确选择。
相反,如果您认为 Elm 保证更重要,那么 Elm 是正确的选择。
在Elm 指南的“Elm/JS 互操作的限制”部分中,Evan Czaplicki 对这个概念进行了更详细的阐述。7
2. 受控状态
JavaScript 允许我们对程序的状态进行任何操作。这对于快速构建原型很有用,但它也很容易导致 bug,因为全局变量的变化很难追踪和理解。
“软件开发中很大一部分缺陷是由于程序员没有完全理解代码可能执行的所有状态”
——约翰·卡马克8
Elm 中的所有函数都必须是纯函数,因此它们不能保存任何状态,并且所有数据都必须是不可变的。应用程序的所有状态都应该存储在一个地方,这在设计上是为了使应用程序更易于理解和调试。
在 Elm 中,状态所在的地方是,Model
它由Elm Runtime 系统9进行管理,以便我们编写的代码 100% 可以是纯粹的(这些概念将在后面详细解释)。
3.最小惊讶原则
Elm 的理念之一是代码的结果应该是可预测的,不会出现意外。10例如:
- 下面详细讨论的Elm 静态类型系统消除了与动态类型相关的一整类意外情况。
- Elm 包管理器支持强制语义版本控制。11 PATCH版本中不会出现意外,因为版本号是由扫描库的脚本强制执行的,例如,删除或重命名暴露的函数。
- Elm-UI是一个用于渲染视图的库,它可以将开发人员的意图以清晰的方式转化为布局,稍后将详细讨论。
像 JavaScript 这样具有自动类型转换(也称为隐式类型转换)的语言可能会导致意外结果。12如果我们将一个数字和一个字符串相加会发生什么?例如,1
和"2"
?结果会怎样3
?结果会怎样"12"
?会出错吗?会得到其他结果吗?
相比之下,Elm 是强静态类型的,因此上述情况不可能发生。13甚至无需添加类型注释14 ,因为类型是由 Elm 编译器推断出来的。类型推断覆盖了 100% 的代码,包括所有外部库。
动态类型与静态类型,插图由Kolja Wilcke绘制,根据CC BY 4.0许可。
如果您需要快速验证概念,动态类型可能更快,而且即使谜题包含错误,它看起来也确实像长颈鹿。但对于健壮的应用程序和正确的谜题解决方案而言,静态类型是正确的选择。
TypeScript 为 JavaScript 添加了可选的静态类型,这可能是 JavaScript 史上最棒的改进之一,它可以部分缓解 JavaScript 动态类型系统的问题。但作为 JavaScript 的超集,它需要在优雅性和简洁性方面做出妥协。它也存在一些“盲点”。15例如,类型声明是可选的(any是逃生出口),类型推断无法覆盖所有代码,需要类型保护,16 JSON 数据未经过类型检查,并且并非所有 JavaScript 库都支持类型注解。
4.“让不可能成为可能”
彭罗斯三角形。17
Elm 类型系统的深度使我们能够精确地模拟场景,使不可能的状态成为可能。18这更像是一种编码模式19而不是语言特性,它需要一个成熟的类型系统才能工作。
为了澄清起见,让我们举一个用类型别名建模 HTTP 状态的示例:20
type alias HttpState =
{ loading : Bool
, error : Maybe String
, success : Maybe String
}
该结构的基数(可能状态的数量)为 2 x 2 x 2 = 8,因为Bool和Maybe 21的基数均为 2。
但 HTTP 请求的可能状态只有三种:Loading
、Error
和。为了使这额外的五种不可能状态成为不可能,我们可以使用自定义类型Success
重写代码:22
type HttpState
= Loading
| Error String
| Success String
自定义类型也称为“总和类型”,因为基数现在是一个总和:1 + 1 + 1 = 3。可能状态的正确数量。
5. 做事方式
该原则23的一个应用是找到问题的最佳解决方案,然后在语言中强制执行它。
例如:
- 采用 Elm 架构作为构建 Web 应用程序的标准方式
- Elm-Format 的linter格式不可配置。因此,所有 Elm 代码都使用相同的样式进行格式化。制表符与空格之争的终结。
该原则保证了代码库之间的一致性,即使它们属于不同的团队和组织。
其他语言和框架遵循不同的原则。例如,JavaScript 遵循“一个 JavaScript”原则。24这意味着 JavaScript 不受版本控制,并且向后兼容。向后兼容是“多种做事方式”的先决条件。
6.稳定性
虽然 Elm 编译器的改进工作已经完成,但该语言本身在四年多的时间里并未进行任何重大更新。25此外,近期也没有任何可预见的更新。26最新版本主要改进了编译器的性能,并删除了一些被认为不必要甚至有害的功能,例如中缀运算符。27
这很棒,因为我们可以专注于构建优秀的产品,而不必花时间将代码更新到最新版本。
“似乎完美不是在于没有什么可以添加,而在于没有什么可以删除。”
——安东尼·德·圣·埃克苏佩里
核心模块也非常稳定。目前大部分活动都发生在非核心模块和工具中。28
我们从 0.18 版本开始编写 Elm,过渡到 0.19 版本的过程非常顺利。在更新 HTTP 库30时,我们遇到了更多问题。由于缺乏内部沟通,我们的一个内部依赖项突然更新到 HTTP 2.0,迫使我们在短时间内更新了所有剩余的代码。
7.函数式编程
函数式编程31又开始流行了!或许我们已经身处第三次范式转变之中。32
无论你使用哪种语言,函数式编程都能带来好处。你应该在方便的时候使用它,而不方便的时候,你应该仔细考虑。
——约翰·卡马克33
函数式编程擅长利用函数组合来处理复杂性,将难题分解成可处理的问题。然后将解决这些可处理问题的函数组合在一起,最终解决原来的难题。
通过这种方式获得的功能往往很小,从而增加了它们的可重用性、可维护性和可读性。
一个有趣的事实是,对于许多开发人员来说,Elm 是函数式编程的门户,是一种教学工具,因为它比其他函数式语言更容易学习。
最近出现了几种新的函数式编程语言:Gleam、Unison、Roc、Koka、Formality。
对于函数式编程来说,这是一个激动人心的时刻。
柯里化
柯里化是许多函数式语言中都存在的一个特性。在 Elm 中,所有函数默认都是柯里化的。柯里化是指将一个接受多个参数的函数转换为一系列接受单个参数的函数:34
add a b = a + b -- <function> : number -> number -> number
add 1 -- <function> : number -> number
add 1 2 -- 3 : number
柯里化的主要优点是增加了组合函数的灵活性,就像用类型签名完成的益智游戏一样。35例如,如果您需要将 10 添加到列表的项目,则可以使用add
上面定义的函数编写:
List.map (add 10) [1, 2, 3] -- Gives [11,12,13]
8. 强制纪律
纯函数式语言能够激励程序员更好地思考他们正在构建的程序。虽然这些限制可能会增加初始开发时间,但其可维护性的提升足以弥补这些努力。
Elm 强制开发人员遵守纪律,而不是让他们自行遵守纪律。这一特性,加上 Elm 的其他特性,使得它非常适合大型前端团队。
“所有语法上合法的东西最终都会出现在你的代码库中”
——约翰·卡马克36
强制纪律的另一个例子是,不可能在 Elm 库中包含 JavaScript 代码。37这意味着 Elm 保证(例如无运行时错误)也对您的依赖项有效。
9. 易学性
Elm 对初学者非常友好。这并不意味着 Elm 不够复杂,而是意味着它设计精良。它既有适合初学者的简单结构,也有适合大师的复杂结构。复杂性是循序渐进的。38这一概念有时被称为“循序渐进学习”或“逐步揭示复杂性” 。39
此外,在其发展过程中,那些造成混淆且不重要的特性已被删除或修改,从而将其转变为一种易于学习的精简语言。40
要用 Elm 编写 Web 应用程序,您不需要成为 JavaScript、CSS 或 HTML 专家。
设置开发环境也很简单,因为“现代 Web”设置中通常需要的所有工具(例如捆绑器、linters 和 Web 框架)在 Elm 中都是嵌入的或不必要的。41
根据我们的经验,初级 Elm 开发人员可以在几周内提高工作效率,并在几个月内掌握该语言。
10. 编译器作为助手
Elm 编译器可以静态分析代码中的不一致之处,并向程序员提供精确的反馈。42
这个特性至关重要,它启发了一种新的编码风格:编译器驱动开发 (Compiler Driven Development)。简而言之:修改部分代码,然后让编译器错误引导你完成剩余的任务。然后反复进行。43
当编译器驱动开发44首先涉及定义类型签名时,我们就进入了类型驱动开发的领域。45
“只要能编译,就说明能用” 46
最佳实践应该尽可能地自动化,而 Elm 编译器在这方面发挥着重要作用。Elm将“最佳实践”作为默认设置。47
编译器保证覆盖所有边缘情况,而这在手动编写单元测试中很难实现。编译器静态分析的另一个优势是速度极快,并且能够提供错误的精确位置。48
Elm 编译器可以生成最先进的错误消息,其高质量标准现在也成为其他语言设计者的灵感来源。49
11. Elm 作为影响者
大多数技术都会以某种方式受到现有理念的影响。例如,Elm 就受到了 Haskell、Standard ML、OCaml 和 F# 的影响。
另一方面,Elm 凭借其创新理念正在影响前端社区和整个编程行业。
例如:
-
Redux是 React 的状态管理系统,其灵感来自 Elm 架构。50
-
SwiftUI是一款用于在所有 Apple 平台上构建用户界面的工具,它深受 Elm 架构和 React 的启发。51
-
这些是受 Elm 架构启发的其他 UI 框架和库:Elmish | Hydux | Hyperapp | DvaJS | Iced | Miso | Realm | Yew | Bolero | Bucklescript-tea | Fabulous | Selm | SwiftElm | Tea-in-swift | Portal | Swift-elm | Harvest | Functional-frontend-architecture | Willow | Seed | Act | Yew | elm-ts | .NET Multi-platform App UI | Bolero。
我们现在使用 Elm,因为我们相信它是 Web 开发的最佳选择之一,而且受其启发的工具数量之多也充分证明了它的优秀。同时,我们也在密切关注该领域的发展,即使出现更好的语言,我们也不会担心更换语言。
考虑到 Elm 的影响力以及函数式编程的普遍趋势,这个“更好的东西”似乎会是类似于 Elm 的东西。所以,如果真有过渡的话,应该会很顺利。52
12. Elm 架构
Elm 架构可能是 Elm 中最重要、最具影响力的创新。53它是一种单向数据流,54有助于保持应用程序的井然有序。此外,由于这是使用 Elm 构建应用程序的标准方法,它还能帮助您快速理解其他开发人员构建的应用程序。
Elm 架构中单向数据流的简单表示。(来源:Elm 指南)。55根据CC BY-NC-ND 4.0 许可证授权。
Elm 架构包含三个构建块:
Model
--- 你的应用程序的状态,唯一可以改变的东西view
--- 一种将您的状态转换为 HTML 的方法update
--- 一种根据Model
和消息更新您的状态的方法
如果我们放大Elm
上图中的块,我们会看到里面的内容:
Elm 运行时系统56如何使用 Elm 架构来协调 Elm 应用程序的无限循环57 。
Elm 运行时系统:
- 等待某事发生,例如“按下按钮”
- 将事件转换为适当的消息
Msg
- 发送
Msg
并Model
返回update
更新的Model
可选命令Cmd
,例如 HTTP 请求 - 发送
Cmd
(如果有)到效果引擎 - 发送更新
Model
到view
将返回新的 HTML - 使用新的 HTML 更新 DOM
- GOTO 开始
13. Elm 调试器
内置的Elm 调试器58是调试 Elm 应用程序的实用工具。它显示应用程序的状态,并跟踪应用程序生命周期内触发的所有消息。它还允许您回溯到过去,与我们正在编写的代码建立直接联系。59
Elm 调试器。从左到右:应用程序;消息历史记录;当前消息和模型。60
这与 Bret Victor 在其著名演讲“原则上的发明”中所展示的内容类似。61
14. Elm-UI,CSS/HTML 的替代品
Elm-UI是一种用于布局和界面的全新语言。62它是 HTML 和 CSS 的完整替代方案。它是最常用的非核心 Elm 库,我们几乎在所有项目中都使用它。63
它将之前提到的最小惊讶原则应用于网页设计。你的意图在设计中清晰地表达出来,这在使用 CSS 时非常罕见,使设计过程变得轻松有趣且快捷。
例如,假设我们有一个蓝色框,我们想要将一个宽度和高度未知的元素(水平和垂直)居中,其中包含文本“I'm centered!🎉”:
在 HTML/CSS 中使用 Flexbox 的一个可能的解决方案是:64
<style>
.parent {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
</style>
<div class="parent">
<div>I'm centered! 🎉</div>
</div>
要在 Elm-UI 中获得相同的结果,您可以编写:65
el [centerX, centerY] <| text "I'm centered! 🎉"
请注意,此解决方案不太冗长,并且我们的意图、centerX
和centerY
都表述清晰,并直接应用于包含文本的元素,而不是其父元素。
它是如何工作的?让我们简单看看 Elm-UI 在幕后做了什么。
首先,Elm 没有单独的模板语言,Elm 的模板语言是Elm。66
例如,以下这段使用标准 Elm HTML 库(而非 Elm-UI)的 Elm 代码块:67
div [] [ text "I'm centered! 🎉" ]
生成此 HTML
<div>I'm centered! 🎉</div>
这两种表示之间存在一一映射。所以这里没什么特别的。
现在,使用Elm-UI,当我们编写:
el [centerX, centerY] <| text "I'm centered! 🎉"
Elm-UI以编程方式生成以下 HTML(加上一堆 CSS,为简洁起见省略):68
<div class="hc ah cx av cy s e wc">
<div class="s t wf hf">
I'm centered! 🎉
</div>
</div>
Elm-UI为我们完成了所有繁重的工作,添加了样式和元素以确保页面看起来完全符合我们的预期。69
使用Elm-UI,一对一映射不再是 Elm 代码和 HTML/CSS 之间的映射,而是 Elm 代码和布局之间的映射,从而使结果可预测。
Elm-UI将 CSS/HTML 视为字节码,就像Elm将 Javascript 视为字节码一样。70
在花了数年时间学习各种 CSS 技巧之后,感觉就像呼吸了一口新鲜空气。71
15. 可读性和 Elm 语法
函数式语言是声明式的,它让我们专注于写什么,而不是如何写。隐藏“如何”的细节使代码更易于阅读和理解,代码的“意图”变得透明。
在 Elm 社区中,编写可读性代码被视为重中之重。这一点很重要,因为作为开发人员,我们花在阅读代码上的时间比编写代码的时间要多。
Elm 采用机器学习风格的语法,这与 Java、JavaScript 和其他流行语言的 C 风格语法截然不同。Elm的选择牺牲了其熟悉度,使其更具便利性和适用性,72因为有时熟悉度会掩盖复杂性。73
我们喜欢这种语法的原因在于它的简洁性。与 C 语言语法相比,大多数括号、关键字和标点符号都不是必需的。
例如,我们这样定义add
一个将两个数字相加的函数:
add a b = a + b
管道操作员
管道运算符也存在于 Elixir、F# 和(可能)JavaScript 等其他语言中,它74可以帮助处理多个括号或数据流。让我们考虑一下这个调用四个嵌套函数的代码片段:
f ( g ( h ( i 7 ) ) )
它可以用管道运算符重写为:
f <| g <| h <| i 7
这种风格的好处是我们不再需要右括号。
使用反向管道运算符,我们可以用第二种风格重写它,以使数据流明确:
7
|> i
|> h
|> g
|> f
模式匹配
模式匹配的一个例子是case .. of
允许我们根据自定义类型变体进行分支。例如:
type TrafficLight = Green | Yellow | Red -- Or Blue | Yellow | Red in Japan 🚦
hexColor trafficLight =
case trafficLight of
Green -> "00ff00"
Yellow -> "ffff00"
Red -> "ff0000"
如果我们想为颜色类型添加第四种变体,编译器会强制我们将该情况添加到此构造中 - 这非常有用。
分隔符前置列表
Elm 的格式化程序Elm-Format会将分隔符(逗号)放在行首,而不是行尾。例如:
trafficLights =
[ Green
, Yellow
, Red
]
这种格式化样式有几个好处,例如,代码看起来更有条理(所有逗号都对齐),并且合并冲突更少。75
16.重构
Elm编译器就像一个助手,并且代码一旦编译完成通常就会起作用,这使得重构成为一种愉快的体验。
使重构变得容易的另一个因素是,作为一种纯函数式语言,我们编写代码的顺序并不重要。76
例如,在 Elm 中,我们可以写:77
b = a + 2
a = 1
即使这两行代码看起来顺序不对,在 Elm 中也能正常工作,但在命令式语言中,同样的代码会抛出错误“ b未定义” 。78
重构变得更简单,因为我们可以毫不费力地重新排列代码片段。
在我们最大的项目中,我们正处于第三次重大重构迭代的中期,其中有些代码仍在第一次迭代中,还有一些代码处于第二次迭代中。所有代码都能很好地协同工作。我们现在正在逐步将所有代码迁移到第三次迭代。在 Elm 中,你无需从一开始就把事情做好。
17. 招聘
招聘非主流语言的开发人员有一些缺点。例如,能够流利掌握该语言的开发人员并不多。
然而,学习 Elm 是一个快速的过程。正如前面所说,我们的经验是,只需几周时间就能达到高效学习的效果,而掌握它则需要几个月的时间。
因此,我们不应该问“有多少申请人了解 X?”,而应该问:“了解 X 能告诉我们关于申请人的什么信息?”,而应该关注那些有热情、有能力适应和学习新概念的工程师。
此外,招聘一名技术含量较低的员工可以提升您作为创新型公司的品牌形象,让您成为最酷的孩子。
18. 性能快,资源少
Elm 编译器可以针对 Elm 是纯函数式语言这一特性应用多项优化。这带来了诸多好处,包括:
- Elm 应用程序的性能位居前列。Elm 内部使用了虚拟 DOM 的概念,类似于 React。Elm 虚拟 DOM 的速度与 Svelte 相当,但 Svelte 使用不同的机制来更新 DOM。79
- Elm 编译器生成的资源比其他框架更小。为了实现这一目标,我们进行了多项优化,其中一项就是精确到单个函数的死代码消除,该函数可以在整个生态系统中运行。如果您导入一个大型包,并且只使用其中的一个函数,编译器将确保生成的代码中只包含该函数。80
Elm 编译器本身也很快。我们更大的代码库包含约 66,500 行 Elm 代码,它增量编译只需 0.3 秒,从头编译则只需 2.5 秒。81
19.内容驱动的静态网站
Elm 并不适合构建主要由内容驱动的静态网站。在这种情况下,旧的服务器端渲染网站可能是更好的选择。
另一方面,如果你喜欢上了 Elm,就很难再回到纯 JavaScript/HTML/CSS 了,所以我们尝试用 Elm 来创建静态网站。市面上有不少静态网站生成工具。我们使用了Elm-Starter 82这个库,它可以将 Elm 网站转换为服务器端渲染的 PWA,并且可安装、离线运行,并且无需 JavaScript 即可运行。
这些特性有助于获得良好的 Lighthouse 分数和良好的搜索引擎排名 (SEO)。
我们不喜欢 Elm 的地方
1. 非主流
如果很多人相信某件事,它就一定是真的吗?83
所有非主流技术都存在一些共同的问题。如果它们不是主流,推广起来就会很困难,尤其是在决策者使用“迎合流行”这种谬论的情况下。例如,“X 技术比 Y 技术更好,因为它在 GitHub 上拥有更多 Stars” 。84
我们认为,所有论点都应具体情况具体分析。有时,非主流确实会产生相关影响(参见“重新发明轮子”);有时,它的含义比表面看起来的更微妙(参见“招聘”);大多数情况下,非主流与优秀品质无关。85
如果您仍然需要放心,请考虑一下,无论 Elm 多么受欢迎,许多公司都在使用它,其中包括微软、IBM、eBay、福特、亚马逊、Zalando 和 Thoughtbot 等几家大公司。86
2. 缺乏可谷歌搜索的资源
在 Google 中询问 Elm 问题并不总是能得到好的结果。
大多数 Elm 公开对话都发生在 Elm Slack 频道87中,Google 机器人看不到该频道。
另外,另一个后果是 Stack Overflow 网站上的资料数量有限。但这个事实并不总是像看起来那么糟糕。
Stack Overflow 有时会因为信息未更新或仍然“过时”而受到影响,这使得信息变得毫无用处,有时甚至是有害的。
在 Elm 的 Slack 频道中,信息始终新鲜,社区也非常支持。只是不太显眼,所以加入 Slack 频道需要付出额外的努力。
其他时候,资源分散,SEO 效果不佳。例如,这份有价值的提示列表很少出现在 Google 搜索结果中。88
3. 重新发明轮子
由于 Elm 并非主流语言,有时需要重新发明一些原本可以通过采用其他技术获得的功能。例如,我们受react-jsonschema-form的启发,编写了一个用于创建 HTML 表单的库。89
这个问题在过去更为重要,因为如今 Elm 包的数量涵盖了广泛的主题。
4. 思维转变
对于只使用面向对象风格进行编程的开发人员来说,纯函数式编程可能会令人费解和畏惧。
“面对改变主意和证明没有必要改变主意之间的选择,几乎每个人都忙于证明。”
- 约翰·肯尼斯·加尔布雷斯
有些人认为这是一种好处,因为它可以让你走出舒适区,让你对编程有不同的看法。
但对于其他人来说,这是一种负担,可能会阻碍团队采用 Elm。
5. 仍然需要一些 JavaScript 和 CSS
理想情况下,我们只需使用 Elm 语言编写即可构建应用程序。但是,如果需要使用未转换为 Elm 的第三方库,我们仍然需要使用 JavaScript。这样做意味着再次陷入可能出现运行时错误的境地。
Elm 提供了三种与外部库交互的方式:标志、端口和自定义元素。90所有这些都需要您编写一些 JavaScript。
例如,在我们的案例中,我们必须使用 JavaScript 库来处理付款。
使用Elm-UI库时,所需的 CSS 代码有限。在我们的应用程序中,我们有一些 CSS 代码片段,主要是为了支持 IE11 而做的一些小技巧。
与此相关,Elm 可能不太适合需要与第三方 JavaScript 库大量集成的短期项目。
结论
我们讨论了使用 Elm 或纯函数式语言进行编程的一些好处,并讨论了其中的主要问题。
对于我们来说,与问题相比,好处是巨大的,这就是我们对所做的选择感到满意的原因。
“然后我开始学习函数式编程......编程又变得有趣了!”
- 鲁纳尔·比亚纳松91
这些技术优势带来的结果是极大的放松感,不再感到孤单,也不用害怕破坏东西。
与 Elm 之前的体验相比,现在的编码更加愉快、高效,并且不会出现运行时错误!🎉
其他证词
听到不同的观点总是好的。您可以在这里找到其他公司采用 Elm 的案例:
- 贝尔罗伊榆树
- Elm 在 NoRedInk
- 榆树辉煌
- Humio 的 Elm
- 微软的 Elm
- 吉兹拉的榆树
- 福特榆树
- 文化放大器中的榆树
- Thoughtbot 的 Elm
- Diesdas Digital 的 Elm
- Talenteca 的 Elm
- 其他公司的 Elm
笔记
-
Elm是一种编译型、不可变、强静态类型和纯函数式编程语言,可编译为 JavaScript。JavaScript 是一种即时编译、弱动态类型、多范式编程语言。要了解有关 Elm 的更多信息,最好先阅读官方指南。如果您熟悉 JavaScript,可以查看From JavaScript?,其中简要比较了两种语言的语法。Elm 语言(包括编译器和核心库)由 Evan Czaplicki 在一个小型核心开发团队的支持下设计和开发。Evan 在争议或争论中保留最终决定权。这种设置是许多语言最初几年的常见做法,可确保连贯的愿景和精心设计的 API。↩
-
James Kolce 撰写的《Elm 架构简介及如何构建我们的第一个应用程序》是2017 年 在CSS-Tricks网站上发布的三部分系列文章的第二部分。↩
-
Elm 网站是Elm 入门信息和文档的主要来源。网站上关于运行时错误的一段话是:“ Elm 使用类型推断来检测极端情况并提供友好的提示。NoRedInk 大约两年前转向了 Elm,在编写了超过 25 万行代码之后,他们仍然没有在生产环境中费力地修复令人困惑的运行时异常。 ” ↩
-
在前端,运行时错误是指发生在浏览器中的错误。这些错误可能会完全停止网站的功能,而您作为创建者甚至可能对此毫不知情,因为它们发生在其他人的设备上。有些工具允许您在发生这些错误时收到通知。这些错误通常是 JavaScript 错误,例如,尝试访问 null 或 undefined 的值 。↩
-
尽管现在已经快两年了,但关于乐天使用 Elm 的更多细节,可以在我在2019 年奥斯陆 Elm Day会议上发表的演讲“Elm 在大型公司中”中找到。↩
-
这段话摘自约翰·卡马克 (John Carmack)的一篇深入文章,探讨了使用 C++ 进行函数式编程的价值。约翰·卡马克是一位独立的人工智能研究员、Oculus VR的首席技术官顾问,同时也是Armadillo Aerospace和Id Software的创始人。在 Id Software,他曾担任《指挥官基恩》(Commander Keen)、《德军总部 3D》(Wolfenstein 3D)、 《毁灭战士》(Doom)和《雷神之锤》(Quake)等游戏的首席程序员。↩
-
Elm 运行时系统是代码中负责指导应用程序运行的部分。例如,它负责计算如何渲染 HTML、如何发送 HTTP 请求、如何将用户的点击重定向回 Elm 代码等等 。↩
-
elm diff
Elm 包管理器允许您通过运行类似 的命令来检查任何已发布的 Elm 包中的差异(添加/删除/修改的功能)。Evan Czaplicki 在视频《Convergent Evolution》elm diff elm/json 1.0.0 1.1.2
中提供了一个此功能的实例。↩ -
YouTube 上有很多关于这个话题的搞笑视频。其中最受欢迎的可能是Kyle Simpsons 的 《What the... JavaScript?》↩
-
请注意,Elm 要求在整数和浮点数之间进行显式转换。有些人觉得这很麻烦。为了提高 Elm 编译器的速度,这种显式转换是必要的。您可以在“隐式类型转换”一文中阅读更多相关信息。↩
-
Elm 中的类型注解并非必需,但添加它们被认为是一种良好实践。它有助于编译器提供更精确的错误信息,并检测错误,例如在无限类型的情况下。↩
-
Dillon Kearns 的文章《TypeScript 的盲点》列举了 TypeScript 的几个弱点 。↩
-
类型保护是 TypeScript 表达式,它执行运行时检查以区分自定义类型。例如:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersfunction isFish(pet: Fish | Bird): pet is Fish { return (pet as Fish).swim !== undefined; } 之后可以编写如下代码:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersif (isFish(pet)) { pet.swim(); } else { pet.fly(); }
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characterscase pet of Fish fish -> fish.swim Bird bird -> bird.fly
-
Richard Feldman 在 2016 年 Elm-conf 上的同名演讲中很好地解释了使不可能状态变得不可能的概念。↩
-
Elm 中的这些编码模式被认为是良好的实践。让不可能的状态变得不可能就是其中之一 。↩
-
A
type alias
是类型的简称 。↩ -
类型
Maybe
是 Elm 处理缺失值的方式,因为null或undefined不存在。 Maybe 定义为 -
正如 Evan Czaplicki 所说,“自定义类型是 Elm 中最重要的特性” 。↩
-
“一个 JavaScript原则”的核心在于移除版本控制并始终保持向后兼容。这一原则,加上十天的设计和二十五年的向后兼容,不可避免地积累了大量不同的实现方式。例如,定义一个函数可以用几种不同的方式实现。↩
-
最新的重大变化是2016 年 5 月的“告别函数式响应式编程”。以下是更新的概览图。↩
-
中缀运算符已被移除,因为它创建了一些花哨的非标准运算符,导致代码难以理解。这是一篇关于移除中缀运算符原因的更详细的解释。↩
-
显示Elm 软件包所有更新的 feed是衡量 Elm 库活跃度的好方法。同样,此活动不应与语言的采用率混淆。稳定的库通常不会经常更新。您很少会在 feed 中看到核心库。有时,有人抱怨编译器和核心模块的工作缺乏透明度。Elm在某些方面并不遵循标准的“开源”文化,并且大多数核心工作都在私有仓库中完成。如果您有兴趣支持该语言,最好的方式是参与其社区并为生态系统做出贡献。↩
-
从0.18版本到0.19版本的更新主要是编译器的优化 。↩
-
函数式编程是一种通过应用和组合函数来构建程序的编程范式。它是一种声明式编程范式,基于一系列仅在参数和返回值方面相互依赖的函数。它看起来像这样:
-
从面向对象到函数式编程,Richard Feldman 谈论范式转变 。↩
-
出于好奇,JavaScript 中最接近 Elm 函数的等价函数
-
像 中这样将较少数量的参数传递给函数
add 10
被称为偏应用,它为一些有趣的编码技巧打开了大门。如果你仔细观察,就会发现它就像依赖注入,只不过是被注入的部分。Scott Wlaschin 在他的演讲《函数式设计模式》10
中解释了这种模式以及其他一些模式。↩ -
例如Elm-Browser中的sandbox、element、document、application;Elm-HTTP中的get、post、request;Elm-Playground中的picture、animation、game;等等 ↩
-
Chris Krycho 在文章《复杂性的渐进披露和类型化函数式程序设计语言》中讨论了这一点,而Evan Czaplicki 在演讲《让我们成为主流》中解释了这一概念。↩
-
由于 Elm 语言的设计以用户为中心,所以其他特性根本没有添加。例如类型类。正如 Evan Czaplicki 在《让我们成为主流!Elm 中以用户为中心的设计》一文中所解释的那样:“如果你要放弃简单性,最好是有充分理由的。” ↩
-
您可以使用elm reactor开始尝试 Elm ,这是一个内置于 Elm 编译器的 Web 服务器,每次刷新浏览器时都会自动重新编译代码。对于更高级的编码,可以使用Elm-Live,这是一个支持自定义 HTML 和热重载的 Web 服务器。我们团队就是用的这个。您也可以使用常用的框架,例如
Elm-Live
Webpack 或 Parcel。↩ -
编译器助手 (Compiler as Assistant)的概念与 Elm 本身同时诞生。在本文中,Evan Czaplicki 解释了 0.16 版本在这方面的进一步改进 。↩
-
Dillon Kearns 编写的 Elm 程序,以微小的步伐加快移动速度↩
-
Kevin Yank在本视频中解释了什么是编译器驱动开发。Louis Pilfold 解释了编译器如何在软件开发过程中充当助手,他提到了 BEAM,一种受 Elm 启发的语言。这是Evan Czaplicki 在一次演讲中谈到 编译器驱动开发的另一个例子。↩
-
Idris可能是最适合类型驱动开发的语言。Idris 具有“ holes”特性,而 Elm 可以使用Debug.todo。↩
-
这种说法可能起源于 Haskell,但也适用于 Elm。↩
-
Elm 将“最佳实践”作为默认做法的一个例子是关于变量遮蔽(同一个变量名以模糊的方式定义两次)。虽然大多数 linters 在出现变量遮蔽时会发出警告,但 Elm 编译器会生成错误并停止编译,直到变量遮蔽问题得到解决。更多关于此内容,请参阅“变量遮蔽”一文。↩
-
“这应该能给每一条错误信息带来启发”,John Carmack 在评论 Elm 错误信息时说道 ↩
-
Swift语言的设计者之一 Joe Groff 提到,SwiftUI 受到了 Elm 和 React 的启发。↩
-
ClojureScript、ReScript和PureScript是三种具有与 Elm 类似概念的语言 。↩
-
关于 Elm 架构的详细信息,请参阅官方 Elm 指南。Elm 架构是 Elm 应用构建的主要方式。此外,Elm 架构也存在不同的变体。Ryan Haskell -Glatz 开发的Elm-spa是一款工具,可帮助创建单页应用,并在 Elm 架构之上创建额外的抽象。Rémi Lefèvre使用Effect 模式构建了RealWorld 示例应用。↩
-
Elm 架构基于类似 React 的单向数据流(又称单向数据绑定),这与Angular、Vue 和 Svelte 等框架的双向数据流(又称双向数据绑定)形成对比(在 Svelte 中可以禁用双向绑定)。双向数据绑定存在一些问题。例如,视图和模型之间的多对多依赖关系可能会造成级联更新的无限循环。另一个问题是缺乏对变更检测机制的控制。这是一种不易控制的隐式行为。单向数据流往往更可预测 。↩
-
当我们编写 Elm 代码时,我们的代码 100% 是纯粹的,因此没有副作用。但是如果没有副作用,我们的应用程序将只是一个无聊而沉默的空白屏幕。Elm运行时系统是代码中负责处理副作用的部分。在我们的代码中,我们只需请求执行这些副作用,然后等待结果。副作用的例子包括 HTTP 请求或 DOM 修改。我们如何在保持纯粹的同时实现副作用?在 Elm 中,有两种方法。例如,对于 HTTP 请求之类的操作,有命令(
Cmd
),它们是以数据形式存在的指令,我们将其作为请求发送到 Elm 运行时系统。对于更改 DOM,实现副作用的方法是将整个世界的状态作为参数并返回其新版本。因此,我们可以在保持纯粹的情况下改变世界(副作用)。在我们的例子中,世界是模型,而执行此操作的函数是更新函数:(有关更多详细信息,update: Msg -> Model -> (Model, Cmd msg)
请参阅Elm 架构)。视频“什么是 IO monad?” Alexey Kutepov 从总体上解释了这一概念 。↩ -
如果您熟悉游戏开发,您会发现Elm 架构与游戏循环之间存在相似之处。主要区别在于,游戏通常不会等待某些事情发生,而是循环会一直运行。当我们使用 Elm 开发游戏时,我们会使用onAnimationFrame执行相同的操作,以便循环以每秒 60 次的正常速度持续运行 。↩
-
有关Elm 调试器的更多信息,请参阅《完美的 Bug 报告》 ↩
-
Elm 调试器通常在生产环境中发布的应用程序中被禁用,但您可以在elmjapan.org上找到它的示例,该调试器在该网站上保持活跃以用于教育目的 。↩
-
Bret Victor 是一位界面设计师、计算机科学家和电气工程师,以其关于未来科技的演讲而闻名。在他的演讲《原则上的发明》中,Victor 阐述了他关于修复我们开发软件的根本缺陷的愿景。简而言之,他的愿景是“创造者需要与他们正在创造的东西建立即时联系”。更多详情,请参阅James Somers 的 《即将到来的软件末日》。↩
-
Elm 软件包管理器的增强镜像按受欢迎程度列出了软件包。如果排除核心库,排名前 5 的软件包分别是Elm - UI | Elm-JSON-Decode-Pipeline | Elm-CSS | elm - color | Remotedata。↩
-
使用 CSS至少有三种不同的方法可以将元素居中,甚至更多。你可以使用
Pre-Flexbox
style (Codepen上的示例)、 ( CodepenFlexbox
上的示例)或( Codepen上的示例)。使用flexbox的版本可能更简单 。↩Grid
-
Evan Czaplicki在视频Convergent Evolution中提到Elm 的模板语言就是 Elm,并将 Elm 与 React 进行了比较 。↩
-
请注意,它
div
是一个接受两个列表的函数,一个用于属性,一个用于子元素。text
也是一个函数。查看这些函数的类型签名可能有助于更好地理解: -
Elm-UI的优势在复杂的布局中比在一个简单的示例中更加明显。此外,Elm-UI还能确保我们的 HTML 代码有效且易于理解。例如,它会强制我们在图片中添加描述,并阻止我们在 HTML 元素中添加子元素
img
。该img
函数使用标准 HTML Elm 库定义如下:img
时,由于函数本身的定义,我们无法向 HTML 元素添加子元素image
:image
不接受子项列表,而只接受包含src
和 的参数description
。↩ -
在迁移到Elm-UI之前,我们是css-tricks.com的忠实用户,它是一个优秀的 CSS 技巧和 Web 开发相关信息来源。讽刺的是,正是在这个网站上,我们了解到了 Elm 的存在,并最终转向使用Elm-UI,最终css-tricks.com变得不再那么重要了 。↩
-
Evan Czaplicki 在整个视频《Convergent Evolution》中解释了使用 ML 风格语法的决定。↩
-
Rich Hickey在他的演讲《我们到了吗?》 (第 11 分钟)中提到了熟悉度掩盖复杂性的观点,他主张重新审视 OOP 的基本原则 。↩
-
请参阅“逗号前置列表的案例”以了解更多关于逗号前置列表方法的优势。这种方法适用于任何列表分隔符。例如,在 CSS 中我们可以这样写:
-
Elm 也没有提升,这是一种 JavaScript 机制,其中变量和函数声明在编译阶段被放入内存中,给人的印象是它们在代码执行之前被移动到其范围的顶部 。↩
-
Elm 代码示例可在此处获取。另一种顺序很重要的情况是当可变性起作用时,例如
=
符号 就失去了它的数学意义?a = a + 2
在数学上是不可能的。但如果数据不可变,它仍然成立,因为你只能写newA = a + 2
。更多关于这方面的内容,请参阅“糟糕递归的提示”一文。↩ -
Elm 和 Svelte 的性能不相上下,这可以从JavaScript 框架基准测试中得到验证。这个帖子有一个关于 Web 框架性能的有趣讨论 。↩
-
在 JavaScript 中,相当于“死代码消除”的方法被称为“摇树优化”,它通常以模块为单位进行,而不是单个函数。其他优化也使得 Elm 的资源占用较少。我们最大的应用程序约有 66,500 行 Elm 代码,压缩后大小为 188kb,其中包括 SVG 资源、额外的 JavaScript 代码以及多种语言的翻译 。↩
-
这些是在 Elm中生成静态站点最常用的工具:Elm-Pages | ElmStatic | Elm- Starter。↩
-
诉诸大众,或称“诉诸大众论证”,是一种谬论,认为某事必定是真的,因为许多人或大多数人相信它。插图由 Elm 和Elm-Playground制作,源代码在此。有趣的是,Elm 最近出现在《纽约时报》的填字游戏中。这是否意味着 Elm 现在已经成为主流编程语言了? ↩
-
Evan Czaplicki 在“什么是成功? ”演讲中讨论了这个话题 。↩
-
例如,面向对象范式是否因为其固有的优良品质及其处理复杂问题的能力而成为主流?这是偶然的吗?(正如 Richard Feldman 在他的视频《为什么函数式编程不是常态? 》中所说)是因为它是一种较差的范式(正如 Brian Will 在《面向对象编程很糟糕》中强调的那样),但微软及其创建和推广了它? ↩
-
Richard Feldman 在视频《黑暗中构建 UI(又名 Elm 编程)》和Elm 公司列表中提到了其中一些公司。↩
-
Elm Slack 频道拥有约 20,000 名成员。Elm 社区使用的另一个平台是discourse.elm- lang.org。Reddit网站上还有一个频道,但内容往往不太愉快,因此很少有 Elm 开发者在那里发表评论 。↩
-
Elm 提供了多种与 JavaScript 通信的方法。以下是通过一些示例介绍 JavaScript 互操作性。↩
-
Rúnar Bjarnason是函数式编程的倡导者。他是 Scala“红皮书”的合著者,也是编程语言 Unison 的创始人。Unison 被称为“来自未来的友好编程语言”,与 Elm 有相似之处,因为它们都受到了 Haskell 的启发,正如视频“Unison 编程语言简介”中所解释的那样。↩