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?
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
.