我正在建立一些基础架构来在Haskell中进行远程过程调用,并且由于在这里无法解释的原因太长,我无法重用现有的库。

设置如下:我有一个用于对数据进行序列化和反序列化的类型类:

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


其中BData.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)


使用Data-Typeable中的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


但是我看不到如何填补这里的空白。由于Haskell将值级别和类型级别分开,因此我无法使用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


但这很糟糕,原因如下:


扩展很繁琐。
它不能通用地处理对(或通常:元组)。每一个
类型组合需要处理。
不是很Haskelly

最佳答案

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

关于haskell - RPC(或者:如何基于TypeRep值消除函数应用程序的歧义?),我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/51143593/

10-10 05:20