Why does smallCheck's `Series` class have two types in the constructor?

梦想与她 提交于 2019-12-13 15:14:15

问题


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:

  1. Was putting that Identity there the "right thing to do"? I was inspired by the type of the Test.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 like Serial m Integer => Serial m Person instead (that necessitates some more scary-looking compiler flags: FlexibleContexts and UndecidableInstances at least)?

  2. What is that first parameter (the m in Serial 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!