I am having a lot of trouble understanding this compilation error. The code is the following:
#!/usr/bin/env stack
-- stack script --resolver lts-8.22
{-# LANGUAGE OverloadedStrings #-}
import Network.HTTP.Simple
import qualified Data.ByteString as BS
import Control.Monad.Reader
import Control.Monad.Except
import Control.Monad.Trans.Class
import Data.Conduit
type Cmd i o = ReaderT (BS.ByteString) (ExceptT HttpException (ConduitM i o IO))
runCmd :: Cmd i o a -> BS.ByteString -> ConduitM i o IO (Either HttpException a)
runCmd cmdTS host = runExceptT $ runReaderT cmdTS host
readHost :: Cmd () BS.ByteString ()
readHost = do
host <- ask
let req = setRequestMethod "GET"
$ setRequestHost host
$ defaultRequest
lift . lift $ httpSource req getResponseBody
Basically I have a MT stack of 4 layers and in readHost
I am trying to lift an object from the second to bottom layer to the top layer so I have two lift
composed together. But sadly the code will not compile and I got the following error:
[-Wdeferred-type-errors]
• No instance for (Control.Monad.Trans.Resource.Internal.MonadResource
IO)
arising from a use of ‘httpSource’
• In the second argument of ‘($)’, namely
‘httpSource req getResponseBody’
In a stmt of a 'do' block:
lift . lift $ httpSource req getResponseBody
In the expression:
do host <- ask
let req
= setRequestMethod "GET" $ setRequestHost host $ defaultRequest
lift . lift $ httpSource req getResponseBody
Please help me understand what this error message means. On a side note, I have not seen people building a stack like this with ConduiT
sitting at the bottom, so I guess this is bad practice? What will be a good "design pattern" here? I know that the word "design patter" does not exist in haskell but I cannot think of a better word. Thanks a lot!
I started by reducing the layers in the monad transformer stack to shrink your code to this:
#!/usr/bin/env stack
-- stack script --resolver lts-8.22
{-# LANGUAGE OverloadedStrings #-}
import Network.HTTP.Simple
import qualified Data.ByteString as BS
import Control.Monad.Reader
import Control.Monad.Except
import Control.Monad.Trans.Class
import Data.Conduit
type Cmd i o = ConduitM i o IO
readHost :: BS.ByteString -> Cmd () BS.ByteString ()
readHost host = do
let req = setRequestMethod "GET" . setRequestHost host $ defaultRequest
httpSource req getResponseBody
I then ran stack {scriptname}
and got an error very similar to the one you got
for the larger stack:
{scriptname}:17:3: error:
• No instance for (resourcet-1.1.9:Control.Monad.Trans.Resource.Internal.MonadResource
IO)
arising from a use of ‘httpSource’
• In a stmt of a 'do' block: httpSource req getResponseBody
In the expression:
do { let req
= setRequestMethod "GET" . setRequestHost host $ defaultRequest;
httpSource req getResponseBody }
In an equation for ‘readHost’:
readHost host
= do { let req = ...;
httpSource req getResponseBody }
Figuring out this smaller case may very well make the more complicated version easier to tackle.
Let's look at the type for httpSource
:
httpSource :: (MonadResource m, MonadIO n) =>
Request
-> (Response (ConduitM i ByteString n ()) -> ConduitM i o m r)
-> ConduitM i o m r
Its return type is ConduitM i o m r
where m
must be an instance of
MonadResource
.
Now let's have a second glance at the final return type of readHost
.
It's Cmd () BS.ByteString ()
which is nothing but
ConduitM () BS.ByteString IO ()
in disguise.
Compare Conduit () BS.ByteString IO ()
with ConduitM i o m r
to see that
m
corresponds with IO
.
Is IO
an instance of MonadResource
? According to GHC, it isn't.
A little looking around using Hoogle lets us know that ResourceT IO
is an
instance of MonadResource
. Let's try it out in a program.
-- stack script --resolver lts-8.22
{-# LANGUAGE OverloadedStrings #-}
import Network.HTTP.Simple
import qualified Data.ByteString as BS
import Control.Monad.Reader
import Control.Monad.Except
import Control.Monad.Trans.Class
import Control.Monad.Trans.Resource
import Data.Conduit
main :: IO ()
main = putStrLn "hello"
type Cmd i o = ConduitM i o (ResourceT IO)
readHost :: BS.ByteString -> Cmd () BS.ByteString ()
readHost host = do
let req = setRequestMethod "GET" . setRequestHost host $ defaultRequest
httpSource req getResponseBody
This compiles without errors and also prints to standard output.
I believe you can use this fix for the larger program. At the very least it explains the error.