使用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 a
是Maybe String
。
我知道this answer,但至少如此处所述,这两个选项(Proxy
和Tagged
)似乎太尴尬,无法真正解决问题。
答案 0 :(得分:4)
您可以使用TypeApplications
来绑定类型变量,如下所示:
{-# LANGUAGE TypeApplications #-}
associativityLaw @[Int]
如果您提到类型较高的类型,并且想将f a
绑定到[Int]
,则必须绑定类型变量f
和a
分别:
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>