0

I am working on OAuth2 authentication for a Yesod application and I am having a type error that I really really don't understand. The code is broken at the moment, and I have a few :: IO ()'s and undefineds thrown around to help me isolate the type error, but the relevant code is:

getAccessToken :: Manager -> OAuth2 -> ExchangeToken -> IO (OAuth2Result Errors OAuth2Token)
getAccessToken manager oa code = do

  let (uri, defaultBody) = accessTokenUrl oa code
  let body = defaultBody <> [ ("client_id", TE.encodeUtf8 . oauthClientId $ oa )
                            , ("client_secret", TE.encodeUtf8 . oauthClientSecret $ oa)
                            , ("resource", TE.encodeUtf8 . oauthClientId $ oa)
                            ]

  response <- performOAuth2PostRequest manager oa uri body

  return undefined

performOAuth2PostRequest :: Manager -> OAuth2 -> URI -> PostBody -> IO (Response ByteString)
performOAuth2PostRequest manager oa uri body  = do
  defaultReq <- uriToRequest uri

  let addBasicAuth = applyBasicAuth (TE.encodeUtf8 . oauthClientId $ oa)
                                    (TE.encodeUtf8 . oauthClientSecret $ oa)

  let req = (addBasicAuth . updateRequestHeaders Nothing) defaultReq

  (httpLbs (urlEncodedBody body req) manager) :: IO (Response ByteString)

Notice that I am specifically setting the type of the httpLbs (urlEnc...) manager action as an IO (Response ByteString) using the ScopedTypeVariables extension. Also, that line of code should be an IO action because it's being performed at the top level of an IO action.

In fact, I ran a GHCi session and did:

Network.OAuth.OAuth2.HttpClient Network.OAuth.OAuth2.Internal 
Network.HTTP.Conduit Data.Functor Prelude> :t httpLbs
httpLbs
  :: Control.Monad.IO.Class.MonadIO m =>
     Request
     -> Manager -> m (Response Data.ByteString.Lazy.Internal.ByteString)

Which confirms my understanding that httpLbs should yield a MonadIO m => m (Response ByteString).

But here is the error I get:

• Couldn't match type ‘Response
                         Data.ByteString.Lazy.Internal.ByteString’
                 with ‘IO (Response ByteString)’
  Expected type: Manager -> IO (Response ByteString)
    Actual type: Manager
                 -> Response Data.ByteString.Lazy.Internal.ByteString
• The function ‘httpLbs’ is applied to two arguments,
  its type is ‘Request
               -> m1 (Response Data.ByteString.Lazy.Internal.ByteString)’,
  it is specialized to ‘Request
                        -> Manager -> Response Data.ByteString.Lazy.Internal.ByteString’

Why is GHC specializing m to Response instead of IO? How do I fix it?

nomen
  • 3,626
  • 2
  • 23
  • 40
  • Based on what I can tell, the actual problem is the expected type is a strict `ByteString` while the actual type is a lazy `ByteString` (which the compiler helpfully indicates is a different type by printing it fully qualified). The error seems bogus, however, since there is no way to specialize the type of `httpLbs` to `Request -> Manager -> Response Lazy.ByteString` - even if you did set `m ~ Response` (which would give another very obvious error - `no instance for MonadIO Response`) then the actual instantiation would be `... -> Response (Response Lazy.ByteString)`. – user2407038 Nov 29 '17 at 22:29
  • Aside: the type annotation you gave in the final line doesn't make use of `ScopedTypeVariables` because `IO (Response ByteString)` doesn't contain any type variables; and it is also entirely superfluous, since the type signature on the function already gives the type. – user2407038 Nov 29 '17 at 22:31

1 Answers1

1

You haven't included your import statements, making it difficult to debug this. My best guess though is that you've imported Network.HTTP.Simple, which provides functions that do not require an explicit Manager argument. I'm guessing this from the error message providing the expected type:

Request -> m1 (Response Data.ByteString.Lazy.Internal.ByteString)

Solution: either change the import, or drop the Manager argument.

Michael Snoyman
  • 31,100
  • 3
  • 48
  • 77