Haskell: как исправить ошибку неоднозначного компилятора переменной типа?

Я использую GHCJSi версии 0.2.0-7.10.3: http://www.github.com/ghcjs/ghcjs/ и библиотеку reflex-dom версии 0-4 с https://github.com/reflex-frp/reflex-dom. Я не использую reflex-dom-0.3 от Hackage.

Следующая программа на Haskell не компилируется с 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 ()

я получаю ошибку

 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.

Я встраиваю библиотечную функцию reflex-dom decodeXhrResponse (а также decodeText). Я меняю сигнатуру типа FromJSON a => XhrResponse -> Maybe a на сигнатуру без переменной типа XhrResponse -> Maybe Apod. Затем программа успешно компилируется.

{-# 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

Я попытался добавить переменную типа с областью действия для rspApod, например rspApod :: Event t Apod или rspApod :: Event Spider Apod, но это не помогло.

Вопросы:

Как мне изменить первую программу для успешной компиляции? (встраивание и изменение библиотечной функции — очень плохой хак!)

Почему компилятор не находит и не использует экземпляр FromJSON для типа данных Apod?


person Jogger    schedule 28.12.2016    source источник
comment
Ваша исходная программа неоднозначна. Как насчет того, чтобы просто добавить сигнатуру типа decodeXhrResponse r :: Maybe Apod.   -  person Reid Barton    schedule 28.12.2016
comment
@Reid: Спасибо, но переход на let rspApod = fmapMaybe ( \r -> decodeXhrResponse r :: Maybe Apod) asyncEvent не решает проблему: выдает ту же ошибку No instance for. К сожалению ...   -  person Jogger    schedule 28.12.2016
comment
В это как-то с трудом верится...   -  person Reid Barton    schedule 28.12.2016
comment
Опубликуйте выше полную ошибку после того, как вы добавили аннотацию типа.   -  person chi    schedule 29.12.2016


Ответы (1)


Таким образом, исходная подпись функции

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

Поэтому при использовании компилятору необходимо найти экземпляр FromJSON для данного a. В вашем случае a равно Apod, поэтому компилятор должен штрафовать экземпляр FromJSON за Apod. В вашем коде компилятор не может знать, что вы намерены. Это распространенная проблема при синтаксическом анализе, когда компилятору необходимо сообщить, каким должен быть тип цели.

Теперь вы можете возразить, что он должен уметь определять тип цели по окружающему коду, такому как asyncEvent, но по какой-то причине это не так. Возможно, окружающий код такой же общий. Рассмотрим следующий сценарий:

main = print $ read x

Как компилятор может узнать целевой тип для чтения x?

read :: Read a => String -> a очевидно, что это не информирует его о цели.

print :: Show a => a -> IO (), и это просто утверждает, что a должен иметь экземпляр Show.

a слишком общий для анализа, нам нужен конкретный тип.

Поэтому, когда при встраивании функций и изменении сигнатур типов для включения Apod вы давали компилятору информацию, необходимую ему, чтобы знать, какой экземпляр FromJSON искать.

Вот как бы я решил это:

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 ()

Добавление аннотации встроенного типа :: Maybe Apod должно дать компилятору информацию, необходимую для определения предполагаемой цели синтаксического анализа. Разумно использовать сигнатуры типов таким образом, поскольку это действительно эффективно.

Надеюсь, это поможет!

person Fresheyeball    schedule 29.12.2016
comment
Большое спасибо за ваше любезное объяснение и ваше решение. Ваше решение такое же, как и у Рида. К сожалению, не помогает - person Jogger; 29.12.2016
comment
Даже если я аннотирую с обеих сторон let rspApod :: Event Spider (Maybe Apod) = fmap (\r -> decodeXhrResponse r :: Maybe Apod) asyncEvent, я все равно получаю сообщение об ошибке «Нет экземпляра для (aeson-0.9.0.1:Data.Aeson.Types.Class.FromJSON Apod)», возникающее из-за использования «decodeXhrResponse». - person Jogger; 29.12.2016
comment
Я добавил дополнительное объявление instance FromJSON (Maybe Apod) и получил 2 ошибки: Overlapping instances for FromJSON (Maybe Apod) arising from a use of ‘aeson-1.0.2.1:Data.Aeson.Types.FromJSON.$gdmparseJSON’ и No instance for (aeson-0.9.0.1:Data.Aeson.Types.Class.FromJSON Apod). Похоже, что существует 2 разные версии Data.Aeson 1.0 и 0.9!! Как это возможно ?? - person Jogger; 29.12.2016
comment
@Reid: После того, как я переустановил ghcjs, reflex и relex-dom, ваше решение работает! Большое спасибо за вашу помощь - person Jogger; 29.12.2016
comment
@Fresheyball: После того, как я переустановил ghcjs, reflex и relex-dom, ваше решение заработало! Большое спасибо за вашу помощь - person Jogger; 29.12.2016