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
カッコもなくスッキリと合成できた。 一つ一つのシンプルな仕組みがうまく合致して整理できたときはやっぱり嬉しいな。