Extensible のメモ (その1)
拡張可能レコードいろいろ
Recod
型を追う
type Record = RecordOf Identity
type RecordOf h = (:*) (Field h)
(:*) :: (k -> *) -> [k] -> *
: Immutable productdata (h :: k -> *) :- (s :: [k]) = HProduct (SmallArray# Any)
SmallArray#
は Unboxed Type の配列型(Array#
より空間が小さい?)- http://hackage.haskell.org/package/ghc-prim-0.5.0.0/docs/GHC-Prim.html#t:SmallArray-35
- https://wiki.haskell.org/Unboxed_type
- http://d.hatena.ne.jp/tanakh/?of=59
Any
型- http://hackage.haskell.org/package/ghc-prim-0.5.0.0/docs/GHC-Prim.html#t:Any
- http://qiita.com/lotz/items/8b22ce15fb66cf293536
newtype Field (h :: v -> *) (kv :: Assoc k v) = Field { getField :: h (AssocValue kv) }
data Assoc k v = k :> v
type family AssocValue (kv :: Assoc k v) :: v where AssocValue (k ':> v) = v
RecordOf h '[kv]
の h
はモナドトランスフォーマーの基底となるモナドに近い.
[kv]
は key-value な型レベルリスト(可変タプル).
map
のようなことをするには?
基本的に Data.Extensible.Product にある,高階関数を使う.
hmap :: (forall x. g x -> h x) -> (g :* xs) -> h :* xs
hmapWithIndex :: (forall x. Membership xs x -> g x -> h x) -> (g :* xs) -> h :* xs
hzipWith :: (forall x. f x -> g x -> h x) -> (f :* xs) -> (g :* xs) -> h :* xs
hfoldMap :: Monoid a => (forall x. h x -> a) -> (h :* xs) -> a
しかし,これらの高階関数は forll x. g x -> h x
とあるように,各フィールドの型に依存しないような関数(例えば恒等写像 id
とか)しか渡せない.
型に合わせて関数を分けるには
要するにアドホック多相な関数を map
したい場合は,Forall
を使う.
class (ForallF c xs, Generate xs) => Forall (c :: k -> Constraint) (xs :: [k]) where
henumerateFor :: proxy c -> proxy' xs -> (forall x. c x => Membership xs x -> r -> r) -> r -> r
hgenerateListFor :: Applicative f => proxy c -> (forall x. c x => Membership xs x -> f (h x)) -> f (HList h xs)
Forall
型クラスは,特定の型レベルリストに型レベルの繰り返し文を与えるような型クラス- インスタンスは単純に空の型レベルリストと,そうでない場合の場合分け(たぶん)
henumerateFor
は列挙する関数(?)proxy c
は型レベルリストへの制約の型proxy' xs
は型レベルリストの型Membership xs x
型は型レベルセット(リスト)の要素型x
のxs
での位置を表すらしい
hgenerateListFor
はへテロリストの生成関数(?)h
はモナドみたいなもの
繰り返しには以下の関数を使う(他にも hfoldMapFor
や htabulateFor
関数がある)
-- Data.Extensible.Product
hgenerateFor :: (Forall c xs, Applicative f) => proxy c -> (forall x. c x => Membership xs x -> f (h x)) -> f (h :* xs)
hgenerateFor p f = fmap fromHList $ hgenerateListFor p f
p
は制約f
は繰り返し処理するアドホック多相な関数
例えば,FromJSON
instance Forall (KeyValue KnownSymbol FromJSON) xs => FromJSON (Record xs) where
parseJSON = withObject "Object" $
\v -> hgenerateFor (Proxy :: Proxy (KeyValue KnownSymbol FromJSON)) $
\m -> let k = symbolVal (proxyAssocKey m) in
case HM.lookup (fromString k) v of
Just a -> Field . return <$> parseJSON a
Nothing -> fail $ "Missing key: " `mappend` k
(型が曖昧になってしまうせいか,うまく補助関数は使えない)
withObject "Object"
ってのはタダの aeson の関数parseJSON (Object v) = ...
と同じ- マッチしなかったときの処理を規定してくれる
Proxy :: Proxy (KeyValue KnownSymbol FromJSON)
が制約KnownSymbol
が Key への制約KnownSymbol
は型レベル文字列であることを指してる
FromJSON
が Value への制約- 要するに Value の型は
FromJSON
型クラスのインスタンスであるって意味
- 要するに Value の型は
- 今回
m
は Key-Value な一要素と考えれば良さそうproxyAssocKey
は型レベルKey-Valueから型レベル文字列(Key)を取り出すsymbolVal
は型レベル文字列から文字列に変換するfromString
は単純に文字列型からText
やByteString
のようなIsString
型クラスのインスタンスの型に変換する関数- aeson の
Object
型はHashMap Text Value
のため
- aeson の
Field . return :: Monad h => AssocValue kv -> Field h kv
ToJSON
の場合
instance Forall (KeyValue KnownSymbol ToJSON) xs => ToJSON (Record xs) where
toJSON = Object . HM.fromList . flip appEndo [] . hfoldMap getConst' . hzipWith
(\(Comp Dict) -> Const' . Endo . (:) .
liftA2 (,) (fromString . symbolVal . proxyAssocKey) (toJSON . getField))
(library :: Comp Dict (KeyValue KnownSymbol ToJSON) :- xs)
HM.fromList :: (Eq k, Hashable k) => [(k, v)] -> HashMap k v
Endo
newtype Endo a = Endo { appEndo :: a -> a }
- ref: Data.Monoidを眺めた·うさぎ小屋
- e.g ` (appEndo . mconcat . map Endo) [(+ 1), (- 4), (+ 3)] 0 = (0 + 3) - 4 + 1 = 13`
hzipWith :: (forall x. f x -> g x -> h x) -> (f :- xs) -> (g :- xs) -> h :- xs
: zipWith のへテロリスト版hfoldMap :: Monoid a => (forall x. h x -> a) -> (h :- xs) -> a
:map
してfold
のへテロリスト版newtype Const' a x = Const' { getConst' :: a }
:const
関数の型バージョンnewtype Comp (f :: j -> *) (g :: i -> j) (a :: i) = Comp { getComp :: f (g a) }
: 関数合成を模した型library :: forall c xs. Forall c xs => Comp Dict c :- xs
: 制約辞書のへテロリストdata Dict :: Constraint -> *
(KeyValue KnownSymbol ToJSON) :: kv -> Constraint
かな?(Comp Dict (KeyValue ...) :: kv -> *) :- xs
って区切り方- イメージとしては
[Comp Dict]
な感じ(リストじゃなくてヘテロリストもとい Record だけど)
- zip する関数
toJSON . getField $ v
でフィールドの値を JSON にしてるfromString . symbolVal . proxyAssocKey
で型レベルに存在するフィールドの Key を取得し,文字列にまで変換- これらをタプルに
(k, v)
(:) (k, v)
とすることで[(k,v)] -> [(k,v)]
の型になる- コレを
Endo
型でラップし,さらにConst'
型でラップ Const'
はhzipWith
の型と整合性を保つため(forall x. f x -> g x -> h x)
の部分で,x
はフィールドだと思えばいい
Endo ([(k,v)] -> [(k,v)])
型のラップした関数だけgetConst'
で取り出し,Endo
型を畳み込む(関数合成)flip appEndo []
で空のリストに適用し,HM.fromList
でハッシュマップに変換し,Object
でラップ
use-case: レコードのフィール名だけ取り出す
拡張可能レコードの値は要らないので henumerateFor
関数を使う.
Record xs
の xs
を Proxy xs
として要求されるので
type Book = Record BookFields
type BookFields =
'[ "name" >: String
, "authors" >: [String]
, "date" >: String
, "isbm" >: String
, "price" >: Float
]
を定義(Book
から BookFields
を取り出す type family も無いみたいだし).
>> henumerateFor (Proxy :: Proxy (KeyValue KnownSymbol Show)) (Proxy :: Proxy BookFields) ((:) . symbolVal . proxyAssocKey) []
["name","authors","date","isbm","price"]
Show
は本質的に何の関係もない.
extensible-0.4.7.2
から追加された KeyIs
というのを使うと省ける
>> henumerateFor (Proxy :: Proxy (KeyIs KnownSymbol)) (Proxy :: Proxy BookFields) ((:) . symbolVal . proxyAssocKey) []
["name","authors","date","isbm","price"]