今のところcomparableしかkeyに使えないElmのDictを幽霊型で拡張して使うアイデア
タイトルの通りの事情があるので、例えばtype Id = Id Stringのようなものをkeyとして使うDictは単純には実現できない
これらは要は標準ではcomparableでないカスタムdata typeをcompareするための関数を初期化時に与えておき、それを内部的に使用するDictの代替実装(中身は赤黒木)
満足行くようであればこれでもいいと思う
今日思いついたアイデアとして、対象とするkeyの型が上述の単純なvalue typeのような、primitive typeを単一のコンストラクラ(タグ)で包んだだけのシロモノであれば、以下のように幽霊型を使ってみてもいいかも code:elm
-- Internal Phantom Type, unexposed
type TaggedStringKeyDict key val
= TaggedStringKeyDict (Dict String val)
-- Exposed
type alias IdDict val =
TaggedStringKeyDict Id val
type Id = Id String
idDictDecoder : Decoder val -> Decoder (IdDict val)
idDictDecoder valDecoder =
Decode.map TaggedStringKeyDict (Decode.dict valDecoder)
idDictGet : Id -> IdDict val -> Maybe val
idDictGet (Id id) (TaggedStringKeyDict dict) =
Dict.get id dict
idDictInsert : Id -> val -> IdDict val -> IdDict val
idDictInsert (Id id) v (TaggedStringKeyDict dict) =
TaggedStringKeyDict (Dict.insert id v dict)
idDictToList : IdDict val -> List ( Id, val )
idDictToList (TaggedStringKeyDict dict) =
Dict.foldr (\idStr v acc -> ( Id idStr, v ) :: acc) [] dict
メリット
package導入しなくても良い
Internal typeはcoreのDict String valなので、多分Elm 0.19のNative Code追放でも使い続けられる
直接的なDecoderを簡単に定義できる(というかDecode.map TaggedStrringKeyDictするだけ)のでJSONとの間の運用が容易
書いてないけどEncoderも簡単に定義できる。単にunwrapして取り出した中身の素のDictをDict.toListすればList ( String, val )が手に入るので、それをEncode.objectすれば良い。valのencodeをどこで挟むかはまあよしなに。Dict.toListする前にDict.foldrで、とかやれば良さそう
ちゃんとkeyの型についてチェックできる(まあpackageを使ってもできる)
keyの値からcomparableに対する写像の計算がはっきり定義できるのであれば、どんな型の値でも原理的にはいける。ただし、Decode.dictを利用できるのは実体がStringの場合だけ
もっとも、JSONは規格上keyが全て文字列なので、この前提は成立する
JSONとの間でのSerDeを考えなくていい内部データ構造については、fromListとtoListを適切に定義するとか、よしなに
まあこれもpackage使ってもできる
デメリット
ボイラープレート増えまくり
上述の通り別に機能的にすごく増えたりはしない
このようなDictを適用したいデータ構造がそれほど多くないのであれば、ボイラープレートの必要性も最小限に済むのでありかも?あと、幽霊型の一例として。