haskellio-monadhspec

Testing a typeclass with MonadIO: "No instance nor default method" error


I have a typeclass that performs some IO. I've generalised it a little using MonadIO:

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

In a test I wish to replace the implementation so I can test a function that uses getSomething, so I do this:

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

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

Without an instance declaration the code warns:

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

So I add:

instance MonadIO WorkingDBM

which of course compiles.

Running the tests in Hspec causes this runtime error:

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

I have tried using liftIO from Control.Monad.IO.Class:

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

but this results in a NonTermination runtime exception:

uncaught exception: NonTermination (<<loop>>)

Any ideas how I can resolve this please?


Solution

  • One solution is to support real IO in WorkingDBM. For example:

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

    The derived instance for MonadIO will also work fine; but the empty instance will not, as it is equivalent to

    instance MonadIO WorkingDBM where
        liftIO = undefined
    

    which will obviously blow up the first time you try to actually do IO.