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

Is it possible to pass and retrieve a state in a quickcheck generator

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

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

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

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