gpt4 book ai didi

haskell - QuickCheck:如何使用穷举检查器来防止被遗忘的 sum 类型的构造函数

转载 作者:行者123 更新时间:2023-12-03 22:39:29 28 4
gpt4 key购买 nike

我有一个 Haskell 数据类型,比如

data Mytype
= C1
| C2 Char
| C3 Int String

如果我 caseMytype忘记处理其中一种情况,GHC 给了我一个警告(穷举检查)。

我现在想写一个 QuickCheck Arbitrary实例生成 MyTypes喜欢:
instance Arbitrary Mytype where
arbitrary = do
n <- choose (1, 3 :: Int)
case n of
1 -> C1
2 -> C2 <$> arbitrary
3 -> C3 <$> arbitrary <*> someCustomGen

问题在于我可以为 Mytype 添加一个新的替代品。并忘记更新 Arbitrary 实例,因此我的测试不会测试该替代方案。

我想找到一种方法来使用 GHC 的详尽检查器来提醒我在我的任意实例中被遗忘的案例。

我想出的最好的是
arbitrary = do
x <- elements [C1, C2 undefined, C3 undefined undefined]
case x of
C1 -> C1
C2 _ -> C2 <$> arbitrary
C3 _ _ -> C3 <$> arbitrary <*> someCustomGen

但它并没有真正感到优雅。

我凭直觉觉得没有 100% 干净的解决方案,但我希望能够减少忘记这种情况的机会 - 特别是在代码和测试分离的大型项目中。

最佳答案

我使用 TemplateHaskell 实现了一个解决方案,您可以在 https://gist.github.com/nh2/d982e2ca4280a03364a8 找到原型(prototype).有了这个,你可以写:

instance Arbitrary Mytype where
arbitrary = oneof $(exhaustivenessCheck ''Mytype [|
[ pure C1
, C2 <$> arbitrary
, C3 <$> arbitrary <*> arbitrary
]
|])

它的工作原理是这样的:你给它一个类型名称(如 ''Mytype )和一个表达式(在我的例子中是 arbitrary 样式的列表 Gen s)。它获取该类型名称的所有构造函数的列表,并检查表达式是否包含所有这些构造函数至少一次。如果您刚刚添加了一个构造函数但忘记将其添加到 Arbitrary 实例,则此函数将在编译时警告您。

这是使用 TH 实现的方式:
exhaustivenessCheck :: Name -> Q Exp -> Q Exp
exhaustivenessCheck tyName qList = do
tyInfo <- reify tyName
let conNames = case tyInfo of
TyConI (DataD _cxt _name _tyVarBndrs cons _derives) -> map conNameOf cons
_ -> fail "exhaustivenessCheck: Can only handle simple data declarations"

list <- qList
case list of
input@(ListE l) -> do
-- We could be more specific by searching for `ConE`s in `l`
let cons = toListOf tinplate l :: [Name]
case filter (`notElem` cons) conNames of
[] -> return input
missings -> fail $ "exhaustivenessCheck: missing case: " ++ show missings
_ -> fail "exhaustivenessCheck: argument must be a list"

我正在使用 GHC.Generics轻松遍历 Exp 的语法树: 与 toListOf tinplate exp :: [Name] (来自 lens )我可以轻松找到所有 Name s 整体 exp .

我很惊讶 Language.Haskell.TH 中的类型没有 Generic实例,并且(使用当前的 GHC 7.8)都没有 IntegerWord8 - Generic这些实例是必需的,因为它们出现在 Exp 中。 .所以我将它们添加为孤立实例(对于大多数情况, StandaloneDeriving 会这样做,但对于像 Integer 这样的原始类型,我必须复制粘贴实例,因为 Int 有它们)。

该解决方案并不完美,因为它没有使用像 case 这样的详尽检查器。确实如此,但正如我们同意的那样,在保持 DRY 的情况下这是不可能的,而且这个 TH 解决方案是 DRY。

一种可能的改进/替代方法是编写一个 TH 函数,它一次检查整个模块中的所有 Arbitrary 实例,而不是调用 exhaustivenessCheck。在每个任意实例内。

关于haskell - QuickCheck:如何使用穷举检查器来防止被遗忘的 sum 类型的构造函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25535616/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com