是否可以在快速检查生成器中传递和检索状态
Is it possible to pass and retrieve a state in a quickcheck generator
我想创建一个快速检查生成器来生成像树一样的数据结构。由于此结构的具体特征,我希望根据其在结构中的深度生成值,并存储在一个地方生成的标签以在另一个地方重用它们。所以我想将一个状态传递给我的生成器(就像在 State monad 和 put
和 get
中)。
quickcheck 库中是否有一个函数可以做到这一点,或者我应该结合 StateT monad with the Gen monad ? Can quickcheck-transformer 作为解决方案吗?
可以创建带有状态的快速检查生成器。然后可以使用此状态根据嵌入值调整生成器。
它需要模块:
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)
假设我们要生成一个深度有限的随机结构,以header开头且没有重复标签。
我们创建以下状态数据结构:
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
monad 顶部使用 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 库的某些转换器制作特定版本,以使其与新的 monad 一起使用 MyGen
。
您将不得不重写源文件中描述的一些函数 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
我想创建一个快速检查生成器来生成像树一样的数据结构。由于此结构的具体特征,我希望根据其在结构中的深度生成值,并存储在一个地方生成的标签以在另一个地方重用它们。所以我想将一个状态传递给我的生成器(就像在 State monad 和 put
和 get
中)。
quickcheck 库中是否有一个函数可以做到这一点,或者我应该结合 StateT monad with the Gen monad ? Can quickcheck-transformer 作为解决方案吗?
可以创建带有状态的快速检查生成器。然后可以使用此状态根据嵌入值调整生成器。
它需要模块:
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)
假设我们要生成一个深度有限的随机结构,以header开头且没有重复标签。
我们创建以下状态数据结构:
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
monad 顶部使用 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 库的某些转换器制作特定版本,以使其与新的 monad 一起使用 MyGen
。
您将不得不重写源文件中描述的一些函数 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