Elm初心者です。
写経していて、よくHtml Msg
とHtml msg
を書き間違えて怒られました。
私はHaskellerでもあるのでmsg
が型変数なのは分かるのですが、そもそもmsg
型変数がMsg
型以外になるケースが思いつきません。
というような疑問をTwitterに投げたらelm-origami
の作者みやも(@miyamo_madoka)さんに優しく教えていただいて腹落ちしたので、同じことに悩む初心者仲間のためにまとめ。
誤りがあればマサカリお願いします。
そもそもElmにおけるメッセージとは
今回の記事の肝はココ
- メッセージとは外界のイベントを表現するもので、最終的にupdate関数に渡される値のこと
たとえばBrowser.element
をみると
element :
{ init : flags -> ( model, Cmd msg )
, view : model -> Html msg
, update : msg -> model -> ( model, Cmd msg )
, subscriptions : model -> Sub msg
}
-> Program flags model msg
メッセージはview
やsubscriptions
からElmランタイムに渡されることが見てとれます。
メッセージとMsg型
上のBrowser.element
の例でもわかるとおり、メッセージはmsg
という型変数となっていて**Msg
型である必要はありません**
ElmにおいてMsg
型はElmランタイムが何か裏であれこれしてくれる特別な型ではなく、ユーザーが定義した単なるカスタム型でしかありません。
ElmランタイムからみてMsg
型の定義は必須ではなく**Int
やString
がメッセージを運んでくれるなら**Html Int
でもHtml String
でも良いわけです。
試しに https://elm-lang.org/examples/buttons をMsg
型を使わずに書き換えてみます。
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
になる」 とも読めてしまうため混乱します。
「msg
とMsg
が似すぎているのがいけない」、ということならば上で書いたとおり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
この説明でわかるかわかりませんが。
— 3拍子が好き(みやも) (@miyamo_madoka) May 28, 2021
Html msg型の型変数はマーカー的なものでonClickなどのイベントでランタイムに送る型を表しています
ランタイムからupdate関数に送られるのでupdateでうける型ともいえます
IntやStringでも問題ないです。その場合IntやStringがイベントで送られてくる感じです