generated at
extensibleについて
H勉強会で次回やるみたいな話をしたのでそれに向けた適当なメモ書き

拡張可能レコード
extensibleパッケージで提供されている,フィールドが拡張可能なレコード.名前そのまんま

既存のレコードの問題点
ファックなことにフィールド名はレコードセレクタ関数になるので平然と値の名前空間を侵略してくる
部分的である.
extensible1.hs
data A = A1 { field1 :: String } | A2 { field2 :: String } main = print $ field1 $ A2 "abc" -- コンパイルエラーにならず,実行時エラーになる.
拡張できない(それはそう)
フィールドがファーストクラスではない
フィールド名自体はデータではなく制約として扱われる(OverLoadedLabelsの場合)
つまりはフィールド多相な関数が使えない
ネストしまくったレコードのフィールドにアクセスするのがめんどい

extensible
拡張可能なレコード(直積),拡張可能ヴァリアント(直和)を提供とそれにまつわる型クラス,関数を提供する
フィールド名を型レベルに昇格して,その型とフィールドに格納する値を紐付け,フィールド名自体は型レベルリストに格納して拡張可能なレコードを実現する.(本質的には(型レベルの)リストなのでフィールドを追加できる)
イメージとしては連想配列(なおKeyは型)
拡張可能ヴァリアントも似たような実装になっている

使い方
定義はこんな感じ
extensible2.hs
-- 普通のレコード data User = { id :: ID , name :: Text , age :: Int } -- 拡張可能レコード type User = Record '[ "id" >: ID , "name" >: Text , "age" >: Int ] -- ヴァリアント data Color = RGB Int Int Int | CMYK Int Int Int Int -- 拡張可能ヴァリアント type Color = Variant '[ "rgb" >: (Int,Int,Int) , "cmyk" >: (Int,Int,Int,Int) ]
上記のフィールド名は型,フィールド名への注釈もまた型である.このような記法で定義するために DataKind 拡張を, >: という型レベル演算子を使うために TypeOperators 拡張を使用している. >: Assoc key val 型を作る型コンストラクタであり, Assoc key val は値レベルでいうところの (key, val) に相当する.
実際にこの拡張可能レコードを使うには以下のようにする
extensible3.hs
user1 :: User user1 = #id @= "U123456789" <: #name @= "Alice" <: #age @= 24 <: nil -- lensの(^.),(.~)をつかって以下のうようにフィールドにアクセスできる user1 ^. #id user1 & #name ~. "Bob"
#フィールド名 @= 値 という感じでフィールドを定義, <: で繋いでいく.(型レベル)リストとしてみれば <: Cons に対応する
#フィールド名 という形でフィールドを指定できるのは OverloadedLavels 拡張による.
型クラス であるレコード xs ys のレコードを(順番に関係なく)すべて持っていることも表現できる.
これを用いて shrink 関数で一部のフィールドの切り出しや順番の入れ替えができる.
happend を使えば拡張もできる.
拡張可能ヴァリアントの場合以下のようになる
extensible4.hs
-- Lensの(#)を使用している.embedAssocはFieldからVariantを作ることができる関数である. color1 = #rgb # (0,0,0) :: Color color2 = embedAssoc $ #cmyk @= (0,0,0,0) :: Color
spread 関数でヴァリアントの一部を合成してヴァリアントを作ることができる.

型クラスのインスタンスにする
extensibleのパッケージに含まれる ForAll を使い型クラス制約を与えることでインスタンスにすることができる.
extensible5.hs
-- xsが拡張可能ヴァリアント Hoge型クラスのインスタンスにする instance (ForAll KeyValue KnownSymbol Hoge) xs => Hoge (Variant xs) where ...
ForAll KeyValue KnownSymbol Hoge はすべての型レベルリストの要素( k >: v )が 制約 KnownSymbol k 及び Hoge v を満たすということである. ForAll はこのような意味をもつ制約を計算する型レベル関数になっている.

フィールド多相の関数
extensible6.hs
type Person = Record '[ "personId" :> Int , "name" :> String ] type Address = Record '[ "personId" :> Int , "address" :> String ] -- personIdのフィールドを持つレコードに共通の処理を提供(フィールド多相) getPersonId :: Associate "personId" Int xs => Record xs -> Int getPersonId = view #personId

再帰的な定義
再帰的な定義は型シノニムでは行えないので, newtype をつかうこと.

既存のレコードの問題点の解決
extensibleの拡張可能レコードは既存のレコードの問題点をほとんど解決している.
フィールド名の名前空間への侵食 -> 型レベルにすることで衝突しなくなる
部分的である -> 型検査で上記のような場合を弾くことができる.
拡張できない -> 型レベルリストなので拡張できる
フィールドがファーストクラスでない -> フィールドを型にすることで第一級に扱える
ネストしたレコードへのアクセス -> フィールドに対してlensのアクセス方法を流用できることで簡単にアクセスできる