有时我有一组测试,我想在一堆不同的类型上运行。这里有一个简单的例子,说明我通常是怎么做的:
import Test.Framework (Test)
import Test.Framework.Providers.QuickCheck2 (testProperty)
import Test.QuickCheck
additionCommutes :: (Eq a, Num a) => a -> a -> Bool
additionCommutes x y = x + y == y + x
divisionByItselfIsOne :: (Eq a, Ord a, Fractional a) => a -> Property
divisionByItselfIsOne x = x > 0 ==> x / x == 1
-- pretend rounding errors won't occur, this is just an example
floatTests :: [Test]
floatTests = [
testProperty "float addition commutes"
(additionCommutes :: Float -> Float -> Bool),
testProperty "float divided by itself is 1"
(divisionByItselfIsOne :: Float -> Property)
]
doubleTests :: [Test]
doubleTests = [
testProperty "double addition commutes"
(additionCommutes :: Double -> Double -> Bool),
testProperty "double divided by itself is 1"
(divisionByItselfIsOne :: Double -> Property)
]
但是我宁愿避免重复列出每种类型的测试。(可能会涉及很多测试。)我想定义一次测试列表,然后为每种类型实例化它。像这样…
numberTests :: (Eq a, Ord a, Num a, Fractional a) => [Test] for the type "a"
numberTests = [
testProperty "double addition commutes"
(additionCommutes :: a -> a -> Bool),
testProperty "double divided by itself is 1"
(divisionByItselfIsOne :: a -> Property)
]
floatTests = numberTests :: [Test] for the type "float"
doubleTests = numberTests :: [Test] for the type "double"
当然,这在Haskell中是无效的。我有一种感觉,可能有一些神奇的类型级编程技术,我可以用它来完成这个。我浏览了马奎尔的《用类型思考》,但我仍然看不出如何解决这个问题。有什么建议吗?
如果有一种技术可以在原则上工作,但不能很好地与QuickCheck的反射东西一起工作,那很好——我更感兴趣的是建立我的类型级编程技能,而不是解决这个特殊的问题。
你很接近了:
{-# Language AllowAmbiguousTypes #-}
{-# Language ScopedTypeVariables #-}
{-# Language TypeApplications #-}
-- vvvvvvvvv
numberTests :: forall a. (Eq a, Ord a, Num a, Fractional a) => [Test]
numberTests = -- just like in the question
-- vvvvvvv
floatTests = numberTests @Float
doubleTests = numberTests @Double
你甚至不需要特别命名floatTests
和doubleTests
;可以写成
allTests = numberTests @Float ++ numberTests @Double
如果你想的话