Elmを通してFluxを理解する

@Panda_Program

Elm触ってみた

以下は2019年2月に社内勉強会で発表した内容です。本文の記述は最新の情報ではない場合があります。

最新の情報はElm の公式ドキュメントをご覧ください。

発表すること

  • Elm触ったらFluxが理解できたよ

発表しないこと

  • 関数型言語の一般的な説明
    • 「第一級の関数(first class function)」
    • 高階関数
    • カリー化
    • 作用・副作用の話
    • などなど

Elmという言語

特徴

  • 静的型付けの関数型言語(typed functional language)
  • コンパイルしてJSを吐き出す
  • SPAが作れる
  • The Elm Architecture
  • バージョンは0.19

An Introduction to Elm

「Elmで何か作ってみると、Elmの考え方が身につくのでJSやReactがうまく書けるようになるよ」

If you are on the fence, I can safely guarantee that if you give Elm a shot and actually make a project in it, you will end up writing better JavaScript and React code. The ideas transfer pretty easily!

https://guide.elm-lang.org/

Reduxに影響を与えてる。

チュートリアル

カウンターを作る

button.elm
import Browser
import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)


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


-- MODEL

type alias Model = Int

init : Model
init =
  0


-- UPDATE

type Msg = Increment | Decrement

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

    Decrement ->
      model - 1


-- VIEW

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

https://guide.elm-lang.org/architecture/buttons.html

Elmの構造

The Elm Architectureに基づいてアプリケーションを作る。 MVCではない。

The Elm Architectureでは下記の関数を使用する

  • Model
  • Update
  • View

Model

  • アプリケーションの本体(と言ってもいいと思う)
  • 型で定義する
  • カウンターの場合、ただのint型。
  • initで初期化する。ここではModelを数値の0と定義している。
-- MODEL

type alias Model = Int

init : Model
init = 0

(これは下記と同義)
init : Int
init = 0

update関数

  • Msg -> Model -> Model
  • メッセージと前のモデルを受け取って、新しいモデルを返す
  • モデルの更新(操作)を担う
  • Modelの変更はここに集約されている
-- UPDATE

-- メッセージを定義
type Msg = Increment | Decrement

update : Msg -> Model -> Model
update msg model =
  -- メッセージの内容に応じて、モデルを操作する
  case msg of
    Increment ->
      model + 1

    Decrement ->
      model - 1

view関数

  • Model -> Html Msg
  • モデルを受け取って、Htmlを返す
  • ただの表示を担う箇所と、Msgが埋め込まれている箇所がある
-- VIEW

view : Model -> Html Msg
view model =
  div []
    -- ボタンがクリックされた時onClick関数が発火し、update関数にMsgが送られる
    [ button [ onClick Decrement ] [ text "-" ]
    , div [] [ text (String.fromInt model) ]
    , button [ onClick Increment ] [ text "+" ]
    ]

ElmとRedux

  • ElmはReduxの設計思想に影響を与えている
  • ReduxはFlux。Elmは The Elm Architecture
  • ただ、データの流れは一方向という点で同じ

FluxとThe Elm Architectureの比較

Flux

| Flux | Elm| 共通点 | | --- | --- | --- | |Action | Msg | イベント | |Dispatcher |Update | 状態の更新 | | Store | Model| 状態の管理| | View | View | 表示 |

You Might Not Need Redux(Dan Abramov)

以下はReactでReduxなしでFluxを表現したコード

counter.js
import React, { Component } from 'react';

const counter = (state = { value: 0 }, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { value: state.value + 1 };
    case 'DECREMENT':
      return { value: state.value - 1 };
    default:
      return state;
  }
}

class Counter extends Component {
  state = counter(undefined, {});
  
  dispatch(action) {
    this.setState(prevState => counter(prevState, action));
  }

  increment = () => {
    this.dispatch({ type: 'INCREMENT' });
  };

  decrement = () => {
    this.dispatch({ type: 'DECREMENT' });
  };
  
  render() {
    return (
      <div>
        <button onClick={this.increment}>+</button>
        {this.state.value}
        <button onClick={this.decrement}>-</button>
      </div>
    )
  }
}

https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367

Elmと似てる

イベント

  increment = () => {
    this.dispatch({ type: 'INCREMENT' });
  };

  decrement = () => {
    this.dispatch({ type: 'DECREMENT' });
  };

Msg

type Msg = Increment | Decrement

状態の更新

現在の状態を受け取る。

dispatcher

const counter = (state = { value: 0 }, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { value: state.value + 1 };
    case 'DECREMENT':
      return { value: state.value - 1 };
    default:
      return state;
  }
}

 dispatch(action) {
   this.setState(prevState => counter(prevState, action));
 }

update

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

    Decrement ->
      model - 1

状態の管理

state = counter(undefined, {});

const counter = (state = { value: 0 }, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { value: state.value + 1 };
    case 'DECREMENT':
      return { value: state.value - 1 };
    default:
      return state;
  }
}
type alias Model = Int

init : Model
init = 0

表示

view

render() {
  return (
    <div>
      <button onClick={this.increment}>+</button>
      {this.state.value}
      <button onClick={this.decrement}>-</button>
    </div>
  )
}
view : Model -> Html Msg
view model =
  div []
    [ button [ onClick Decrement ] [ text "-" ]
    , div [] [ text (String.fromInt model) ]
    , button [ onClick Increment ] [ text "+" ]
    ]

(注)Elmはコンポーネント指向ではない

  • Reactはコンポーネントの組み合わせ
  • Elmは1つのアプリケーション

(参考)「Elm のコンポーネント論争とは何か」 http://jinjor-labo.hatenablog.com/entry/2017/05/12/183154

結論

  • Elmを学ぶことでFluxが理解できる!
  • Reactもうまく書けそう!
  • 型でプログラミングする、という感覚がつかめた!

Elm関連サイト

Elm - A delightful language for reliable webapps.

An Introduction to Elm(チュートリアル)

Elm 2日ほどやった感想(mizchi's blog)

余談

Elmでポートフォリオサイト作ってみました

Happy Coding 🎉

パンダのイラスト
パンダ

記事が面白いと思ったらツイートやはてブをお願いします!皆さんの感想が執筆のモチベーションになります。最後まで読んでくれてありがとう。

  • Share on Hatena
  • Share on Twitter
  • Share on Line
  • Copy to clipboard