Reputation: 2167
I can not really understand why such line of code actually work :
From Network.HTTP.Simple
{-# LANGUAGE OverloadedStrings #-}
import Network.HTTP.Simple
import qualified Data.ByteString.Char8 as B8
main :: IO ()
main = httpBS "http://example.com" >>= B8.putStrLn . getResponseBody
I am able to rewrite the stuff with do notation :
test = do
request <- return "http://example.com"
result <- httpBS request
let body = getResponseBody result
B8.putStrLn body
This works, even If I cannot figure out what is the type of return "http://example.com"
.
Q1 : How the compiler manage to find which Monad I want to use ?
My guess is : it is coming up from the fact that return of the do block is an IO() and so it would be an IO (Request) ?
Now, When trying to use httpBS in more complex code, I have some difficulties
test.hs file :
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedStrings #-}
import Network.HTTP.Simple
import qualified Data.ByteString.Char8 as B8
request = parseRequest "http://example.com"
this gives the error :
Prelude> :load test.hs
[1 of 1] Compiling Main ( test.hs, interpreted )
test.hs:8:11: error:
* Ambiguous type variable `m0' arising from a use of `parseRequest'
prevents the constraint `(Control.Monad.Catch.MonadThrow
m0)' from being solved.
Relevant bindings include
request :: m0 Request (bound at test.hs:8:1)
Probable fix: use a type annotation to specify what `m0' should be.
These potential instances exist:
instance e ~ GHC.Exception.SomeException =>
Control.Monad.Catch.MonadThrow (Either e)
-- Defined in `Control.Monad.Catch'
instance Control.Monad.Catch.MonadThrow IO
-- Defined in `Control.Monad.Catch'
instance Control.Monad.Catch.MonadThrow Maybe
-- Defined in `Control.Monad.Catch'
...plus one other
...plus 15 instances involving out-of-scope types
(use -fprint-potential-instances to see them all)
* In the expression: parseRequest "http://example.com"
In an equation for `request':
request = parseRequest "http://example.com"
|
8 | request = parseRequest "http://example.com"
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Failed, no modules loaded.
Ok. Entering same stuff in the interpreter works :
*Main Network.HTTP.Simple> import Network.HTTP.Simple
*Main Network.HTTP.Simple> req = parseRequest "http://example.com"
*Main Network.HTTP.Simple> :t req
req :: Control.Monad.Catch.MonadThrow m => m Request
*Main Network.HTTP.Simple> req
Request {
host = "example.com"
port = 80
secure = False
requestHeaders = []
path = "/"
queryString = ""
method = "GET"
proxy = Nothing
rawBody = False
redirectCount = 10
responseTimeout = ResponseTimeoutDefault
requestVersion = HTTP/1.1
}
It looks like the dreaded monomorphic restriction stuff that I already stumble accross c.f this question
So I understand I have to give the type.It's ok but then I can not figure out how to use it with the >>= notation , I can only manage to have it working with the do notation :
-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedStrings #-}
import Network.HTTP.Simple
import qualified Data.ByteString.Char8 as B8
url = "http://example.com"
maybeRequest :: Maybe Request
maybeRequest = parseRequest url
ioRequest :: IO Request
ioRequest = parseRequest url
--no. wrong type ioRequest.
--testKO = httpBS ioRequest >>= B8.putStrLn . getResponseBody
--How to have it working with a one-liner and the >>= notation ?
--do notation ok
test = do
request <- ioRequest
response <- httpBS request
let body = getResponseBody response
B8.putStrLn body
Q2 : how to use the Request and httpBS with the (>>=) operator if the Request is to be build previously ?
Upvotes: 1
Views: 152
Reputation: 16145
I cannot figure out what is the type of
return "http://example.com"
Fortunately, GHCi can:
> :t return "http://example.com"
return "http://example.com" :: Monad m => m [Char]
So it's a valid monad action for any monad that returns a String.
Since return
is part of the Monad type class definition, this should not be a surprise.
Q1 : How the compiler manage to find which Monad I want to use ?
It uses type inference. Being inside a do-block constrains this to a monad by default and so does return "..."
. (RebindableSyntax
lets you overload the implicit (>>=)
of do-blocks to any operator, but that's not something you see often.)
The action httpBS :: MonadIO m => Request -> m (Response ByteString)
further constrains this to something called MonadIO m
, which is apparently a special kind of Monad that has liftIO :: Monad m => IO a -> m a
. The simplest example of a MonadIO m
is IO
, but the compiler hasn't concretized this yet.
Finally, B8.putStrLn :: ByteString -> IO ()
constrains test :: IO ()
.
Q2 : how to use the Request and httpBS with the (>>=) operator if the Request is to be build previously ? How to have it working with a one-liner and the >>= notation ?
Both do-blocks and point-free one-liners can be convenient and slightly magical. You can bridge these two notations by de-sugaring a do-block and then perform η-reduction. A good read for understanding do-notation is still Philip Wadler's Monads for functional programming.
The following:
test = do
request <- ioRequest
response <- httpBS request
B8.putStrLn (getResponseBody response)
de-sugars into:
test =
ioRequest >>= \request ->
httpBS request >>= \response ->
B8.putStrLn (getResponseBody response)
But B8.putStrLn (getResponseBody response)
is (B8.putStrLn . getResponseBody) response
.
(The point of this transformation is to have both lambdas expressed as \x -> f x
.)
So this becomes:
test =
(ioRequest >>= \request -> httpBS request)
>>= \response -> (B8.putStrLn . getResponseBody) response
But \request -> httpBS request
is just httpBS
.
And \response -> (B8.putStrLn . getResponseBody) response
is just B8.putStrLn . getResponseBody
.
So this becomes:
test =
(ioRequest >>= httpBS)
>>= B8.putStrLn . getResponseBody
Reformatting this to fit one line:
test = ioRequest >>= httpBS >>= B8.putStrLn . getResponseBody
Upvotes: 5
Reputation: 62848
Q1: Every line of a do-block must be "in" the same monad. (That's why maybeRequest
doesn't work; you can't mix the Maybe
monad with the IO
monad.)
Q2: Close, but not quite.
ioRequest
is an I/O action, so you need to use >>=
to fetch the request out of it:
test = ioRequest >>= httpBS >>= B8.putStrLn . getResponseBody
While I'm here, I'll also point out that
request <- return "http://example.com/"
is exactly equivalent to
let request = "http://example.com/"
Upvotes: 3