View on GitHub

test-extensible

Haskell の extensible パッケージのテストリポジトリ

Extensible のメモ (その1)

拡張可能レコードいろいろ

Recod 型を追う

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)

繰り返しには以下の関数を使う(他にも hfoldMapForhtabulateFor 関数がある)

-- 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

例えば,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

(型が曖昧になってしまうせいか,うまく補助関数は使えない)

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)

use-case: レコードのフィール名だけ取り出す

拡張可能レコードの値は要らないので henumerateFor 関数を使う. Record xsxsProxy 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"]