Elm入門實踐(二)——類型篇

記得Facebook曾經在一次社區活動上說過,隨着他們愈來愈多地使用Javascript,很快就面臨了曾經在PHP上遇到的問題:這東西究竟是啥?javascript

動態語言就像把雙刃劍,你能夠愛死它的靈活性,也可能由於一個小的疏忽而損失慘重。Elm選擇了靜態強類型,這一般也是多數函數式語言的選擇,沒有了OO語言中的概念,強大的類型系統負責解決一切「這是什麼?」的問題html

類型註解

也能夠叫作類型簽名,Elm 使用冒號:來註明類型,在Hello world的基礎上,讓咱們分別定義一個變量和函數,而且註明類型java

import Html exposing (..)
import Html.Attributes exposing (..)

elm : String
elm = "elm"

sayHello : String -> String
sayHello name = 
  "Hello, " ++ name

main =
  div [class "hello"] 
    [ span [] [text (sayHello elm)]
    ]

嘗試將elm的值"elm"改成數字,看看會發生什麼?編程

Detected errors in 1 module.


-- TYPE MISMATCH ---------------------------------------------------------------

The type annotation for `elm` does not match its definition.

5| elm : String
         ^^^^^^
The type annotation is saying:

    String

But I am inferring that the definition has this type:

    number

編譯器發現了錯誤,而且可以定位到具體的行數。redux

若是不聲明類型呢?若是註釋掉類型註解數組

import Html exposing (..)
import Html.Attributes exposing (..)

--elm : String
elm = 6

--sayHello : String -> String
sayHello name = 
  "Hello, " ++ name

main =
  div [class "hello"] 
    [ span [] [text (sayHello elm)]
    ]

從新編譯,仍是會報錯,只是錯誤信息變了,此次是第14行:安全

Detected errors in 1 module.


-- TYPE MISMATCH ---------------------------------------------------------------

The argument to function `sayHello` is causing a mismatch.

14|                      sayHello elm)
                                  ^^^
Function `sayHello` is expecting the argument to be:

    String

But it is:

    number

即便沒有顯式的類型註解,Elm的類型推導系統也會發揮做用,此處經過類型推導認爲sayHello函數的參數應該是字符串,可是傳入了數字,所以報錯。數據結構

對比兩次不一樣的錯誤提示能夠看出,類型註解能讓編譯器更準確地發現和定位錯誤。隨着學習的深刻你會慢慢喜歡上類型系統帶來的安全感:若是編譯失敗,明確的提示能幫助你快速定位問題。而只要編譯經過,程序就必定能運行。架構

基本類型和List

基本類型

基本類型和多數語言是相似的,無非就是String, Char, Bool Int, Float,能夠參考官網的literals。須要注意在Elm中,String必須用雙引號,單引號是用來表示Char的,字符串單引號黨須要適應一下。編輯器

List

嚴格來講List並非類型,它的類型是List a,其中的a被稱做類型變量,這是由於List做爲容器,它能夠裝String,Int,或者什麼都不裝,所以類型必須是動態的:

> [ "Alice", "Bob" ]
[ "Alice", "Bob" ] : List String

> [ 1.0, 8.6, 42.1 ]
[ 1.0, 8.6, 42.1 ] : List Float

> []
[] : List a

關於類型變量後面會繼續討論,在這裏咱們只須要記住一點:List不是類型,相似List String這樣的纔是

因爲參數只有一個,Elm的List只能容納單一類型的元素,和Javascript來者不拒的List不一樣,下面這樣的是會被編譯器發現並報錯的:

list = [1, "a"]

類型別名

類型別名用於組合或複用已知的類型,好比

type alias Name = String
type alias Age = Int
  
type alias User = {name: Name, age: Age}

user : User
user =
  { name = "Zhang zhe", age = 89 }
  
setUserName : String -> User -> User
setUserName name user = {user | name = name}

它不只可讓基本類型具有業務語義,還能夠爲複雜的數據結構組合出合適的、語義化的類型。沒有別名的話,setUserName的類型簽名就得寫成下面這樣……一坨:

setUserName : String -> {name: String, age: Int} -> {name: String, age: Int}

Union Types

Union type 是Elm類型系統中最重要的部分之一,它用來表示一組可能的值,每一個值叫作一個Tag,以下:

type Bool = True | False

type User = Anonymos | Authed

其中TrueFalseAnonymosAuthed 都是Tag名(注意Tag不是Type)。看起來很像枚舉?不僅這樣,Union type強大的地方在於:Tag能夠攜帶一組已知類型。上面的代碼咱們雖然能區分兩類用戶,但並不能獲取認證用戶的名稱,這時候就能夠用已知類型結合Tag表達:

type User = Anonymos | Authed String

當咱們建立Union type的時候,實際上爲每一個Tag都建立了相應的值構造器

> type User = Anonymous | Authed String

> Anonymous
Anonymous : User

> Authed
Authed : String -> User

不帶其它信息的Anonymous能夠直接做爲使用(想一想TrueFalse),而帶有已知類型的Authed其實是一個函數,它接受String,返回User類型:

users : List User
users = [
  Anonymous, 
  Authed "Kpax"]

在Haskell中沒有Tag的叫法,類似的東西就叫值構造器(value constructor),直接的代表了它的用途:構建屬於該類型的值

Tag還能夠被解構:

getAuthedUserName : User -> String
getAuthedUserName user =
  case user of 
    Anonymous ->
      ""
    Authed name ->
      name

這個函數返回Authed用戶的名稱,若是是Anonymous用戶則返回空字符串。

完整的可在在線編輯器中執行的代碼以下:

import Html exposing (..)
import List

type User = Anonymous | Authed String

users : List User
users = [
  Anonymous, 
  Authed "Kpax",
  Authed "qin"]

getAuthedUserName : User -> String
getAuthedUserName user =
  case user of 
    Anonymous ->
      ""
    Authed name ->
      name      

main =
  div [] (List.map (text << getAuthedUserName) users)

text << getAuthedUserName 使用了Elm中的<<操做符實現兩個函數的compose,相似於lodash中的_.flowRight函數

Type variables

上面已經提到了List a類型,其中a即類型變量,表示一個當前還不肯定的類型,相似於面向對象編程中泛型的概念

map函數的類型簽名也使用了類型變量:

map : (a -> b) -> a -> b

這使得咱們調用map函數map userToString user時,只要保證user是User類型便可,map函數並不關心具體的類型。

那麼如何定義一個List a類型呢?代碼以下

type List a = Empty | Node a (List a)

前面說到Tag能夠攜帶已知類型,那麼是否能夠攜帶正在定義的這個類型呢?答案是確定的!這就是類型的遞歸List a就是這樣一個帶有類型參數的遞歸類型,平時咱們寫的數組,能夠理解爲以下代碼的語法糖

-- []
Empty

-- [1]
Node 1 Empty

-- [1, 2, 3]
Node 1 (Node 2 (Node 3 Empty))

一樣的思路,咱們徹底能夠本身實現二叉樹等數據結構,有興趣的朋友不妨試試,官方文檔有相關章節可供參考

Counter with type

上一章[基礎篇]()咱們講了Counter的實現,代碼以下:

import Html exposing (..)
import Html.Events exposing (onClick)
import Html.App as App

type Msg = Increment | Decrement

update msg model = 
  case msg of 
    Increment -> 
      model + 1
    Decrement ->
      model - 1

view model =
  div []
    [ button [onClick Decrement] [text "-"]
    , text (toString model)
    , button [onClick Increment] [text "+"]
  ]

initModel = 3

main = App.beginnerProgram {model = initModel, view = view, update = update}

讓咱們用剛剛學習的知識給以上代碼添加類型和類型註解

首先,咱們有initModel這個數據,它的類型是Int,不具有任何業務語義,讓咱們定義一個類型別名Model來表示Counter的數據

type alias Model = Int

天然initModel的類型應該爲Model

initModel : Model
initModel = 3

update函數的類型簽名比較簡單,它接受消息Msg和當前數據Model,返回新的數據Model:

update : Msg -> Model -> Model

view函數接受Model類型的數據,返回什麼呢?若是查閱div函數的文檔,你會發現返回的是一個帶有類型變量的類型Html msg。其實很好理解,由於渲染界面的函數不只要輸出Html,當事件發生時還要輸出消息,輸出消息的類型,就是應該賦給變量msg的類型,在Counter中消息的類型是Msg,所以:

view : Model -> Html Msg

完整代碼:

import Html exposing (..)
import Html.Events exposing (onClick)
import Html.App as App

type alias Model = Int

type Msg = Increment | Decrement

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

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

initModel : Model
initModel = 3

main = App.beginnerProgram {model = initModel, view = view, update = update}

總結

類型的學習可能有些枯燥,可是很是重要。若是你瞭解redux,你會發現Union type簡直天生就是作action的料,比起redux在javascript中使用的字符串既簡潔又達意,甚至還能夠嵌套組合,談笑風生!高到不知道哪裏去了!

下一章咱們將把在線編輯器放到一邊,把Counter遷移到本地運行,而後實現一個CounterList,在CounterList中,你會看到Elm是如何複用組件,以及爲何Elm被稱爲理想的分形架構。

各類架構對比,能夠參考Cycle.js做者Andre Staltz的文章

相關文章
相關標籤/搜索