我想创建一个快速检查生成器来生成一个像树一样的数据结构。由于此结构的具体特征,我希望根据其在结构中的深度生成值,并在一个地方生成存储标记,以便在另一个地方重用它们。所以我想传递一个状态给我的生成器(就像在与put
和get
的状态单子)。
在快速检查库中是否有一个函数可以做到这一点,或者我应该将StateT monad与Gen monad结合起来?
可以创建带有状态的快速检查生成器。这个状态可以用来根据嵌入的值来调整生成器。
它需要以下模块:
import Control.Monad
import Control.Monad.Trans.Class
import Control.Monad.Trans.State
import Test.QuickCheck
import Test.QuickCheck.Gen
考虑这个数据结构:
data MyElem = Header String
| Upper [MyElem]
| Str [String]
| Tag String
deriving(Show)
假设我们想要生成一个深度有限的随机结构,从一个头开始,没有重复的标签。
创建如下状态数据结构:
data MyState = MyState
{ lastHeader :: Maybe String -- ^ The title of the last header (If a header is passed).
, level :: Int -- ^ The level (depth) where we are in the structure.
, usedTags :: [String] -- ^ The list of laready used tags.
}
initState = MyState
{ lastHeader = Nothing
, level = 1
, usedTags = []
}
我们使用StateT
修饰符用嵌入的MyState
和内部的Gen
monad(来自quickcheck库)创建一个状态
type MyGen t = StateT MyState Gen t
我们可以为MyElem
数据结构的每个元素定义一个状态依赖行为的生成器。在这些函数中,我们将使用lift
函数在MyGen
单子之上使用quickcheck
函数并检索它们的结果。
genUpper :: MyGen MyElem
genUpper = do
modify (st -> st { level = level st + 1 })
es <- genBase
modify (st -> st { level = level st - 1 })
return $ Upper es
genElems :: MyGen [MyElem]
genElems = do
l <- gets level
if l == 1
then vectorOfM 6 $ oneofM [genStr, genHeader, genUpper, genTag]
else if l > 4 -- Limit the depth to 4
then vectorOfM 3 $ oneofM [genStr, genTag]
else vectorOfM 4 $ oneofM [genStr, genUpper, genTag]
genStr :: MyGen MyElem
genStr = do
l <- gets level
v <- lift $ case l of -- Set the maximum string length according to the level of the structure.
1 -> choose (5, 10)
2 -> choose (3, 7)
_ -> choose (1, 5)
s <- lift $ vectorOf v $ elements ["Lorem", "Ispum", "Dolor", "Sit", "Amet", "Elit", "Duis", "Sagittis", "Tortor"]
return $ Str s
genHeader :: MyGen MyElem
genHeader = do
h <- lift $ elements ["A header", "Another header", "Still another header", "No more header"]
modify (st -> st { lastHeader = Just h })
return $ Header h
genTag :: MyGen MyElem
genTag = do
tgs <- gets usedTags
let tag = head $ filter (`notElem` tgs) $ map (i -> "TAG" ++ show i) [1 ..]
modify (st -> st { usedTags = tag : usedTags st }) -- Set the already used tags
return $ Tag tag
genBase = do
stat <- get
case lastHeader stat of
Nothing -> do -- Force the first element to be a Header.
e <- genHeader
es <- genBase
return $ e : es
Just _ -> do
genElems
有必要为quickcheck库的一些转换器制作特定版本,以使其与新的monadMyGen
一起工作。您必须重写源文件Test.QuickCheck.Gen
vectorOfM :: Int -> MyGen t -> MyGen [t]
vectorOfM = replicateM
oneofM :: [MyGen t] -> MyGen t
oneofM [] = error "oneofM used with empty list"
oneofM gs = do
v <- lift $ choose (0, length gs - 1)
gs !! v
然后您可以使用新的生成器与generate
和evalStateT
函数。
main = do
struct <- generate $ evalStateT genBase initState
print struct