Есть ли способ получения двоичных экземпляров для типов записей Vinyl с использованием Derive и Template Haskell или иным образом?

Я пробовал пакет Vinyl, который использует виды уровня типа для создания структур записей с полиморфизмом уровня поля. и автоматически предоставляемые линзы. Обе эти функции были бы очень удобны для моего проекта, так как первая позволяет создавать структуры записей, которые являются подтипами друг друга, без конфликтов имен, а вторая значительно упрощает обновление вложенных структур.

Проблема возникает с сериализацией результирующих структур. Обычно я использую Data.DeriveTH для автоматического создания двоичных экземпляров, но, похоже, он не может справиться с этими структурами. Следующий код

{-# LANGUAGE DataKinds, TypeOperators #-}
{-# LANGUAGE FlexibleContexts, NoMonomorphismRestriction #-}
{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}
{-# LANGUAGE TemplateHaskell #-}

import Data.Vinyl

import Data.Binary
import Data.DeriveTH

eID          = Field :: "eID"      ::: Int
location     = Field :: "location" ::: (Double, Double)

type Entity = Rec 
    [   "eID"      ::: Int
    ,   "location" ::: (Double, Double)
    ]

$(derive makeBinary ''Entity)

приводит к этой ошибке в GHCI

Exception when trying to run compile-time code:
  Could not convert Dec to Decl
TySynD Main.Entity [] (AppT (ConT Data.Vinyl.Rec.Rec) (AppT (AppT PromotedConsT (AppT (AppT (ConT Data.Vinyl.Field.:::) (LitT (StrTyLit "eID"))) (ConT GHC.Types.Int))) (AppT (AppT PromotedConsT (AppT (AppT (ConT Data.Vinyl.Field.:::) (LitT (StrTyLit "location"))) (AppT (AppT (TupleT 2) (ConT GHC.Types.Double)) (ConT GHC.Types.Double)))) PromotedNilT)))
Language/Haskell/Convert.hs:(37,14)-(40,8): Non-exhaustive patterns in case

  Code: derive makeBinary ''Entity
Failed, modules loaded: none.

Кажется, это связано с этим фрагментом кода в модуле Derive Convert:

instance Convert TH.Dec HS.Decl where
    conv x = case x of
        DataD cxt n vs con ds -> f DataType cxt n vs con ds
        NewtypeD cxt n vs con ds -> f NewType cxt n vs [con] ds
        where
            f t cxt n vs con ds = DataDecl sl t (c cxt) (c n) (c vs) (c con) []

Теперь я действительно не знаю, как читать Template Haskell, поэтому я не могу добиться здесь большого прогресса. Мне пришло в голову, что я подаю производный синоним типа, а не тип данных, и это может его сломать, поэтому я попытался обернуть его в новый тип:

newtype Entity2 = Entity2 {entity :: Entity}

$(derive makeBinary ''Entity2)

что приводит к этой еще более тупой ошибке:

Exception when trying to run compile-time code:
    Could not convert Type to Type
AppT (AppT PromotedConsT (AppT (AppT (ConT Data.Vinyl.Field.:::) (LitT (StrTyLit "eID"))) (ConT GHC.Types.Int))) (AppT (AppT PromotedConsT (AppT (AppT (ConT Data.Vinyl.Field.:::) (LitT (StrTyLit "location"))) (AppT (AppT (TupleT 2) (ConT GHC.Types.Double)) (ConT GHC.Types.Double)))) PromotedNilT)
Could not convert Type to Type
AppT PromotedConsT (AppT (AppT (ConT Data.Vinyl.Field.:::) (LitT (StrTyLit "eID"))) (ConT GHC.Types.Int))
Could not convert Type to Type
PromotedConsT
Language/Haskell/Convert.hs:(71,5)-(80,26): Non-exhaustive patterns in function conv

Глядя в Convert.hs мы имеем

instance Convert TH.Type HS.Type where
    conv (ForallT xs cxt t) = TyForall (Just $ c xs) (c cxt) (c t)
    conv (VarT x) = TyVar $ c x
    conv (ConT x) | ',' `elem` show x = TyTuple Boxed []
                  | otherwise = TyCon $ c x
    conv (AppT (AppT ArrowT x) y) = TyFun (c x) (c y)
    conv (AppT ListT x) = TyList $ c x
    conv (TupleT _) = TyTuple Boxed []
    conv (AppT x y) = case c x of
        TyTuple b xs -> TyTuple b $ xs ++ [c y]
        x -> TyApp x $ c y

Теперь я предполагаю, что что-то идет не так, потому что GHC 7.6 представил новые языковые конструкции, которые шаблон Derive Haskell не принимает во внимание, что приводит к неисчерпывающим шаблонам.

Итак, мой вопрос: есть ли какой-то путь вперед, либо добавление к производному, либо написание моего собственного производного от типов виниловых пластинок, или что-то подобное? Было бы позором, если бы преимущества винила пришлось пожертвовать ручным написанием всей сериализации.


person Vic Smith    schedule 24.12.2012    source источник
comment
Должна быть возможность вручную записать экземпляры один раз для всех виниловых записей, подобно тому, как записывается экземпляр Show.   -  person Sjoerd Visscher    schedule 24.12.2012
comment
Я изначально думал, что вы не можете сделать это так, по крайней мере, без TH, но теперь вы упомянули об этом, я вполне мог ошибаться. Я попробую...   -  person Vic Smith    schedule 24.12.2012


Ответы (1)


Я ожидал, что столкнусь с некоторыми проблемами при написании экземпляров Binary со всеми этими трюками с типами, но это не могло быть проще:

instance Binary (Rec '[]) where
  put RNil = return ()
  get = return RNil

instance (Binary t, Binary (Rec fs)) => Binary (Rec ((sy ::: t) ': fs)) where
  put ((_,x) :& xs) = put x >> put xs
  get = do
    x <- get
    xs <- get
    return ((Field, x) :& xs)
person Sjoerd Visscher    schedule 24.12.2012
comment
Удивительно, я прошел примерно половину пути за то время, которое вам потребовалось, спасибо! Почти каждый день я встречаю новую причину полюбить этот язык и его сообщество. - person Vic Smith; 24.12.2012
comment
(Здесь создатель винила!) Даже я удивлен, насколько это было просто. Хорошая работа, @Sjoerd Visscher! - person Jonathan Sterling; 11.05.2013