前端N面-elm-lang

elm是什么?

他是一个著名的外卖平台

elm是一门开发语言, 是一门函数式语言, 但是不同于其他函数式语言, 因为这门语言只聚焦在前端开发方面, 也就是说,最后它能够编译成javascript.
elm是一门很年轻的语言, Evan Czaplicki 再2012年才把它设计出来, 而且是在他的毕业设计上(大雾)…
但是尽管elm非常年轻, 但显然它再前端圈里也是有这举足轻重的地位的,至于为什么, 我们稍后再说

为什么我们需要了解elm

最近两年, 因为react 和redux 的函数式编程思想, 导致前端的函数式编程大热. 我们不谈函数式编程真正能给前端带来什么,仅仅说函数式编程作为一种完全不同编程范式,
再学习这种编程思想的同时, 一定能过扩宽我们的编程思路, 一些偏函数式的库的出发点和基石, 毕竟再js里函数也是一等公民
如今前端框架吸收函数式编程以及强类型语言优点的形势下, 甚至可能会开启我们对前端的重新认识

elm有什么特点?

  • 强类型 (static type)
  • 函数式语言 (fpr)
  • 一次编译, (处处运行?) No Runtime Exceptions
  • 数据不可变 (immutable)
  • 虚拟dom (Great Performance)

语法特点

elm的语法是来源于haskell的,虽然不是haskell的方言, 但还是吸收了很多形式,一下不会完全展开,
直挑一些后面实例用的到的一些语法做介绍

list

list和js里的array类似, 但是每一个值都必须是相同的类型

1
2
a = [1, 2, 3, 4] -- correct 
a = [1, "2", 3, 4] -- wrong

Records

Record 和js里的Object很像, 他的定义方法是:

1
2
3
4
5
6
7
john = { first = "John", last = "Hobson", age = 25 }

.last john // "Hobson" .last 是一个records访问器

john.last // "Hobson" 直接访问last

{john | last = 'blabla' } // { first = "John", last = "blabla", age = 25 }

Function

1
2
3
add : Int -> Int -> Int // 类型定义
add a b = a + b // function add (a,b) {return a + b}
add 1 2 // 3

这里就可以发现elm的函数式特点, 再我们定义一个函数的时候, 如果有多个参数, 参数和参数之间的定义使用->来分割的,
其实这也表名了这个函数是被科里化的,函数的执行可以被分解成以下两部

1
2
temp = add 1 // init -> init
temp 2 // 3

再elm里所有的函数都是被科里化的, 科里化的好处? 打个比方

1
2
3
4
5
6
7
8
9
10
11
repeat(10, 'a') // 'aaaaaaaaaa'
// 科里化后
repeat(10)('a') // 'aaaaaaaaaa'

// 没有科里化
repeat10(str) {
return repeat(10, str);
}

// 科里化的
repeat10 = repeat(10)

pipeline

因为是函数式语言, 会出现多层的函数嵌套,比如再js中

1
2
3
4
5
6
7
8
9
10
function test(param) {
let a = fa(param);
let b = fb(a);
let c = fc(c);
return c
}
// or
function test(param) {
return fc(fb(fa(param)));
}

你也可以通过lodash 或者 ramda 这样的fp工具库来做链式调用, 这里就不一一展开, 感兴趣的可以看对应的文档

而再elm里, 就有专门的语法来处理这种情况

1
2
3
4
5
param = 
param
|>fa
|>fb
|>fc

一个字, 优雅

type

elm是一个强类型语言, 所以显然我们需要对类型做定义, 这里重点讲一下elm里面的union type, 因为这是elm的重要核心

####普通类型

1
2
3
User : String 
User tom = 'tom'
User num = 1 // error

####Union Type
Union type 用来表示一组可能tag集合,比如Hr, Admin 都是User的一个variants

1
type User = Hr String | Admin String

而Union type 也可以被解构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
getUserLabel: User -> String
getUserLabel user =
case user of
Hr name ->
name
Admin name ->
role ++ name

```elm
注意`case user of`下面的代码, 是不是和js里的`switch`很像? 所以union type很多情况都被当做是一种条件判断

```elm
type User = Guest String | Admin String Int
getWelcomeMessage: User -> String
getWelcomeMessage user =
case user of
Guest _->
"guest login!"
Admin _ _ ->
"admin login! welcome back!"

示例

ok, 学完上面的几个知识点, 我们开始看一个简单的示例,来了解一个elm程序是如何构成的吧!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
module Main exposing (..)

import Browser
import Html exposing (Html, button, div, text, span, input)
import Html.Events exposing (onClick, onInput)

-- MAIN
main =
Browser.sandbox { init = init, update = update, view = view }

-- MODEL
type alias Model = { count: Int }

init : Model
init = { count = 1 }

-- UPDATE
type Msg = Increment | Decrement

update : Msg -> Model -> Model
update msg model =
case msg of
Increment ->
{ model | count = model.count + 1 }

Decrement ->
{ model | count = model.count + 1 }

-- VIEW
view : Model -> Html Msg
view model =
div []
[ button [ onClick Decrement ] [ text "-" ]
, div [] [ text (String.fromInt model.count) ]
, button [ onClick Increment ] [ text "+" ]
]

这里view是一个函数,它使用当前的modell来渲染dom, 使用的是类似jsx的函数调用, 这是一个纯的标准的函数式组件

update函数接受 Msg, 根据不同variants来对model做更新, 这和redux的reducer是非常类似的, 不同的variants就类似于不同的actionType
状态被一个纯函数来更新,并返回一个新的状态

init定义了当前的初识状态

最后形成一个数据流 init -> view -> update -> init 这样的一个单向数据流

上面就是一个简单的例子, 能够很好的提现elm开发的理念, 和它的代码解构, 先看一下上面的几行注释,
能明显看出来, 代码把程序分成几个部分:

  • 数据 (model the state of your application)
  • 视图 (view a way to turn your state into HTML)
  • 更新 (update a way to update your state based on messages)

elm管这种结构叫做 elm architecture,
elm architecture

是不是很像redux的逻辑?实际上并不是elm借鉴了redux, 而是redux借鉴了elm! 同时也顺便激发了dva

I read about Elm before Redux but didn’t get it fully, later realized it was important influence. – Dan Abrawov

Lightweight front-end framework based on redux, redux-saga and react-router. (Inspired by elm and choo) – Dva

Side Effects

刚才一直在说elm都是纯函数, 但是在实际开发中, 是不可能这么完美的, 我们有很多场景都是不纯粹的, 比如io, network, 和其他js代码交互 等等, elm使用port, 和他本身的rentime封装了这层逻辑,
这样隔离了这些有副作用的逻辑
img

结语

实际上, 目前很少有人在再生产环境使用elm, 一方面是因为它的社区不够强大, 还有一方面是文档不够完善, 再有就是比较陡峭的学习成本, 往往让人望而生畏

不过如果真的要入手学习的话, 作者写的TODOMVC,看明白就很不错啦
如果你敢再生产环境使用, 那你是真正的勇士(其实还真有)

推荐阅读

elm官网

functional-frontend-architecture, 如何用纯js撸一个elm architecture

更详细的教程