我正在玩Haskell中实现Redis客户端库,我的目标是尽可能地编码Haskell类型系统中Redis命令的语义.对于那些不知道的人来说,Redis是一个通过网络访问的数据存储区.我会用它来举例说明我的问题,但Redis并不是这个问题的焦点.
考虑这个功能
get :: (RedisValue a) => Key -> Redis a
get k = decodeValue <$> sendCommand ["GET", key]
Run Code Online (Sandbox Code Playgroud)
它向数据存储区发送命令并返回存储在给定下的值Key(对于此示例,您可以考虑type Key = String).至于返回类型:
Redis是的实例Monad和MonadIO.它封装了有关网络连接的信息.sendCommand发送请求并返回数据存储区的回复.
a是多态的,例如可以返回Strings或ByteStrings,具体取决于上下文.
以下代码应阐明上述内容.
data Redis a = ...
instance MonadIO Redis where ...
instance Monad Redis where ...
sendCommand :: [String] -> Redis String
class RedisValue a where
decodeValue :: String -> a
-- example instances
instance RedisValue String where ...
instance RedisValue ByteString where ...
Run Code Online (Sandbox Code Playgroud)
Redis支持一种简单的交易形式.在事务中,大多数命令可以与事务外部相同地发送.但是,它们的执行会延迟,直到用户发送提交命令(exec在Redis中调用).在事务内部,数据存储区仅返回存储命令以供以后执行的确认.在commit(exec)后,返回所有存储命令的所有结果.
这意味着get上面的-function在事务上下文中看起来有点不同:
get :: (RedisStatus a) => Key -> RedisTransaction a
get k = decodeStatus <$> sendCommand ["GET", key]
Run Code Online (Sandbox Code Playgroud)
注意:
monadic类型现在RedisTransaction用于指示事务上下文.
在a现在返回类型的任何实例RedisStatus.RedisValue和的实例之间存在重叠RedisStatus.例如String,在两个类中.专用Status数据类型可能只在RedisStatus类中.
我的问题是,如何编写一个get在两个上下文中都有效的函数,以及适合上下文的返回类型类.我需要的是
一种给出get返回类型"Redis或RedisTransaction"的方法,
类型a是的一个实例RedisValue中的Redis上下文和的一个实例RedisStatus中的RedisTransaction上下文.
decode根据上下文自动执行正确操作的函数.我认为这必须来自(多参数)类型.
如果您知道我如何做到这一点或者指向一些示例代码甚至是文章,那么您将得到我的谢意!
首先,我认为最好有两个不同的get命令.那就是说,这是一种方法.
class RedisGet m a where
get :: Key -> m a
instance (RedisValue a) => RedisGet Redis a where...
instance (RedisStatus a) => RedisGet RedisTransaction a where...
Run Code Online (Sandbox Code Playgroud)
你需要MPTC,但没有FunDeps或Type Families.每次使用get都需要有足够的信息来确定m和a唯一.
我同意多参数类型类很适合这里.这是一种方法:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
newtype Redis a = Redis (IO a) deriving Monad
newtype RedisTransaction a = RedisTransaction (IO a) deriving Monad
newtype Key = Key {unKey :: String}
newtype Value = Value {unValue :: String}
newtype Status = Status {unStatus :: String}
class Monad m => RedisMonad m a | m -> a where
sendCommand :: [String] -> m a
instance RedisMonad Redis Value where
sendCommand = undefined -- TODO: provide implementation
instance RedisMonad RedisTransaction Status where
sendCommand = undefined -- TODO: provide implementation
class Decodable a b where
decode :: a -> b
instance Decodable Status String where
decode = unStatus
instance Decodable Value String where
decode = unValue
get :: (RedisMonad m a, Decodable a b) => Key -> m b
get k = do
response <- sendCommand ["GET", unKey k]
return (decode response)
Run Code Online (Sandbox Code Playgroud)
注意类型isomorphisms的使用Value和Status:它使得String你的实现产生的东西稍微强一些sendCommand显然不仅仅是任意字符序列,而是遵循一些固定格式的返回值和状态.