快速检查:如何使用穷举检查器来防止总和类型的被遗忘的构造函数

nh2*_*nh2 14 haskell quickcheck

我有一个Haskell数据类型

data Mytype
  = C1
  | C2 Char
  | C3 Int String
Run Code Online (Sandbox Code Playgroud)

如果我case在a Mytype并忘记处理其中一个案例,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
Run Code Online (Sandbox Code Playgroud)

这个问题是我可以添加一个新的替代方案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
Run Code Online (Sandbox Code Playgroud)

但它并不是真的很优雅.

我直觉地认为没有100%的清洁解决方案,但是会欣赏任何可以减少忘记此类情况的机会 - 尤其是在代码和测试分离的大型项目中.

chi*_*chi 1

这里我利用了一个未使用的变量_x。但这并不比您的解决方案更优雅。

instance Arbitrary Mytype where
  arbitrary = do
    let _x = case _x of C1 -> _x ; C2 _ -> _x ; C3 _ _ -> _x
    n <- choose (1, 3 :: Int)
    case n of
      1 -> C1
      2 -> C2 <$> arbitrary
      3 -> C3 <$> arbitrary <*> someCustomGen
Run Code Online (Sandbox Code Playgroud)

当然,必须使最后一个case与 的虚拟定义保持一致_x,因此它不是完全 DRY。

或者,可以利用 Template Haskell 构建一个编译时断言,检查其中的构造函数是否Data.Data.dataTypeOf是预期的构造函数。该断言必须与Arbitrary实例保持一致,因此这也不完全是 DRY。

如果您不需要自定义生成器,我相信Data.Data可以Arbitrary通过 Template Haskell 来生成实例(我想我看到了一些代码正是这样做的,但我不记得在哪里)。这样,实例就不可能错过构造函数。