extensible/初心者向け攻略情報
ghci で試してみたい人は以下のコマンドを入力してみよう!
code:_
$ stack repl --package extensible --package lens --resolver lts --ghci-options "-XDataKinds -XOverloadedLabels -XTypeApplications -XTypeOperators"
ghci> import Data.Extensible
得られる効果
フィールド名に制約がないので、既存のデータをそのままレコードに変換できる
ラッパー型を変えればフィールドが欠けている場合の処理をカスタマイズできる
フィールド名と関数名の名前空間が別になるので、重複するフィールド名を定義できる
type 宣言により、レコードに対して型クラスのインスタンスを定義する必要が無い(既にあるものは)
型レベルリストによってフィールド全体に対する走査を行える
フィールド多相な関数を定義できる
拡張可能レコード
Haskell のレコード
code: (hs)
data Person = Person
{ name :: String
, age :: Int
}
型の定義
code: (hs)
type PersonRecord = Record
'[ "name" >: String
, "age" >: Int
]
値の定義
code: (hs)
person :: PersonRecord
person = #name @= "guchi" <: nil
値の取得
拡張可能レコードのフィールドから値を取得する。
code: (hs)
値の更新
拡張可能レコードのフィールド値を更新する。
code: (hs)
フィールド多相な関数
以下の拡張可能レコード Person, Address には personId という同一 (フィールド名と型が等しい) のフィールドが存在する。
code: (hs)
type Person = Record
'[ "personId" :> Int
, "name" :> String
]
type Address = Record
'[ "personId" :> Int
, "address" :> String
]
Person および Address 型のどちらを与えても、personId の値を返すような関数 getPersonId を以下のように定義することができる。
code: (hs)
getPersonId :: Associate "personId" Int xs => Record xs -> Int
また、フィールドの制約を増やしたい場合は単純にタプルを使う。
code: (hs)
type NameAge xs =
( Associate "name" String xs
, Associate "age" Int xs
)
略記法
code: (hs)
runIdentity . getField = view _Wrapper
拡張可能レコードの順番
code: (hs)
上記の2つの拡張可能レコードはフィールドの順番が異なるだけだが、両者は異なる型として扱われる点に注意。
実際に Proxy を使って確認することができる。
code: (hs)
ghci> :set -XDataKinds -XTypeOperators -XTypeApplications
ghci> import Data.Proxy Data.Extensible
このように Proxy に同じ型を適用した場合は True となる。
code: (hs)
ghci> Proxy @ PersonA == Proxy @ PersonA
True
ghci> Proxy @ PersonB == Proxy @ PersonB
True
しかし、ProxyA と ProxyB のように異なる型の場合は、ちゃんと型エラーになることがわかる。
code: (hs)
ghci> Proxy @ PersonA == Proxy @ PersonB
<interactive>:10:20: error:
• Couldn't match type ‘Int’ with ‘Char’ Expected type: Proxy PersonA
Actual type: Proxy PersonB
• In the second argument of ‘(==)’, namely ‘Proxy @PersonB’
In the expression: Proxy @PersonA == Proxy @PersonB
In an equation for ‘it’: it = Proxy @PersonA == Proxy @PersonB
再帰的な型定義
例えば以下のような定義を extensible のレコードとバリアントを使って表現するとしよう。
code: (hs)
data Term
= TmVar Text
| TmLam Text Term
| TmApp Term Term
しかしながら、以下の type を使った素朴な定義はコンパイルエラーとなってしまう。(type では再帰型を定義できないため)
code: (hs)
type Term = Variant
'[ "var" >: Text
, "lambda" >: (Text, Term)
, "app" >: (Term, Term)
]
そのため、type の代わりに newtype の利用を推奨する。
code: (hs)
newtype Term = Term
{ unwrapTerm :: Variant
'[ "var" >: Text
, "lambda" >: (Text, Term)
, "app" >: (Term, Term)
]
}