是否可以在快速检查生成器中传递和检索状态?



我想创建一个快速检查生成器来生成一个像树一样的数据结构。由于此结构的具体特征,我希望根据其在结构中的深度生成值,并在一个地方生成存储标记,以便在另一个地方重用它们。所以我想传递一个状态给我的生成器(就像在与putget的状态单子)。

在快速检查库中是否有一个函数可以做到这一点,或者我应该将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和内部的Genmonad(来自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

然后您可以使用新的生成器与generateevalStateT函数。

main = do
struct <- generate $ evalStateT genBase initState
print struct

相关内容

  • 没有找到相关文章

最新更新