在 QuickCheck 中生成满足特定 属性 的数据的最佳做法是什么?

What is the best practice to generate data which satisfy specific property in QuickCheck?

当我们使用QuickCheck检查我们的程序时,我们需要为我们的数据定义生成器,有一些通用的方法来定义它们,但是当我们需要生成的数据满足一些约束时,通用的方法通常变得无用上班。

例如

data Expr
    = LitI Int
    | LitB Bool
    | Add Expr Expr
    | And Expr Expr

data TyRep = Number | Boolean

typeInfer :: Expr -> Maybe TyRep
typeInfer e = case e of
    LitI _ -> Number
    LitB _ -> Boolean
    Add e1 e2 -> case (typeInfer e1, typeInfer e2) of
        (Just Number, Just Number) -> Just Number
        _ -> Nothing
    And e1 e2 -> case (typeInfer e1, typeInfer e2) of
        (Just Boolean, Just Boolean) -> Just Boolean
        _ -> Nothing

现在我需要定义 Expr 的生成器(即 Gen Exprinstance Arbitrary Expr),但也希望它生成正确的类型(即 isJust (typeInfer generatedExpr)

一个天真的方法是使用suchThat过滤掉无效的,但是当ExprTyRep变得复杂时,这显然是低效的。

另一个类似的情况是关于引用完整性,例如

data Expr
    = LitI Int
    | LitB Bool
    | Add Expr Expr
    | And Expr Expr
    | Ref String -- ^ reference another Expr via it's name

type Context = Map String Expr

在这种情况下,我们希望生成的 Expr 中的所有引用名称都包含在某些特定的 Context 中,现在我必须为特定的 [=21= 生成 Expr ]:

arbExpr :: Context -> Gen Expr

但是现在shrink会出问题,为了解决这个问题,我必须定义一个特定版本的shrink,并且每次使用arbExpr时都使用forAllShrink,这意味着很多工作。

所以我想知道,是否有做这些事情的最佳实践?

对于 well-typed 项,在许多情况下,一种简单的方法是为每种类型使用一个生成器,或者等效地,一个函数 TyRep -> Gen Expr。在此基础上添加变量,这通常会变成函数 Context -> TyRep -> Gen Expr.

在使用变量(没有类型或非常简单的类型)生成术语的情况下,通过上下文索引术语的类型(例如,就像您使用 bound 库所做的那样)应该可以做到很容易推导出一个通用的生成器。

对于收缩,hedgehog 的方法可以很好地工作,其中 Gen 与收缩版本一起生成一个值,使您无需定义单独的收缩函数。

请注意,随着 well-formedness/typing 关系变得更加复杂,您开始触及理论壁垒,在该处生成术语至少与任意证明搜索一样困难。


对于更高级的 techniques/related 文献,以及我自己对可能在 Haskell 中使用它的评论:

  • 生成具有均匀分布的约束数据,Claessen 等人,FLOPS'14(PDF). I believe the Haskell package lazy-search 描述的大部分机制论文,但它似乎是针对枚举而不是随机生成。

  • 进行随机判断:根据 Type-System 的定义自动生成 Well-Typed 项,作者:Fetscher 等人,ESOP '15 (PDF),标题说明了一切。不过,我不知道 Haskell 的实现;你可能想问问作者。

  • 初学者的运气:Property-Based 生成器的语言,Lampropoulos 等人,POPL'17 (PDF) (disclaimer: I'm a coauthor). A language of properties (more concretely, functions T -> Bool, e.g., a typechecker) that can be interpreted as random generators (Gen T). The language's syntax is strongly inspired by Haskell, but there are still a few differences. The implementation has an interface to extract the generated values in Haskell (github repo)。

  • 为归纳关系生成良好的生成器,Lampropoulos 等人。 POPL'18(PDF)。它在 Coq QuickChick 中,但通过提取将其绑定到 Haskell QuickCheck 似乎相当可行。