我举一个例子来证明我的问题。
说我想为键值存储构建一个中级API。基于我的API的低级API包含以下三个基本功能:
put :: ByteString -> ByteString -> IO ()
get :: ByteString -> -> IO (Maybe ByteString)
del :: ByteString -> -> IO ()
具有明显的语义。为了使我的API更方便,我不仅要传递ByteString
,还要传递可序列化的对象。所以我在第一次尝试时定义了
putOp :: Serialize a => ByteString -> a -> IO ()
getOp :: Serialize a => ByteString -> -> IO (Maybe a)
虽然将cereal
硬连接到我的API中,但这种方法很有用。事实上,经过一段时间我遇到binary-serialise-cbor
看起来很有希望。但是从cereal
更改为它会导致我的API发生重大变化。
是否有一种很好的方法来“封装”我在内部使用的序列化库(避免使用UndecidableInstances
)?
常识可能是写自己的类
class Storable a where
encode :: a -> ByteString
decode :: ByteString -> Either String a
和newtype
newtype Encode a = Encode { unwrap :: a } deriving (Functor, Eq, Show)
并在putOp
和getOp
putOp :: Storable (Encode a) => ByteString -> a -> IO ()
putOp k x = put k (encode $ Encode x)
getOp :: Storable (Encode a) => ByteString -> IO (Maybe a)
getOp k = do
x <- (get k) :: IO (Maybe (Encode a))
return (unwrap <$> x)
我们在哪里
instance Serialize a => Storable (Encode a) where
encode = S.encode . unwrap
decode (Encode r) = Encode <$> S.decode r
这看起来不错;虽然
Storable (Encode a)
上下文,并为Storable a
编写自己的实例,为Storable (Encode a)
编写实例。有更优雅的解决方案吗?