Haskell: How to fix the "type variable ambigous" compiler error?

170 views Asked by At

I'm using GHCJSi, version 0.2.0-7.10.3: http://www.github.com/ghcjs/ghcjs/ and the reflex-dom library version 0-4 from https://github.com/reflex-frp/reflex-dom. I'm not using reflex-dom-0.3 from Hackage.

The following Haskell program does not compile with reflex-dom-0.4:

{-# LANGUAGE RecursiveDo, ScopedTypeVariables, DeriveGeneric, OverloadedStrings #-}
import Reflex
import Reflex.Dom
import Data.Aeson
import GHC.Generics
import qualified Data.Text as T
data Apod = Apod { copyright :: T.Text
                 , date :: T.Text
                 , explanation :: T.Text
                 , hdurl :: T.Text
                 , media_type :: T.Text
                 , service_version :: T.Text
                 , title :: T.Text
                 , url :: T.Text
                 } deriving (Generic, Show)
instance FromJSON Apod
main :: IO ()
main = do
  mainWidget $ el "div" $ do
    buttonEvent <- button "GET"
    let url = "https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY"
    let defaultReq = xhrRequest "GET" url def  
    asyncEvent <- performRequestAsync (tag (constant defaultReq) buttonEvent)
    let rspApod = fmapMaybe (\r -> decodeXhrResponse r) asyncEvent
    return ()

I get the error

 Xhr00.hs:24:36:
    No instance for (aeson-0.9.0.1:Data.Aeson.Types.Class.FromJSON b0)
      arising from a use of ‘decodeXhrResponse’
    The type variable ‘b0’ is ambiguous
    Relevant bindings include
      rspApod :: Event Spider b0 (bound at Xhr00.hs:24:9)
    Note: there is a potential instance available:
      instance (aeson-0.9.0.1:Data.Aeson.Types.Class.FromJSON a,
                aeson-0.9.0.1:Data.Aeson.Types.Class.FromJSON b) =>
                aeson-0.9.0.1:Data.Aeson.Types.Class.FromJSON
                 (Data.These.These a b)
        -- Defined in ‘Data.These’
       In the expression: decodeXhrResponse r
    In the first argument of ‘fmapMaybe’, namely
      ‘(\ r -> decodeXhrResponse r)’
    In the expression:
      fmapMaybe (\ r -> decodeXhrResponse r) asyncEvent
Failed, modules loaded: none.

I inline the reflex-dom library function decodeXhrResponse (and also decodeText). I change the type signature FromJSON a => XhrResponse -> Maybe a to a signature without a type variable XhrResponse -> Maybe Apod. Then the program compiles successfully.

{-# LANGUAGE RecursiveDo, ScopedTypeVariables, DeriveGeneric, OverloadedStrings #-}
import Reflex
import Reflex.Dom hiding (decodeXhrResponse, decodeText)
-- import Reflex.Dom
import Data.Aeson
import GHC.Generics
import qualified Data.Text as T
import Control.Monad
import qualified Data.ByteString.Lazy as BL
import Data.Text.Encoding
data Apod = Apod { copyright :: T.Text
                 , date :: T.Text
                 , explanation :: T.Text
                 , hdurl :: T.Text
                 , media_type :: T.Text
                 , service_version :: T.Text
                 , title :: T.Text
                 , url :: T.Text
                 } deriving (Generic, Show)
instance FromJSON Apod
main :: IO ()
main = do
  mainWidget $ el "div" $ do
    buttonEvent <- button "GET"
    let nasa = "https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY"
    let defaultReq = xhrRequest "GET" nasa def 
    asyncEvent <- performRequestAsync (tag (constant defaultReq) buttonEvent)
    let rspApod :: Event Spider Apod = fmapMaybe (\r -> decodeXhrResponse r) asyncEvent
    return ()
-- Inlined and changed library function:
-- decodeXhrResponse :: FromJSON a => XhrResponse -> Maybe a
decodeXhrResponse :: XhrResponse -> Maybe Apod
decodeXhrResponse = join . fmap decodeText . _xhrResponse_responseText
-- Inlined and changed library function:
-- decodeText :: FromJSON a => T.Text -> Maybe a
decodeText :: T.Text -> Maybe Apod
decodeText = decode . BL.fromStrict . encodeUtf8

I tried to add a scoped type variable for rspApod like rspApod :: Event t Apod or rspApod :: Event Spider Apod but it didn't help.

Questions:

How do I have to change the first program to compile successfully? (inlining and changing a library function is a very bad hack!)

Why does the compiler not find and use the FromJSON instance for the data type Apod?

1

There are 1 answers

5
Fresheyeball On BEST ANSWER

So the function's original signature is

decodeXhrResponse :: FromJSON a => XhrResponse -> Maybe a

So when used the compiler needs to find the FromJSON instance for a given a. In your case the a is Apod, so the compiler should fine the FromJSON instance for Apod. In your code, there is no way for the compiler to know that is your intention. This is a common problem when parsing, where the compiler needs to be told what the goal type is supposed to be.

Now you could argue that it should be able to determine the goal type by the surrounding code like asyncEvent, but its not for some reason. It might be that the surrounding code is just as generic. Consider the following scenario:

main = print $ read x

How can the compiler know the target type for reading x?

read :: Read a => String -> a obviously this doesn't inform it of the target.

print :: Show a => a -> IO () and this just asserts that the a must have a Show instance.

a is too generic to be parsed to, we need a concrete type.

So when when inlined the functions and changed the type signatures to include Apod you gave the compiler the information it needs to know what FromJSON instance to look up.

Here is how I would have solved this:

main :: IO ()
main = do
  mainWidget $ el "div" $ do
    buttonEvent <- button "GET"
    let url = "https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY"
    let defaultReq = xhrRequest "GET" url def  
    asyncEvent <- performRequestAsync (tag (constant defaultReq) buttonEvent)
    let rspApod = fmapMaybe (\r -> decodeXhrResponse r :: Maybe Apod) asyncEvent
    return ()

Adding the :: Maybe Apod inline type annotation should give the compiler the information it needs to know your intended parse goal. It reasonable to use type signatures in this way, since it's actually effectful.

Hope that helps!