问题
This question is related to my other question about smallCheck's Test.SmallCheck.Series class. When I try to define an instance of the class Serial in the following natural way (suggested to me by an answer by @tel to the above question), I get compiler errors:
data Person = SnowWhite | Dwarf Int
instance Serial Person where ...
It turns out that Serial wants to have two arguments. This, in turn, necessitates a some compiler flags. The following works:
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}
import Test.SmallCheck
import Test.SmallCheck.Series
import Control.Monad.Identity
data Person = SnowWhite | Dwarf Int
instance Serial Identity Person where
series = generate (\d -> SnowWhite : take (d-1) (map Dwarf [1..7]))
My question is:
Was putting that
Identitythere the "right thing to do"? I was inspired by the type of theTest.Series.listfunction (which I also found extremely bizarre when I first saw it):list :: Depth -> Series Identity a -> [a]What is the right thing to do? Will I be OK if I just blindly put
Identityin whenever I see it? Should I have put something likeSerial m Integer => Serial m Personinstead (that necessitates some more scary-looking compiler flags:FlexibleContextsandUndecidableInstancesat least)?What is that first parameter (the
minSerial m n) for?Thank you!
回答1:
I'm just an user of smallcheck and not a developer, but I think the answer is
1) Not really. You should leave it polymorphic, which you can do without the said extensions:
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}
import Test.SmallCheck
import Test.SmallCheck.Series
import Control.Monad.Identity
data Person = SnowWhite | Dwarf Int deriving (Show)
instance (Monad m) => Serial m Person where
series = generate (\d -> SnowWhite : take (d-1) (map Dwarf [1..7]))
2) Series is currently defined as
newtype Series m a = Series (ReaderT Depth (LogicT m) a)
which means that mis the base monad for LogicT which is used to generate the values in the series. For example, writing IO in place of m would allow IO actions to happen while generating the series.
In SmallCheck, m appears also in the Testable instance declarations, such as instance (Serial m a, Show a, Testable m b) => Testable m (a->b). This has the concrete effect that the pre-existing driver functions such as smallCheck :: Testable IO a => Depth -> a -> IO () cannot be used if you only have instances for Identity.
In practice, you could make use of this fact by writing a custom driver function which interleaves some monadic effect like logging of the generated values (or some such) inside the said driver.
It might also be useful for other things which I'm not aware of.
来源:https://stackoverflow.com/questions/16557610/why-does-smallchecks-series-class-have-two-types-in-the-constructor