9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Elmの Html Msg と Html msg の違いは?

Last updated at Posted at 2021-05-29

Elm初心者です。
写経していて、よくHtml MsgHtml msgを書き間違えて怒られました。
私はHaskellerでもあるのでmsgが型変数なのは分かるのですが、そもそもmsg型変数がMsg型以外になるケースが思いつきません。

というような疑問をTwitterに投げたらelm-origamiの作者みやも(@miyamo_madoka)さんに優しく教えていただいて腹落ちしたので、同じことに悩む初心者仲間のためにまとめ。
誤りがあればマサカリお願いします。

そもそもElmにおけるメッセージとは

今回の記事の肝はココ

  • メッセージとは外界のイベントを表現するもので、最終的にupdate関数に渡される値のこと

たとえばBrowser.elementをみると

element.elm
element :
    { init : flags -> ( model, Cmd msg )
    , view : model -> Html msg
    , update : msg -> model -> ( model, Cmd msg )
    , subscriptions : model -> Sub msg
    }
    -> Program flags model msg

メッセージはviewsubscriptionsからElmランタイムに渡されることが見てとれます。

メッセージとMsg型

上のBrowser.elementの例でもわかるとおり、メッセージはmsgという型変数となっていてMsg型である必要はありません
ElmにおいてMsg型はElmランタイムが何か裏であれこれしてくれる特別な型ではなく、ユーザーが定義した単なるカスタム型でしかありません。
ElmランタイムからみてMsg型の定義は必須ではなくIntStringがメッセージを運んでくれるならHtml IntでもHtml Stringでも良いわけです。
試しに https://elm-lang.org/examples/buttonsMsg型を使わずに書き換えてみます。

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

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

type alias Model = Int
init : Model
init =
  0

- type Msg
-   = Increment
-   | Decrement


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


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

メッセージとして単なるStringを使うようにしただけですが、変更前と同じように動作します。
(メッセージをStringにするとupdate関数で 「"Incriment"と"Decriment"以外のメッセージが送られる可能性あるだろ?そのための処理も書けよ」 とコンパイラに怒られて処理を追加しました。こういうところが「Elm流石!」と思うところ)

混乱の考察

msgという型変数名は 「ここにバインドされる型がメッセージの役割を満たす 」 という意図を明示しており可読性も向上する一方で、字面として 「この型はMsgになる」 とも読めてしまうため混乱します。

msgMsgが似すぎているのがいけない」、ということならば上で書いたとおりMsgはただのカスタム型なので、例えば先程のプログラムの型名だけを書き換えても動きます。

- type Msg
+ type Message

- update : Msg     -> Model -> Model
+ update : Message -> Model -> Model

- view : Model -> Html Msg
+ view : Model -> Html Message

実際のアプリケーションでは、単純なMsg型よりずっと複雑な型をメッセージとして受け渡しする例もあるそうです。

もう少し具体例

view : Html msg
view = div [] []

この例はイベントがない(onClickなどがない)ため、型変数はまだ何にもバインドされていません。

type Msg = Click

view : Html Msg
view = div [ onClick Click ] []

こちらはMsg型のイベントがあるので、型変数はMsgにバインドされHtml Msgという具体型になります。

innerView : Html msg
innerView = div [] []

view : Html Msg
view = div [ onClick Click ] [ innerView ]

innerViewの型変数msgは単独では1つ目の例と同じですが、viewの中で最終的にMsg型にバインドされるため、うまくいきます。

結論

  • viewやsubscriptionsなどからElmランタイム経由でupdateにを送られるメッセージはMsg型である必要はなく、任意の型で良いので定義は具体型ではなく型変数が使われている
  • 一方でメッセージにはカスタム型としてMsgを定義して使うことで網羅性の保証などの多くの利点が得られるため、そのようにすることが多い

参考

本記事の執筆のためこちらを参照・引用させていただきました
https://discourse.elm-lang.org/t/html-msg-vs-html-msg/2758

9
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?