Home > Enterprise >  Creating an array of things based on a type when you don't have an input parameter to tell you
Creating an array of things based on a type when you don't have an input parameter to tell you

Time:05-25

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.

  • Related