我正在玩Haskell中实现Redis客户端库,我的目标是尽可能地编码Haskell类型系统中Redis命令的语义.对于那些不知道的人来说,Redis是一个通过网络访问的数据存储区.我会用它来举例说明我的问题,但Redis并不是这个问题的焦点.
示例函数考虑这个功能
get :: (RedisValue a) => Key -> Redis a get k = decodeValue <$> sendCommand ["GET", key]
它向数据存储区发送命令并返回存储在给定下的值Key
(对于此示例,您可以考虑type Key = String
).至于返回类型:
Redis
是的实例Monad
和MonadIO
.它封装了有关网络连接的信息.sendCommand
发送请求并返回数据存储区的回复.
a
是多态的,例如可以返回String
s或ByteString
s,具体取决于上下文.
以下代码应阐明上述内容.
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 ...不同的语境,不同的类型
Redis支持一种简单的交易形式.在事务中,大多数命令可以与事务外部相同地发送.但是,它们的执行会延迟,直到用户发送提交命令(exec
在Redis中调用).在事务内部,数据存储区仅返回存储命令以供以后执行的确认.在commit(exec
)后,返回所有存储命令的所有结果.
这意味着get
上面的-function在事务上下文中看起来有点不同:
get :: (RedisStatus a) => Key -> RedisTransaction a get k = decodeStatus <$> sendCommand ["GET", key]
注意:
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...
你需要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)
注意类型isomorphisms的使用Value
和Status
:它使得String
你的实现产生的东西稍微强一些sendCommand
显然不仅仅是任意字符序列,而是遵循一些固定格式的返回值和状态.