问题
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
Identity
there the "right thing to do"? I was inspired by the type of theTest.Series.list
function (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
Identity
in whenever I see it? Should I have put something likeSerial m Integer => Serial m Person
instead (that necessitates some more scary-looking compiler flags:FlexibleContexts
andUndecidableInstances
at least)?What is that first parameter (the
m
inSerial 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 m
is 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