Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

haskell

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.

like image 258
mhwombat Avatar asked Oct 31 '25 10:10

mhwombat


1 Answers

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.

like image 143
Daniel Wagner Avatar answered Nov 04 '25 02:11

Daniel Wagner



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!