記得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
函數的參數應該是字符串,可是傳入了數字,所以報錯。數據結構
對比兩次不一樣的錯誤提示能夠看出,類型註解能讓編譯器更準確地發現和定位錯誤。隨着學習的深刻你會慢慢喜歡上類型系統帶來的安全感:若是編譯失敗,明確的提示能幫助你快速定位問題。而只要編譯經過,程序就必定能運行。架構
基本類型和多數語言是相似的,無非就是String
, Char
, Bool
Int
, Float
,能夠參考官網的literals。須要注意在Elm中,String
必須用雙引號,單引號是用來表示Char
的,字符串單引號黨須要適應一下。編輯器
嚴格來講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 type 是Elm類型系統中最重要的部分之一,它用來表示一組可能的值,每一個值叫作一個Tag
,以下:
type Bool = True | False type User = Anonymos | Authed
其中True
和False
, Anonymos
和Authed
都是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能夠直接做爲值
使用(想一想True
和False
),而帶有已知類型的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函數
上面已經提到了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的實現,代碼以下:
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的文章