LoginSignup
3
1

More than 3 years have passed since last update.

[Elm][ベストプラクティス] view関数でプリミティブな型の引数を複数渡さずにレコードで渡そう

Posted at

Elmも徐々に新規参入者が増えてきた気がするので(?) 細かいベストプラクティスのようなものを投稿しようと思います。一意見に過ぎないので、こうした方が良いああした方が良いという意見があれば是非コメントをお願いします。

view関数でプリミティブな型の引数を複数渡すのを避けよう

タイトルのままですが説明していきます。実際に動くコードです。こちらのコード間違いがあることにお気づきでしょうか? userViewはStringの引数を二つ(userId, userName)を受け取りますが、呼び出しでは(userName, userId)の順で渡してしまっています。さらにuserViewは徐々に拡張していく予感もあります。特にString, Int, Boolなどのプリミティブな型が渡されやすく変動しやすいview関数は、可読性が低くなりメンテナンスしにくい関数となってしまいます。

type alias Model =
    { userList : List User }


type alias User =
    { userId : String, userName : String, groupName : String }

view : Model -> Html Msg
view model =
    div []
        [ userListView model.userList ]


userListView : List User -> Html Msg
userListView userList =
    ul [] <| List.map (\{ userId, userName } -> userView userName userId) userList


userView : String -> String -> Html Msg
userView userId userName =
    li []
        [ p [] [ text <| userId ++ ", " ++ userName ]
        ]

解決策

結論から言うと、個別のview関数に対して直前などに専用の型エイリアスを用意してあげてください。他の言語では名前付き引数なんて名前が付いていることがあります。マジックが少ないElmはショートハンドなどは用意されていないため、愚直にやります。実際に動くコードです。

userListView : List User -> Html Msg
userListView userList =
    ul [] <|
        List.map
            (\{ userId, userName } ->
                userView { userName = userName, userId = userId }
            )
            userList


type alias UserViewType =
    { userId : String, userName : String }


userView : UserViewType -> Html Msg
userView { userId, userName } =
    li []
        [ p [] [ text <| userId ++ ", " ++ userName ]
        ]

注意点

上記の解決策のコードの場合、userViewの呼び出し側でレコードのコンストラクタを利用するのは避けてください。意味がなくなってしまいます。

userView <| UserViewType userName userId

この回避策として、型をインラインに書いてしまうと言う手もあります。しかし、この場合、型注釈が無限に延びるため、可読性が落ちてしまう可能性もあります。、が戻り値はHtml Msg確定なので、問題ないと言う見方もあります。ここはプロダクトでやる際にはチームの決め事としてポリシーを事前に決めていた方がいいかもしれませんね。

userView : { userId : String, userName : String } -> Html Msg

おまけ

今回あえてview関数でさらにプリミティブな引数の場合という風に話のスコープを狭めました。例えば、以下のように型がしっかりしていて、尚且つUtilのような関数の場合 呼び出しミスする可能性が低く、カジュアルに使いたい場合、レコードで渡すのは少し煩わしいです。また、このような関数は一度作られたらインターフェースが変わる可能性も少ないため、頻繁に変更が起き尚且つ引数の数が増えやすいview関数に限定しました。

timeToText : Time.Zone -> Time.Posix -> String

また、レコードのコンストラクタを控えた方が良い文脈として、init関数のModel定義などがあります。以下はなかなか意味がわかりませんね? そもそもレコードのコンストラクタは、使用を控えた方がいいかもしれません。

init = ( Model 1 2 "abc" "def", Cmd.none) 
3
1
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
3
1