Elm ことはじめ

自己紹介

  • wat-aro
  • 10%の時間ではElmでフロントエンドをやっていました
  • 関数型が好きです

おしながき

  • Elm とは
  • 文法
  • TEA
  • まとめ

Elm とは

  • 静的型付け Alt JS
  • A delightfull language
  • No Runtime Exception
  • Great Peerformance
  • Small Assets

静的型付けの Alt JS

Haskell like な見た目

map : (a -> b) -> List a -> List b
map f list =
  case list of
    [] -> []
    (x :: xs) -> f x :: (map f xs)

静的型付けの Alt JS

強力な型推論と親切なエラーメッセージ

> 1 + "1"
-- TYPE MISMATCH ----------------------------------------------------------- elm

I cannot do addition with String values like this one:

4|   1 + "1"
         ^^^
The (+) operator only works with Int and Float values.

Hint: Switch to the (++) operator to append strings!

A delightful language

公式サイトが言ってます

Image from Gyazo

Great Peerformance

Image from Gyazo

Small Assets

Image from Gyazo

文法

  • デフォルトカリー化
  • Haskell-like 型定義
  • パターンマッチ

デフォルトカリー化

MLやHaskellのようにデフォルトでカリー化されている

add : Int -> Int -> Int
add x y = x + y

add 2 3  -- 5

add2 : Int -> Int
add2 = add 2

add2 3 -- 5

Haskell-like 型定義

  • Haskell likeな型定義
  • 代数的データ型
  • 型クラスはない
type Maybe a
  = Just a
  | Nothing

型アノテーションのコロンは一つ

42 : Int
"Hello Elm" : String

パターンマッチ

  • 型定義に沿った形でパターンマッチができる
  • 網羅性検査もされるので漏れが出ない
  • 漏れているとコンパイルエラー
type Maybe a
  = Just a
  | Nothing
map : (a -> b) -> Maybe a -> Maybe b
map f x = case x of
  Just a -> Maybe (f a)
  Nothing -> Nothing

The Elm Architecture(TEA)

  • viewmodelを受け取ってHTMLを作成する
  • HTMLからmsgを投げ、updateが現在のmodelmsgから新しいmodelを作成する
  • 新しいmodelから再度HTMLを作成する
sandbox :
    { init : model
    , view : model -> Html msg
    , update : msg -> model -> model
    }
    -> Program () model msg

sandbox のライフサイクル

Browser.sandbox

カヌンターアプリの例

sandbox :
    { init : model
    , view : model -> Html msg
    , update : msg -> model -> model
    }
    -> Program () model msg

main : Program () Int Msg
main =
  Browser.sandbox { init = 0, update = update, view = view }

まずはメッセージの型定義から

  • Msg は Increment と Decrement の二つ
  • Increment が来たら model を +1 する
  • Decrement が来たら model を -1 する
  • RunTime System が update の返り値を受け取り現在のモデルを更新する
type Msg = Increment | Decrement

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

    Decrement ->
      model - 1

描画に使う関数

  • HTML タグ のタグも関数で定義されている
  • div の第一引数は Attribute msg のリスト
  • div の第二引数は Html msg のリスト
  • onClick の引数に msg を渡すと、クリックされた時にそのメッセージを投げる
  • text は文字列の描画用の関数
  • 型変数になっている部分は型推論で解決される
div : List (Attribute msg) -> List (Html msg) -> Html msg
button : List (Attribute msg) -> List (Html msg) -> Html msg
onClick : msg -> Attribute msg
text : String -> Html msg

描画部分

  • さっきの関数を使ってviewを構成する
view : Int -> Html Msg
view model =
  div []
    [ button [ onClick Decrement ] [ text "-" ]
    , div [] [ text (String.fromInt model) ]
    , button [ onClick Increment ] [ text "+" ]
    ]

副作用を扱う例(Cat gifs)

Browser.element

  • viewは変わらない
  • updateの返り値が modelから(model, Cmd msg)に変更
  • 外部からのイベントを扱う subscriptions が追加(今回は扱いません)
element :
    { init : flags -> ( model, Cmd msg )
    , view : model -> Html msg
    , update : msg -> model -> ( model, Cmd msg )
    , subscriptions : model -> Sub msg
    }
    -> Program flags model msg

Cmd とは

  • 主に副作用を扱う(e.g. HTTPリクエスト, 乱数生成, 現在時の取得)
  • 実行した結果 Cmd msg を返し、そのmsgRuntime System によって updateに渡される

副作用を扱った場合のライフサイクル

Browser.element

Cat Gifs の Msg

  • 再度猫Gifを取得する MorePlease
  • 猫Gifの取得した結果を表す GotGif (Result Http.Error String)
  • Result error value 型は成功・失敗を表すことが出来る
type Msg
  = MorePlease
  | GotGif (Result Http.Error String)

Cat Gif の Model

  • 猫画像取に失敗したら Failure
  • 取得中は Loading
  • 取得に成功したら Success に 猫Gifの URL を文字列で持つ
type Model
  = Failure
  | Loading
  | Success String

Cat Gif の Initialize phase

  • モデルの初期値は Loading
  • getRandomCatGif で猫Gifを取得する
  • Http.get の返り値の型が Cmd msg なので Cmd を強制される
init : () -> (Model, Cmd Msg)
init _ =
  (Loading, getRandomCatGif)

get : { url : String, expect : Expect msg } -> Cmd msg

getRandomCatGif : Cmd Msg
getRandomCatGif =
  Http.get
    { url = "https://api.giphy.com/v1/gifs/random?api_key=dc6zaTOxFJmzC&tag=cat"
    , expect = Http.expectJson GotGif gifDecoder
    }

Cat Gif の更新 phase

  • MorePlease メッセージならLoading状態にして猫Gifを取得しにいく
  • 取得した結果、成功していればモデルにURLを入れて描画、失敗していればFailure画面に
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    MorePlease ->
      (Loading, getRandomCatGif)

    GotGif result ->
      case result of
        Ok url ->
          (Success url, Cmd.none)

        Err _ ->
          (Failure, Cmd.none)

Cat Gif の描画

  • view でもパターンマッチで状態を網羅して描画しているかが検査できる
view : Model -> Html Msg
view model =
  div []
    [ h2 [] [ text "Random Cats" ]
    , viewGif model
    ]

viewGif : Model -> Html Msg
viewGif model =
  case model of
    Failure ->
      div []
        [ text "I could not load a random cat for some reason. "
        , button [ onClick MorePlease ] [ text "Try Again!" ]
        ]

    Loading ->
      text "Loading..."

    Success url ->
      div []
        [ button [ onClick MorePlease, style "display" "block" ] [ text "More Please!" ]
        , img [ src url ] []
        ]

TEA

  • 副作用を扱わない場合 model, update, message, view によって構成される
  • view から message を投げ、その message に合わせて model を更新する
  • 副作用を扱う場合は update から副作用(Cmd)を実行し、Cmd からまた message を投げ update で受け取る
  • 今回は見ていないけれど、外部からの入力(時間によって発するイベントやwebsocketのイベントなど**はsubscriptionで扱う

まとめ

  • Elm の基本的なところを紹介しました
  • TEA を紹介しました
  • 簡潔な型定義とパターンマッチで安全にフロントエンドが開発できるよ!