Extensible のメモ (その1)
拡張可能レコードいろいろ
Recod 型を追う
type Record = RecordOf Identitytype 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 :> vtype 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 vEndonewtype 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"]