レコード
レコードは、型が異なってもよい複数の値に名前をつけて一つにまとめ、一つの値として扱う構造である。extensible のレコードは、通常のレコードと異なり、型レベルリストによって特徴付けられる。
extensibleのレコードは以下の効果を持つ。これらの効果が活かせない場合は、標準のレコードも検討しよう。
多相なアクセサの提供: 特定のキーが含まれていれば、あらゆるレコードを受け取れるような関数が定義できる。
ラッパーによるカスタマイズ: 全フィールドが、Maybe や IOなどのラッパーに含まれている場合も簡潔に表現できる(HKD)。 生成、変換、畳み込み: どんなレコードにも適用できる、一般的な操作が多数提供されている。
共通のインスタンス: 様々なインスタンスが最初からレコードに付与されている。利用者が自分でインスタンスを定義する必要はなく、推奨もされない。
基本形
Haskell 標準の**レコード**の代わりとして使える。
code:haskell
{- stack script
--resolver nightly-2018-04-21
--package extensible
-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE TypeOperators #-} module Main where
import Data.Extensible
type PersonRecord = Record
'[ "name" >: String
, "age" >: Int
]
-- | 拡張可能レコード
p1 :: PersonRecord
<: nil
-- | Haskell のレコード
data Person = Person
{ name :: String
, age :: Int
} deriving Show
p2 :: Person
p2 = Person "wado" 10
main :: IO ()
main = do
print p1
print p2
ラッパー型
実はRecordはRecordOfの特殊な場合であり、全ての値をIdentityでそのまま包んだ場合を表現している。ここでRecordOf Maybeを使えば、要素が欠けているかもしれないレコードも表現できる。このIdentity、Maybeのような型を包装型(wrappers)と呼ぶ。 code:haskell
type Record = RecordOf Identity
そして、RecordOfの実体はField型を包装型とする拡張可能積(/product)なのだ。 code:haskell
type RecordOf h = Field h :* xs
例えば、以下のような関数を使えば、Maybeなど、Applicativeのアクションに包まれた値を集めてレコードを構築することができる。
code:haskell
htraverse (fmap (Field . Identity) . getField)
:: Applicative f => Record f :* xs -> f (Record :* xs)
拡張可能積を生成、変換、分解する関数は多数定義されており、これらを組み合わせることによって、通常のレコードでは考えられないような、非常に柔軟かつ再利用性の高い処理が可能になる。 サブレコード
code:haskell
{- stack script
--resolver nightly-2018-05-09
--package extensible
-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE TypeOperators #-} module Main where
import Data.Extensible
d2 :: Dim2
<: nil
d3 :: Dim3
<: nil
main :: IO ()
main = do
print d2
print d3
print (shrink d3 :: Dim2)
shrink は shrink d2 :: Dim3 のように拡大することはできない。
この場合は shrink ではなく happend を使って明示的に追加する必要がある。
code:haskell
print (happend d2 (#z @= 0 <: nil))
print (happend (#z @= 0 <: nil) d2)
型レベル空リスト
Record に型レベル空リストを与えたい場合、値レベル空リスト同様に、型レベル空リストに種注釈を与える必要がある。
また、PolyKinds が必要となることも忘れてはいけない。
code:haskell
{- stack repl
--resolver nightly-2018-09-23
--package extensible
-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE PolyKinds #-} import Data.Extensible
import GHC.TypeLits
関連する記録