RPC (или: как устранить неоднозначность приложения-функции на основе значений TypeRep?) - PullRequest
0 голосов
/ 02 июля 2018

Я создаю некоторую инфраструктуру для выполнения удаленных вызовов процедур в Haskell, и по причинам, которые здесь слишком длинны для объяснения, я не могу повторно использовать существующие библиотеки.

Итак, вот настройка: у меня есть класс типов для сериализации и десериализации данных:

class Serializable a where
  encode :: a -> B.ByteString
  decode :: B.ByteString -> Maybe a
  maxSize :: a -> Int

, где B - Data.ByteString .

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

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

data SerializableExt = forall t . Serializable t => SerializableExt t

потому что я хочу отправить что-то типа [SerializableExt].

Так что, конечно, мне нужно создать экземпляр Serializable SerializableExt. Здесь начинается проблема:

Для реализации decode :: B.ByteString -> Maybe SerializableExt мне нужно знать конкретный тип, который оборачивает экзистенциальный тип SerializableExt.

Таким образом, я реализую encode :: SerializableExt -> B.ByteString как сериализацию конкретного типа вместе со значением:

encode (SerializableExt x) = encode (typeOf x, x)

с использованием typeOf из Тип данных . Проблема сейчас в реализации decode :: B.ByteString -> Maybe SerializableExt:

decode bs =
  let (tyenc, xenc) = splitPair bs -- Not really important. It just splits bs into the two components
  in case (decode tyenc :: Maybe TypeRep) of
       Just ty -> SerializableExt <$> _ -- Somehow invoke decode xenc, where the choice of which decode to execute depends on the value of ty.
       _ -> Nothing

Но я не вижу, как заполнить дыру здесь. Из-за разделения Хаскеллом уровня значений и уровня типов я не могу использовать значение ty для устранения неоднозначности вызова decode xenc, верно?

Есть ли способ решить эту проблему и на самом деле положить что-то в дыру, которая будет делать то, что я хочу? Или вы можете придумать другой дизайн?

РЕДАКТИРОВАТЬ: Один из способов сделать это будет следующим:

decode bs =
  let (tyenc, xenc) = splitPair bs
  in SerializableExt <$>
       case (decode tyenc :: Maybe TypeRep) of
         Just ty
           | ty == typeRep (Proxy :: Proxy Int) -> decode xenc :: Maybe Int
           | ty = typeRep (Proxy :: Proxy ()) -> decode xenc :: Maybe ()
           | ...
         _ -> Nothing

но это плохо по нескольким причинам:

  1. Расширять утомительно.
  2. Он не может обрабатывать пары (или вообще: кортежи) в общем; каждый комбинация типов должна быть обработана.
  3. Это не очень Хаскели

1 Ответ

0 голосов
/ 05 июля 2018

Data.Dynamic позволяет нам помещать произвольные значения Haskell в один контейнер и выводить их снова безопасным для типов способом. Это хорошее начало для межпроцессного общения; Я вернусь к сериализации ниже.

Мы можем написать программу, которая принимает список Dynamic значений, проверяет количество и типы, в которых она нуждается, и возвращает результат таким же образом.

{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | Experiments with type-safe serialization.

module Main where

import Data.Proxy
import Data.Dynamic
import Data.Foldable
import Data.Type.Equality
import Type.Reflection

foo :: Int -> String -> String
foo i s = concat (replicate i s)

actor :: [Dynamic] -> Either String [Dynamic]
actor (di : ds : _) = case (fromDynamic di, fromDynamic ds) of
    (Just i, Just s) -> Right [toDyn (foo i s)]
    _ -> Left "Wrong types of arguments"
actor _ = Left "Not enough arguments"

caller :: Either String [Dynamic]
caller = actor [ toDyn (3::Int), toDyn "bar" ]

main :: IO ()
main = case caller of
    Left err -> putStrLn err
    Right dyns -> for_ dyns (\d -> case fromDynamic d of
                                    Just s -> putStrLn s
                                    Nothing -> print d)

Мы можем использовать TypeRep для выбора экземпляра класса. (Для простоты тестирования моего кода я использовал String.)

class Serial a where
    encode :: a -> String
    decode :: String -> Maybe a

decodeAs :: Serial a => TypeRep a -> String -> Maybe a
decodeAs _ s = decode s

Наконец, мы хотели бы сериализовать TypeRep и при декодировании проверить, что кодированный тип соответствует типу, в котором мы декодируем.

instance Serial SomeTypeRep

encodeDyn :: (Typeable a, Serial a) => a -> (String, String)
encodeDyn a = (encode (SomeTypeRep (typeOf a)), encode a)

decodeDynamic :: forall a. (Typeable a, Serial a) => String -> String -> Maybe a
decodeDynamic tyStr aStr = case decode tyStr of
    Nothing -> Nothing
    Just (SomeTypeRep ty) ->
        case eqTypeRep ty (typeRep :: TypeRep a) of
               Nothing -> Nothing
               Just HRefl -> decodeAs ty aStr
...