I sometimes have a set of tests that I want to run on a bunch of different types. Here's a simple example of how I usually do that:
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)
]
But I'd rather avoid the repetition of listing the tests for each type. (Perhaps there are a lot of tests involved.) I'd like to define the lists of tests once, and then instantiate it for each type. Something like this...
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"
Of course, that isn't valid Haskell. I have a feeling there might be some magic type-level programming technique that I could use to accomplish this. I skimmed Maguire's Thinking With Types but I still couldn't see how to solve this problem. Any suggestions?
If there's a technique that would work in principle, but wouldn't play nicely with QuickCheck's reflection stuff, that's fine -- I'm more interested in building my type-level programming skills than solving this particular problem.
CodePudding user response:
You're quite close:
{-# 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
You don't even need to specifically name floatTests
and doubleTests
; you could write something like
allTests = numberTests @Float numberTests @Double
if you wanted.