1

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.

D. Amoroso
  • 170
  • 2
  • 10
  • 1
    Yes, it is possible. You can do this using `ScopedTypeVariables`, and it can be made even nicer by using GHC 8’s `TypeApplications` along with `AllowAmbiguousTypes`. – Alexis King Dec 08 '16 at 00:08
  • `forall` is used to introduce a new type *variable*. *Concrete* types such as `Int` need not be introduced, just insert them instead of a *type variable*. Your example then becomes: `checkFunctor :: forall f. (Functor f) => String -> f Int -> IO ()`. In the body of `checkFunctor` you need accordingly: `functorIdentity :: f Int -> TypeIdentity`. – sapanoia Dec 08 '16 at 18:44

1 Answers1

1

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.

sapanoia
  • 789
  • 4
  • 14