QuickCheck属性的DRY类型注释

时间:2018-10-03 12:57:03

标签: haskell-stack quickcheck

使用QuickCheck,可以编写参数多态属性,如下所示:

associativityLaw :: (Eq a, Show a, Semigroup a) => a -> a -> a -> Property
associativityLaw x y z = (x <> y) <> z === x <> (y <> z)

这只是一个例子,因为我的实际属性更加复杂,但是它足以说明问题。此属性验证对于类型a<>运算符是关联的。

想象一下,我想为多个类型使用此属性。我可以这样定义测试列表:

tests =
  [
    testGroup "Monoid laws" [
      testProperty "Associativity law, [Int]" (associativityLaw :: [Int] -> [Int] -> [Int] -> Property),
      testProperty "Associativity law, Sum Int" (associativityLaw :: Sum Int -> Sum Int -> Sum Int -> Property)
    ]
  ]

这有效,但感觉不必要的冗长。我想简单说明一下,对于给定的属性,a应该是[Int],或者a应该是Sum Int

类似这样的假设语法:

testProperty "Associativity law, [Int]" (associativityLaw :: a = [Int]),
testProperty "Associativity law, Sum Int" (associativityLaw :: a = Sum Int)

是否可以通过GHC语言扩展来做到这一点?

我的实际问题涉及类型较高的类型,我希望能够指出例如f a[Int],或者f aMaybe String

我知道this answer,但至少如此处所述,这两个选项(ProxyTagged)似乎太尴尬,无法真正解决问题。

1 个答案:

答案 0 :(得分:4)

您可以使用TypeApplications来绑定类型变量,如下所示:

{-# LANGUAGE TypeApplications #-}

associativityLaw @[Int]

如果您提到类型较高的类型,并且想将f a绑定到[Int],则必须绑定类型变量fa分别:

fmap @[] @Int

对于具有多个类型变量的函数,可以按以下顺序应用args:

f :: a -> b -> Int

-- bind both type vars    
f @Int @String

-- bind just the first type var, and let GHC infer the second one
f @Int

-- bind just the second type var, and let GHC infer the first one
f @_ @String

有时候类型变量的“顺序”可能并不明显,但是您可以使用:type +v并向GHCi询问更多信息:

λ> :t +v traverse
traverse
  :: Traversable t =>
     forall (f :: * -> *) a b.
     Applicative f =>
     (a -> f b) -> t a -> f (t b)

在标准的haskell中,类型变量的“顺序”无关紧要,因此GHC可以为您弥补。但是在TypeApplications存在的情况下,顺序很重要:

map :: forall b a. (a -> b) -> ([a] -> [b])
-- is not the same as
map :: forall a b. (a -> b) -> ([a] -> [b])

因此,在使用高度参数化的代码时,或者您希望用户要在函数上使用TypeApplications时,您可能希望显式设置vars类型的顺序,而不是让GHC用ExplicitForAll为您定义一个订单:

{-# LANGUAGE ExplicitForAll #-}

map :: forall a b. (a -> b) -> ([a] -> [b])

在Java或c#中感觉很像<T1, T2>