这个问题与我的other question有关smallCheck
的Test.SmallCheck.Series
类有关。当我尝试以以下自然方式定义类Serial
的实例(@tel对上述问题的回答建议我)时,出现编译器错误:
data Person = SnowWhite | Dwarf Int
instance Serial Person where ...
事实证明,
Serial
要具有两个参数。反过来,这需要一些编译器标志。以下作品:{-# 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]))
我的问题是:
将
Identity
放在那儿是“正确的事情”吗?我受到Test.Series.list
函数类型的启发(当我第一次看到它时,我也发现它非常奇怪):list :: Depth -> Series Identity a -> [a]
正确的做法是什么?如果我每次看到
Identity
时都盲目地放入它,我会好吗?我是否应该放一些类似Serial m Integer => Serial m Person
的东西(这需要一些看上去更吓人的编译器标志:至少FlexibleContexts
和UndecidableInstances
)?第一个参数(
m
中的Serial m n
)是什么?谢谢!
最佳答案
我只是smallcheck的用户,而不是开发人员,但我认为答案是
1)不是。您应该将其保留为多态,无需上述扩展即可完成:
{-# 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)系列目前定义为
newtype Series m a = Series (ReaderT Depth (LogicT m) a)
这意味着
m
是LogicT
的基本monad,用于生成系列中的值。例如,用IO
代替m
将允许在生成系列时发生IO操作。在SmallCheck中,
m
也出现在Testable
实例声明中,例如instance (Serial m a, Show a, Testable m b) => Testable m (a->b)
。如果您只有smallCheck :: Testable IO a => Depth -> a -> IO ()
的实例,这将产生具体的影响,即无法使用诸如Identity
之类的预先存在的驱动程序功能。实际上,您可以通过编写一个自定义驱动程序函数来利用这一事实,
交错一些单调效应,例如在所述驱动程序内部记录生成的值(或类似值)。
对于我不知道的其他事情,它也可能有用。