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
?
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 givena
. In your case thea
isApod
, so the compiler should fine theFromJSON
instance forApod
. 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 thea
must have aShow
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 whatFromJSON
instance to look up.Here is how I would have solved this:
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!