0
votes

The following code runs a mget command with Hedis, and return the result as a [Maybe BS.ByteString]:

-- | Text to ByteString
tbs :: Text -> BS.ByteString
tbs = BS.pack . T.unpack


-- | ByteString to Text
bst :: BS.ByteString -> Text
bst = T.pack . BS.unpack


mgetRedis :: Redis.Connection -> [Text] -> IO [Maybe BS.ByteString]
mgetRedis connection keys =
    runRedis connection action
    where
        action = do

            result <- Redis.mget $ tbs <$> keys
            -- `result` is of type `Either Reply [Maybe BS.ByteString]`

            case result of
                Right values -> pure values
                _            -> pure []

First of all, I find this code quite messy and was wondering if there was any way to make it cleaner.

Second, I'd like mgetRedis to return a [Maybe Text] instead, using the bst helper written above. I can't do pure $ bst <$> values because there are two levels of unpacking here: first, the Maybe and then the List. Is there any way that this function could return the desired type without drowning in a sea of nested case statements?

1
T.pack . BS.unpack is almost certainly bad idea. Not only it's slow (because of the intermediate list, though the compiler might fuse that away), it also disregards any encoding issues. The text package offers dedicated encoders that make this explicit. Also I find it dubious to return an empty list if the request has failed (?). - leftaroundabout
thanks for pointing the dedicated encoders — for the empty list, I agree, the alternative being, carrying the Either all the way down to the business logic code where the response will be processed? - Jivan

1 Answers

1
votes

What you're asking about is essentially how to fmap arbitrarily deep into a stack of functors. That can easily be done by composing fmaps:

mgetRedis :: Redis.Connection -> [Text] -> IO [Maybe Text]
mgetRedis connection keys =
    runRedis connection action
    where
        action = do

            result <- Redis.mget $ tbs <$> keys
            -- `result` is of type `Either Reply [Maybe BS.ByteString]`

            case result of
                Right values -> pure $ fmap T.decodeUtf8 <$> values
                      -- equivalent to (fmap . fmap) T.decodeUtf8 $ values
                _            -> pure []

The fmap . fmap pattern repeats, i.e. you could fmap three levels deep with fmap . fmap . fmap. But at that point it would probably be better to just think of the whole stack as a single functor, which can be done by way of monad transformers.