sandwood
sandwood

Reputation: 2167

How to pass from a do notation to a (>>=) operator notation?

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

Answers (2)

sshine
sshine

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

MathematicalOrchid
MathematicalOrchid

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

Related Questions