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

#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 ).