Тестирование класса типов с помощью MonadIO: ни экземпляра, ни ошибки метода по умолчанию

У меня есть класс типов, который выполняет некоторый ввод-вывод. Я немного обобщил это, используя MonadIO:

class MonadIO m => MonadDB m where
    getSomething :: String -> m Something
    getSomething s = -- do some IO stuff with liftIO

В тесте я хочу заменить реализацию, чтобы протестировать функцию, использующую getSomething, поэтому я делаю это:

newtype WorkingDBM a = WorkingDBM (Identity a)
    deriving (Functor, Applicative, Monad)

instance MonadDB WorkingDBM where
    getSomething s = return $ Something "blah"

Без объявления экземпляра код предупреждает:

• No explicit implementation for ‘liftIO’
• In the instance declaration for ‘MonadIO WorkingDBM’

Итак, я добавляю:

instance MonadIO WorkingDBM

который, конечно, компилируется.

Запуск тестов в Hspec вызывает эту ошибку времени выполнения:

uncaught exception: NoMethodError (test/BlahSpec.hs:45:10-33: No instance nor default method for class operation liftIO

Я пытался использовать liftIO из Control.Monad.IO.Class:

-- C is the qualified import for Control.Monad.IO.Class
liftIO = C.liftIO

но это приводит к исключению времени выполнения NonTermination:

uncaught exception: NonTermination (<<loop>>)

Любые идеи, как я могу решить это, пожалуйста?


person Alex    schedule 16.01.2017    source источник
comment
Не делайте IO в своем тесте и не поддерживайте настоящие IO в WorkingDBM.   -  person Daniel Wagner    schedule 17.01.2017
comment
Оказывается, в функции, которую я тестировал, было liftIO, поэтому я не могу не выполнить ввод-вывод. Что значит поддержка реальная? Я думал, что MonadIO будет достаточно для этого.   -  person Alex    schedule 17.01.2017
comment
Я написал ответ с некоторыми дополнительными подробностями о том, что я имею в виду. Но я оспариваю ваше утверждение о том, что вы должны выполнить IO внутри экземпляра MonadDB в своем тесте.   -  person Daniel Wagner    schedule 17.01.2017
comment
Я не говорил, что мне нужно делать ввод-вывод внутри MonadDB; Мне нужно использовать liftIO внутри функции, которую я тестирую (которая использует экземпляр MonadDB).   -  person Alex    schedule 17.01.2017
comment
Я не понимаю различия, которые вы пытаетесь провести в своем последнем комментарии. Почему-то я чувствую, что между нами тремя (тобой, мной и компилятором) существует фундаментальное недопонимание, но я еще не видел достаточно подробностей о ваших убеждениях, чтобы понять, что это такое.   -  person Daniel Wagner    schedule 17.01.2017
comment
Уверяю вас, что это недоразумение полностью мое. Я понял, что, хотя я использую этот метод для тестирования функций, управляемых вводом-выводом, тестируемая функция все еще не использует мой тестовый экземпляр, как описано в этом вопросе, поэтому я все еще застрял. Для фона: моей тестовой целью является функция Pipe, которая использует показанную здесь MonadDB (конечно, ее реальную версию). Я хочу поиздеваться над использованием этой функции, и я думал, что это добьется этого, но, увы, я понял свою ошибку. Надеюсь это имеет смысл? PS вы, конечно, решили вопрос, так как я спрашивал liftIO.   -  person Alex    schedule 17.01.2017
comment
Я не уверен, что понимаю. Похоже, стоит открыть новый вопрос с более подробной информацией.   -  person Daniel Wagner    schedule 17.01.2017
comment
Да, справедливое замечание. Я поднял stackoverflow.com/questions/ 41694407/   -  person Alex    schedule 17.01.2017


Ответы (1)


Одним из решений является поддержка реального IO в WorkingDBM. Например:

newtype WorkingDBM a = WorkingDBM (IO a) -- N.B. IO not Identity
    deriving (Functor, Applicative, Monad)

instance MonadIO WorkingDBM where
    liftIO = WorkingDBM

Производный экземпляр для MonadIO также будет работать нормально; но пустой экземпляр не будет, так как он эквивалентен

instance MonadIO WorkingDBM where
    liftIO = undefined

который, очевидно, взорвется в первый раз, когда вы попытаетесь сделать IO.

person Daniel Wagner    schedule 16.01.2017
comment
Спасибо. Как заставить использовать WorkingDBM? В настоящее время я использую unWorkingDBM (WorkingDBM (Identity x)) = x в своем тесте, что, очевидно, заставляет компилятор использовать мой экземпляр MonadDB в тесте. Отсутствие эквивалента при использовании IO a (из-за отсутствия конструктора данных для IO означает, что я не могу заставить компилятор использовать тестовый экземпляр (производный или иной...) - person Alex; 17.01.2017
comment
@atc Вы все еще можете написать runWorkingDBM (WorkingDBM x) = x, вам просто нужно указать тип runWorkingDBM :: WorkingDBM a -> IO a, а не runWorkingDBM :: WorkingDBM a -> a. - person Daniel Wagner; 17.01.2017