HMock and Impredicative Polymorphism (Part 2)
I reviewed the documentation on impredicative polymorphism yesterday while calming a tired baby, and I realized that I could probably resolve the issue described in the HMock and Impredicative Polymorphism blog entry by simply specifying types! I was able to try it out this morning, and I was indeed able to resolve the issue.
Following the types, I discovered an error in the test code. Here is the original code, for reference.
main :: IO ()
main = defaultMain . testCase "experiment" $ do
isEmpty <- runMockT $ do
expect $ WithFile_ anything
|=> \(WithFile "test.txt" ReadMode _action) -> pure True
isFileEmpty "test.txt"
True @=? isEmptyThe WithFile_ function, created by makeMockable
constructs a Matcher,
which is a specification for matching actions. The following definition
is displayed by dumping the Template Haskell splices.
data Test.HMock.Mockable.Matcher MonadHandle
:: ghc-prim-0.6.1:GHC.Types.Symbol
-> (ghc-prim-0.6.1:GHC.Types.Type -> ghc-prim-0.6.1:GHC.Types.Type)
-> ghc-prim-0.6.1:GHC.Types.Type
-> ghc-prim-0.6.1:GHC.Types.Type
where
HIsEOF_
:: (Test.Predicates.Predicate Handle)
-> Test.HMock.Mockable.Matcher MonadHandle "hIsEOF" m_a6hj Bool
WithFile_
:: (Test.Predicates.Predicate FilePath)
-> (Test.Predicates.Predicate IOMode)
-> ( forall r_a6lZ
. Typeable r_a6lZ
=> Test.Predicates.Predicate
(Handle -> Test.HMock.Internal.State.MockT m_a6hj r_a6lZ)
)
-> Test.HMock.Mockable.Matcher MonadHandle "withFile" m_a6hj r_a6lZA Matcher
constructor of course takes a Predicate
for each parameter of the mocked function! I had only seen examples with
a single Predicate
and only put one anything
argument, but I need one for each argument.
main :: IO ()
main = defaultMain . testCase "experiment" $ do
isEmpty <- runMockT $ do
expect $ WithFile_ anything anything anything
|=> \(WithFile "test.txt" ReadMode _action) -> pure True
isFileEmpty "test.txt"
True @=? isEmptyFor this minimal test, that change alone resolves the issue!
In my actual code, the return value is more complicated. The impredicative
polymorphism error goes away, but I got a Typeable
instance error instead. It seems that the constraint in
withFile was lost, resolving one issue but causing another.
This was easily solved by specifying the type of the Matcher.
expect $
( WithFile_ anything anything anything
:: Matcher MonadHandle "withFile" IO ConcreteType
)
|=> \(WithFile "test.md" ReadMode _action) -> pure concreteValueNote that the underlying monad is IO because that is the
monad used by HUnit and Tasty.
The updated code is available on GitHub.