Why does httpJSON fail, but httpLBS succeeds?
Asked Answered
H

2

5

This function (with httpLBS) works:

makeRequest = do
  response <- httpLBS "http://httpbin.org/get" 
  putStrLn $ "The status code was: " ++ show (getResponseStatusCode response)

But this function (with httpJSON) does not:

makeRequest = do
  response <- httpJSON "http://httpbin.org/get" 
  putStrLn $ "The status code was: " ++ show (getResponseStatusCode response)

It throws the error:

Ambiguous type variable `a0' arising from a use of `httpJSON' prevents the constraint 
`(aeson-1.1.2.0:Data.Aeson.Types.FromJSON.FromJSON a0)' from being solved.
          Probable fix: use a type annotation to specify what `a0' should be.
Homeomorphism answered 11/10, 2017 at 17:31 Comment(0)
K
16

Compare the types of httpLBS and httpJSON:

httpLBS ::   MonadIO m              => Request -> m (Response ByteString)
httpJSON :: (MonadIO m, FromJSON a) => Request -> m (Response a         )

Notice that httpLBS always produces a Response ByteString, but httpLBS produces Response a. What does that mean?

In this case, it means that httpJSON can produce a Response containing anything at all with a FromJSON instance, and it’s up to the function’s caller to decide. How does the caller decide? By specifying the type! This is one of the most interesting properties of Haskell’s typeclasses: the behavior of your program is determined by its types.

Of course, most of the time, you don’t see those types, because they are inferred. For example, if you write the following program, you will not need to write any type annotations:

ghci> id True
True

Even though the id function has type a -> a, GHC can infer that there is clearly only one choice for a, Bool, so it is chosen. However, consider your program—how can GHC know what a is supposed to be? The response result is only used in one place, getResponseStatusCode, which has the following type signature:

getResponseStatusCode :: Response a -> Int

This function also works on any Response a, so GHC still can’t decide what the a should be: according to GHC’s terminology, the a variable is ambiguous. The trouble is that picking a specific type for a is necessary, since it needs to know which FromJSON instance to use to parse the response body.

In order to solve this problem, you can disambiguate the expression by supplying your own type annotation, forcing GHC to pick a particular type for a:

makeRequest = do
  response <- httpJSON "http://httpbin.org/get" :: IO (Response ())
  putStrLn $ "The status code was: " ++ show (getResponseStatusCode response)

Of course, you should replace () with whatever type represents the structure of the JSON you expect the response to produce.

Koby answered 11/10, 2017 at 17:48 Comment(1)
Thank you for such an amazing answer. It works, and it helps me understand why!Homeomorphism
P
0

Not sure if this is helpful for anyone else but it helped me. I tried the ::IO (Response ()) annotation and I was getting back the full printed response and then "expected (), encountered Object", and switching it to :: IO (Response Object) fixed it to simply output

λ makeRequest
200
it :: ()
Patsis answered 13/3, 2019 at 20:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.