Драйвер редиса возвращает Redis (Either Reply a)
. При операции HGET
возвращает, соответственно, Redis (Either Reply (Maybe ByteString))
, типа значения по ключу может и не быть. Программист для упрощения пишет сначала специальную функцию redis'
, которая заворачивает ответ редиса в EitherT-based монаду, чтоб соединять подобные вычисления и возвращать первую неудачу:
newtype Redis' a = Redis' (EitherT Redis.Reply Redis.Redis a) deriving (MonadIO)
instance Monad Redis' where
return = Redis' . EitherT . return . Right
(Redis' m) >>= f = Redis' $ m >>= \rv -> unwrapRedis' $ f rv
redis' = Redis' . EitherT
Таким образом, теперь, если вы получаете ответ Redis (Either Reply a)
, вы его преобразуете в
Redis' (EitherT Reply Redis a)
и можете соединять подобные вычисления в do-блоке типа:
do
res <- redis' $ Redis.hget foo bar
res2 <- redis' $ Redis.hget baz zab
и вычисление остановится на первом возврате ошибки.
Далее. Программисту необходимо по кучке значений сделать HGET
и вернуть это как-то внутри кортежа, поскольку мы внутри новой монады Redis'
-- завернуть результат в неё. В случае, если значения хоть по одному ключу не существует, хочется вернуть Nothing
для всех. Потому создаётся новая монада:
newtype HashFields a = HashFields (MaybeT Redis' a)
deriving (Functor, Monad)
instance Applicative HashFields where
pure = return
(<*>) = ap
описывающая вычисления типа Redis'
, которые могут вернуть неудачу. Пишется новая функция
hashField = HashFields . MaybeT . redis'
способная завернуть результат неудачи в новую монаду, которая умеет останавливаться на первой неудаче. Также пишется специальная функция для HGET
:
getField :: ByteString -- ^ Key
-> ByteString -- ^ Hash field name
-> HashFields String
getField key field = fmap toString $ hashField $ Redis.hget key field
Также напишем функцию, которая "запустит" наше вычисление:
getRedisFields :: forall a. HashFields a -> Redis' (Maybe a)
getRedisFields (HashFields f) = runMaybeT f
И теперь лёгким движением руки мы можем сделать что-то вроде:
getRedisFields ((,,,,,) <$>
getField k "foo" <*>
getField k "bar" <*>
getField k "baz" <*>
getField k "zab" <*>
getField k "rab" <*>
getField k "oof")
Вопрос: вам не кажется это "слишком"? Я пока еще не настолько просто манипулирую типами в голове, чтоб ощутить всю ситуацию, хорошо хоть в целом могу медленно прости по шагам по коду, но есть ощущение, что что-то здесь не так.