Подтвердить что ты не робот

Бинарный экземпляр для экзистенциального

Для типа экзистенциальных данных, например:

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, но ответов не было.

4b9b3361

Ответ 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 перед десериализацией, вы получаете сбой.