Search code examples
haskellquickcheckhspec

Abstracting Hspec tests


I am going through "Haskell programming from first principles" and I found myself writing code in the following fashion over and over:

type IntToInt = Fun Int Int
type TypeIdentity = ConcreteFunctorType Int -> Bool
type TypeComposition = ConcreteFunctorType Int -> IntToInt -> IntToInt -> Bool

checkSomething :: IO ()
checkSomething = hspec $ do
        describe "Some functor" $ do
            it "identity property" $ do
                property $ (functorIdentity :: TypeIdentity)
            it "composition property" $ do
                property $ (functorComposition :: TypeComposition)

I tried abstracting this but at my level I am not able to figure out a way to make it work

what I would have liked to accomplish was something like this

checkFunctor :: (Functor f) => String -> f a -> IO ()
checkFunctor description f = hspec $ do
        describe description $ do
            it "identity property" $ do
                property $ (functorIdentity :: f a -> TypeIdentity)
            it "composition property" $ do
                property $ ( functorComposition :: f a -> TypeComposition)

EDIT: After Sapanoia's answer I tried as follows

type TypeIdentity = Bool
type TypeComposition = Fun Int Int -> Fun Int Int -> Bool


checkFunctor :: forall f a. (Functor f) => String -> f a -> IO ()
checkFunctor description f = hspec $ do
    describe description $ do
        it "identity property" $ do
            property $ (functorIdentity :: f a -> TypeIdentity)
        it "composition property" $ do
            property $ (functorCompose' :: f a -> TypeComposition)

but I get the following error

FunctorCheck.hs:22:25: error:
• Couldn't match type ‘a’ with ‘Int’
  ‘a’ is a rigid type variable bound by
    the type signature for:
      checkFunctor :: forall (f :: * -> *) a.
                      Functor f =>
                      String -> f a -> IO ()
    at FunctorCheck.hs:16:26
  Expected type: f a -> TypeComposition
    Actual type: f Int -> Fun Int Int -> Fun Int Int -> Bool

It then becomes quite complicated for me to define the types to generate the arbitrary values and functions.

Is there a way I can bind the type of checkFunctor to a specific type like the following?

checkFuntor :: checkFunctor :: forall f Int. (Functor f) => String -> f a -> IO ()

of course I tried this and it gives me a parse error, I assume it's just me not using 'forall' correctly.


Solution

  • Since you did not add an error message, I assume the problem is a type error where (functorIdentity :: f a -> TypeIdentity) is defined. The problem is that the f introduced here is new and different from the f in the top-level signature. To remedy this, enable the following extension:

    {-# LANGUAGE ScopedTypeVariables #-}
    

    And change the signature of checkFunctor to:

    checkFunctor :: forall f a. (Functor f) => String -> f a -> IO ()
    

    forall introduces new type variables. Without ScopedTypeVariables and an explicit forall it is always present implicitly, and (functorIdentity :: f a -> TypeIdentity) becomes (functorIdentity :: forall f a. f a -> TypeIdentity). However, you do not want a forall here, because you want the type variables f and a to be the same as the top-level ones.