fakedrake
fakedrake

Reputation: 6856

Illegal polymorphic or qualified type in typeclass

The following file Poly.hs file

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE RankNTypes #-}
module Poly () where

type ListModifier s = forall a. s -> [a] -> [a]

instance Monoid (ListModifier s) where
  mempty = const id
  mappend f g st = f st . g st

Gets the typechecker to complain:

Poly.hs:8:10: Illegal polymorphic or qualified type: ListModifier s …
    In the instance declaration for ‘Monoid (ListModifier s)’
Compilation failed.

Initially I though it couldn't compose the rank 2 types but:

λ> :t (undefined :: forall a . a -> String ) . (undefined :: forall b . String -> b)
(undefined :: forall a . a -> String ) . (undefined :: forall b . String -> b)
  :: String -> String

I feel the Poly module is in some way inherently inconsistent but I can't put my finger on the problem.

Upvotes: 0

Views: 124

Answers (1)

Alexis King
Alexis King

Reputation: 43892

ListModifier is a type alias, not a “real” type. Type aliases are essentially macros at the type level, always expanded by the typechecker before actually typechecking. That means your instance declaration is equivalent to the following:

instance Monoid (forall a. s -> [a] -> [a]) where

Even if that were allowed, it would overlap with the existing Monoid (a -> b) instance, so it still wouldn’t work. The larger problem, however, is that you can’t have an instance defined on a forall-quantified type because it wouldn’t make sense from the perspective of instance resolution.

What you could do instead is define a fresh type instead of a type alias using newtype:

newtype ListModifier s = ListModifier (forall a. s -> [a] -> [a])

Now you can define a Monoid instance, since typeclass resolution only needs to look for the ListModifier type, which is much simpler to match on:

instance Monoid (ListModifier s) where
  mempty = ListModifier (const id)
  mappend (ListModifier f) (ListModifier g) = ListModifier (\st -> f st . g st)

Alternatively, you could keep your type alias and define a newtype with a different name, like ReifiedListModifier, then define an instance on that, and you could only do the wrapping when you need to store a ListModifier in a container or use a typeclass instance.

Upvotes: 7

Related Questions