Mini Type Puzzle
飛び入りで Elm アドベントカレンダー 2022 の12/7の記事を書いてみようと思う。
とはいえ何も準備していないので、最近の気持ちよかったことを書く。
趣味で作っているタイマーのコードをぼちぼちいじってたときに、ラジオボタンをセレクトボックスに変更しようと思って書き換えていた。
これまでのラジオボタンなら、各 input の属性に onClick <| SetBgColor GreenBack などのように個別に記述すればよかったのだが、 select になると onInput : String -> Msg を使う必要が出てくる。
でまぁ文字列を変換するコードをちまちま書けばいいじゃないか、という話なのだが、今回はこれまでに書いていたいくつかのパーツがきれいにハマって書くことができたのでちょっと嬉しかったのだ。
あらかじめ言っておくと、タイトルにいうほどのパズルではないと思うので期待はしないでいただきたい。
設定情報をクエリ文字列に変換し、クエリ文字列から設定を復元する、という仕様のため、文字列から代数的データ型の変換を行っている。
このとき便利なのが Dict で、次のように対応関係を定義していた。
type BgColor
= Transparent
| GreenBack
| BlueBack
dictBgColor : Dict.Dict String BgColor
dictBgColor =
let
pairwise bgColor =
( encodeBgColor bgColor, bgColor )
in
Dict.fromList <| List.map pairwise [ GreenBack, BlueBack, Transparent ]
これは以前 Url.Parser.Query.enum を使っていたときの名残だ。
enum : String -> Dict String a -> Parser (Maybe a)
そもそも、 Dict.get が似たようなことをしている。
get : comparable -> Dict comparable v -> Maybe v
しかし、この関数の都合の悪いのは引数の順番である。 Dict comparable v の部分を固定して使いたいときに不便だ。
そこで、関数型のアイデアとしては flip : (a -> b -> c) -> b -> a -> c を使いたくなるのだが、 Elmではすでに削除されているので簡単に実装する。
flip : (a -> b -> c) -> b -> a -> c
flip f a b =
f b a
さて、これによって String -> Maybe BgColor という対応関係が作れる
flip Dict.get dictBgColor -- String -> Maybe BgColor
BgColor の変更を伝える SetBgColor が定義されていて、これは SetBgColor : BgColor -> Msg とみなせるので、これを Maybe BgColor に適用したい。
つまりは Maybe.map を使えば良い。
Maybe.map SetBgColor -- Maybe BgColor -> Maybe Msg
onInput : (String -> Msg) -> Html.Attribute Msg のシグネチャには Msg が要求されるので最後は Maybe ではだめだ。
実際にはそんなケースは発生しないことはわかっているのだが、入力が String である以上はイレギュラーなケースもカバーしなければならない。
そこで、 NoOp という値が役に立つ。もし BgColor に該当しないイレギュラーな値が入力されたときは最終的に「何もしない」が送信される。
Maybe.withDefault NoOp -- Maybe Msg -> Msg
3つの関数の入力と出力がうまい具合に噛み合った。あとはこれを合成するだけだ。
selectBgColor : String -> Msg
selectBgColor =
flip Dict.get dictBgColor >> Maybe.map SetBgColor >> Maybe.withDefault NoOp
カッコもなくスッキリと合成できた。 一つ一つのシンプルな仕組みがうまく合致して整理できたときはやっぱり嬉しいな。