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)