#haskell
#haskell
Вопрос:
Задан экзистенциальный тип данных, например:
data Foo = forall a . (Typeable a, Binary a) => Foo a
Я хотел бы написать instance Binary Foo
. Я могу написать сериализацию (сериализуйте TypeRep
затем сериализуйте значение), но я не могу понять, как написать десериализацию. Основная проблема заключается в том, что при задании TypeRep
вам нужно выполнить обратное сопоставление со словарем типов для этого типа — и я не знаю, можно ли это сделать.
Этот вопрос задавался ранее в списке рассылки haskell http://www.haskell.org/pipermail/haskell/2006-September/018522.html, но ответов дано не было.
Комментарии:
1. Насколько я знаю, это невозможно сделать каким-либо приятным способом.
Ответ №1:
Вам нужен какой-то способ, чтобы каждый Binary
экземпляр мог зарегистрировать себя (точно так же, как в вашей witness
версии). Вы можете сделать это, связав каждое объявление экземпляра с экспортированным внешним символом, где имя символа является производным от TypeRep
. Затем, когда вы хотите десериализовать, вы получаете имя из TypeRep
и динамически просматриваете этот символ (с помощью dlsym()
или чего-то подобного). Значение, экспортируемое с помощью внешнего экспорта, может, например, быть функцией десериализатора.
Это безумно некрасиво, но это работает.
Комментарии:
1. Не могли бы вы быть так любезны и немного объяснить свой ответ? Если бы вы могли опубликовать любой фрагмент кода, это было бы здорово! 🙂
Ответ №2:
Это может быть решено в GHC 7.10 и более поздних версиях с использованием языкового расширения Static Pointers:
{-# 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"
Полное описание решения можно найти в этом сообщении в блоге, а полный фрагмент здесь.
Комментарии:
1. Хорошая идея, но, похоже, она не сработает без ручного добавления для каждого типа мономорфного средства получения, на которое можно указывать статически…
2. @leftaroundabout да, это кажется довольно фундаментальным требованием этого подхода. Все еще лучше, чем любой из других ответов.
3. @abhiroop Я конкретизировал ваш ответ и превратил его в общепринятый ответ, следуя приведенным вами идеям.
4. Спасибо @NeilMitchell Блог об этом очень информативен.
Ответ №3:
Если бы вы могли это сделать, вы также смогли бы реализовать:
isValidRead :: TypeRep -> String -> Bool
Это была бы функция, которая меняет свое поведение из-за того, что кто-то определяет новый тип! Не очень чистый.. Я думаю (и надеюсь), что это невозможно реализовать в Haskell..
Комментарии:
1. Что
isValidRead
делает? Только потому, что вы можете точно сериализовать экзистенциальный, я не думаю, что вы можете запрашивать юниверс типов — поскольку экзистенциальный содержит доказательство того, что тип существовал ранее, поэтому вы не можете наблюдать вновь определенный тип.2. @NeilMitchell: Возможно, мне следовало назвать это
isValidShow
. Он сообщает, является лиString
аргумент возможным результатом дляshow
для данного типа. Если вы используетеget
функциюFoo
Binary
экземпляраput
, это косвенно сообщило бы, является ли его ввод допустимым результатом, полученным из,,.
Ответ №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
значения каждого типа, с которыми вы можете столкнуться при десериализации. При десериализации выполняется поиск по этому списку. Очевидная проблема заключается в том, что если вам не удастся вызвать witness
перед десериализацией, вы получите сбой.
Комментарии:
1. Что вам нужно импортировать для
Binary
экземпляраTypeRep
?2. Выглядит немного небезопасно! </каламбур>
3. @yairchu: Ничто не определяет его, но существует достаточно функций, чтобы разобрать его на части и собрать обратно, чтобы вы могли написать один (см.
splitTyConApp
иmkTyConApp
).