快速检查有关长度索引列表

时间:2016-09-28 19:02:35

标签: haskell dependent-type quickcheck

我正在尝试使用QuickCheck来测试Haskell中有关长度索引列表(a.k.a向量)的属性。我的麻烦是GHC抱怨在Show函数的main约束上出现了一个含糊不清的变量。

我以标准方式定义了载体

data Nat = Z | S Nat

data Vec :: Nat -> * -> * where
   Nil :: Vec 'Z a
   (:>) :: a -> Vec n a -> Vec ('S n) a

vlength :: Vec n a -> Int
vlength Nil = 0
vlength (_ :> xs) = 1 + vlength xs 

我定义了一个将其转换为列表的函数

toList :: Vec n a -> [a]
toList Nil = []
toList (x :> xs) = x : (toList xs)

这样的转换函数应该保留长度,它具有立即编码作为属性:

toList_correct :: Show a => Vec n a -> Bool
toList_correct v = vlength v == length (toList v)

我已将Arbitrary的{​​{1}}个实例定义为:

Vec

当我在instance (Show a, Arbitrary a) => Arbitrary (Vec 'Z a) where arbitrary = return Nil instance (Show a, Arbitrary a, Arbitrary (Vec n a)) => Arbitrary (Vec ('S n) a) where arbitrary = (:>) <$> arbitrary <*> arbitrary 上调用quickCheck函数时发生了问题:

main

和GHC给了我以下信息:

main :: IO ()
main = quickCheck toList_correct

我不知道如何修复此错误。任何提示都非常欢迎。

完整的代码可用here

1 个答案:

答案 0 :(得分:4)

这里有两个相同问题的实例。第一个实例是导致错误消息的实例。第二个实例仅在您解决第一个问题后出现,并且将更难解决。

当您的类型过于笼统时,错误消息来自常见问题。函数的一个更简单的例子就是:

-- Ambiguous type variable ‘a0’ arising from a use of ‘read’
showRead :: String -> String
showRead = show . read

简而言之,GHC知道您将创建某种类型a的中间值(使用read :: Read a => String -> a),然后您可以立即转换回String(使用{ {1}})。问题是要运行这个,GHC确实需要知道你正在阅读和展示的东西的类型 - 但它无法解决这个问题。

类型变量show :: Show a => a -> String不明确

GHC告诉你它无法告诉矢量你希望a在使用quickCheck时要测试的类型。一个修复是添加类型注释(或使用toList_correct)并告诉GHC您想要什么类型的向量:

TypeApplications

类型变量 quickCheck (toList_correct :: Vec n () -> Bool) 不明确

然而,n不仅含糊不清,a也是如此!因为您将其长度编码为向量的类型,所以您只能快速检查特定长度的向量的属性。简单的解决方法是确定特定的长度(或几个长度)。

n

那说,这感觉(并且它应该)有点无意义 - 你想要测试任何长度的向量。解决方案是在向量周围创建一个存在类型 quickCheck (toList_correct :: Vec Z () -> Bool) quickCheck (toList_correct :: Vec (S Z) () -> Bool) quickCheck (toList_correct :: Vec (S (S Z)) () -> Bool)

BoxVector

现在我们有了这种存在类型,我们可以创建一个 data BoxVector a where box :: Vec n a -> BoxVector a deriving instance Show a => Show (BoxVector a) 实例,即使在向量的长度上它也是任意的:

Arbitrary

我们可以在GHCi上将这个任意实例与您之前的实例(我们不需要)进行比较:

    instance (Show a, Arbitrary a) => Arbitrary (BoxVector a) where
      arbitrary = fromList <$> arbitrary
        where
          fromList :: [a] -> BoxVector a
          fromList = foldr (\e (Box es) -> Box (e :> es)) (Box Nil)

现在,我们终于准备好进行测试了。由于我们希望测试以所有可能的长度运行,因此我们需要将其更改为 ghci> sample (arbitrary :: Gen (Vec (S (S Z)) Int)) -- must specify length (:>) 0 ((:>) 0 Nil) (:>) 1 ((:>) 1 Nil) (:>) 0 ((:>) (-2) Nil) (:>) (-4) ((:>) (-6) Nil) (:>) (-1) ((:>) 2 Nil) (:>) (-8) ((:>) (-5) Nil) (:>) (-11) ((:>) 4 Nil) (:>) (-8) ((:>) 2 Nil) (:>) (-8) ((:>) (-16) Nil) (:>) (-16) ((:>) (-11) Nil) (:>) 19 ((:>) (-6) Nil) ghci> sample (arbitrary :: Gen (BoxVector Int)) -- all lengths generated Box Nil Box ((:>) (-2) ((:>) 0 Nil)) Box ((:>) (-4) Nil) Box ((:>) 0 ((:>) (-2) ((:>) (-6) ((:>) (-3) ((:>) (-6) Nil))))) Box ((:>) 8 Nil) Box ((:>) 6 ((:>) (-6) ((:>) 9 Nil))) Box ((:>) 5 ((:>) 4 ((:>) 4 ((:>) (-6) ((:>) (-6) ((:>) (-4) Nil)))))) Box ((:>) (-4) ((:>) 10 ((:>) (-10) ((:>) 2 ((:>) 6 ((:>) 3 ((:>) 4 ((:>) 1 ((:>) 3 Nil))))))))) Box ((:>) 10 ((:>) (-16) ((:>) (-14) ((:>) 15 ((:>) 4 ((:>) (-7) ((:>) (-5) ((:>) 5 ((:>) 6 ((:>) (-1) ((:>) 1 ((:>) (-14) ((:>) (-4) ((:>) 15 Nil)))))))))))))) Box ((:>) (-2) ((:>) 9 ((:>) 0 ((:>) 7 ((:>) 5 ((:>) 17 Nil)))))) Box ((:>) (-19) ((:>) (-7) ((:>) (-17) ((:>) (-8) ((:>) (-16) ((:>) 16 ((:>) (-4) ((:>) 16 ((:>) 13 ((:>) (-7) ((:>) (-3) ((:>) 4 ((:>) (-6) ((:>) (-8) ((:>) (-14) Nil))))))))))))))) 而不是BoxVector

Vec

最后,我们仍然需要指定我们的向量将包含哪些测试。对于这个测试,因为我们不关心将矢量的元素彼此区分,所以也可以使它们成为 toList_correct :: Show a => BoxVector a -> Bool toList_correct (Box v) = vlength v == length (toList v) 的矢量。

()