Бинарный экземпляр для экзистенциального
Для типа экзистенциальных данных, например:
data Foo = forall a . (Typeable a, Binary a) => Foo a
Я хотел бы написать instance Binary Foo
. Я могу написать сериализацию (сериализуйте TypeRep
, а затем сериализуйте значение), но я не могу понять, как написать десериализацию. Основная проблема заключается в том, что при a TypeRep
вам нужно отобразить обратно в словарь типов для этого типа - и я не знаю, можно ли это сделать.
Этот вопрос задан ранее в списке рассылки haskell http://www.haskell.org/pipermail/haskell/2006-September/018522.html, но ответов не было.
Ответы
Ответ 1
Это можно решить в GHC 7.10 и далее с использованием расширения языка статических указателей:
{-# LANGUAGE StaticPointers #-}
{-# LANGUAGE InstanceSigs #-}
data Foo = forall a . (StaticFoo a, Binary a, Show a) => Foo a
class StaticFoo a where
staticFoo :: a -> StaticPtr (Get Foo)
instance StaticFoo String where
staticFoo _ = static (Foo <$> (get :: Get String))
instance Binary Foo where
put (Foo x) = do
put $ staticKey $ staticFoo x
put x
get = do
ptr <- get
case unsafePerformIO (unsafeLookupStaticPtr ptr) of
Just value -> deRefStaticPtr value :: Get Foo
Nothing -> error "Binary Foo: unknown static pointer"
Полное описание решения можно найти в этом сообщении в блоге и полный фрагмент здесь.
Ответ 2
Вам нужно, чтобы каждый экземпляр Binary
мог зарегистрировать себя (как и в вашей версии witness
). Вы можете сделать это, связав объявление каждого экземпляра с экспортированным иностранным символом, где имя символа получено из TypeRep
. Затем, когда вы хотите десериализовать, вы получите имя из TypeRep
и посмотрите на этот символ динамически (с помощью dlsym()
или чего-то подобного). Значение, экспортируемое внешним экспортом, может быть, например, функцией десериализатора.
Это сумасшествие уродливое, но оно работает.
Ответ 3
Если вы можете это сделать, вы также сможете реализовать:
isValidRead :: TypeRep -> String -> Bool
Это будет функция, которая изменяет свое поведение из-за того, что кто-то определяет новый тип! Не очень чистый-иш.. Я думаю (и надеюсь), что это невозможно реализовать в Haskell..
Ответ 4
У меня есть ответ, который немного работает в некоторых ситуациях (недостаточно для моих целей), но может быть лучшим, что можно сделать. Вы можете добавить функцию witness
, чтобы засвидетельствовать любые типы, которые у вас есть, и затем десериализация может искать в таблице свидетелей. Грубая идея (непроверенная):
witnesses :: IORef [Foo]
witnesses = unsafePerformIO $ newIORef []
witness :: (Typeable a, Binary a) => a -> IO ()
witness x = modifyIORef (Foo x :)
instance Binary Foo where
put (Foo x) = put (typeOf x) >> put x
get = do
ty <- get
wits <- unsafePerformIO $ readIORef witnesses
case [Foo x | Foo x <- wits, typeOf x == ty] of
Foo x:_ -> fmap Foo $ get `asTypeOf` return x
[] -> error $ "Could not find a witness for the type: " ++ show ty
Идея состоит в том, что по мере того, как вы проходите, вы вызываете witness
на значения каждого типа, с которыми вы можете столкнуться при десериализации. Когда вы deserialise вы ищете этот список. Очевидная проблема заключается в том, что если вы не вызываете witness
перед десериализацией, вы получаете сбой.