在 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 Expr
或 instance Arbitrary Expr
),但也希望它生成正确的类型(即 isJust (typeInfer generatedExpr)
)
一个天真的方法是使用suchThat
过滤掉无效的,但是当Expr
和TyRep
变得复杂时,这显然是低效的。
另一个类似的情况是关于引用完整性,例如
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 似乎相当可行。
当我们使用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 Expr
或 instance Arbitrary Expr
),但也希望它生成正确的类型(即 isJust (typeInfer generatedExpr)
)
一个天真的方法是使用suchThat
过滤掉无效的,但是当Expr
和TyRep
变得复杂时,这显然是低效的。
另一个类似的情况是关于引用完整性,例如
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 似乎相当可行。