6
votes

The following is quoted from the GHC user guide (Haskell Platform 2012.4.0.0)...

7.6.1.3. Class method types

Haskell 98 prohibits class method types to mention constraints on the class type variable, thus:

class Seq s a where
  fromList :: [a] -> s a
  elem     :: Eq a => a -> s a -> Bool

The type of elem is illegal in Haskell 98, because it contains the constraint Eq a, constrains only the class type variable (in this case a). GHC lifts this restriction (flag -XConstrainedClassMethods).

However, I don't see any explanation of what this means. I can see two possibilities...

  1. The Seq type class implicitly gains the Eq a constraint from elem.
  2. The elem method cannot be used for type class Seq in cases where a is not a member of class Eq (or where it is a member, but that's unknown where elem is used).

I strongly suspect (2) because it seems potentially useful whereas (1) seems useless. (2) basically allows methods to be defined for cases where they can be supported, without limiting the typeclass to only being instanced for those cases.

The example seems to motivate exactly this - the idea being that elem is often a useful operation for sequences, too valuable to live without, yet we also want to support those sequences where elem is unsupportable, such as sequences of functions.

Am I right, or have I missed something? What are the semantics for this extension?

1
Interesting, constrained method types seem to be always accepted regardless of whether you have -XHaskell98 enabled. I filed a ticket.Mikhail Glushenkov
@Mikhail Glushenkov - I didn't think this was part of Haskell 2010 either (could be wrong).Steve314

1 Answers

7
votes

NOTE: it seems like there is a bug in GHC >= 7 that makes GHC accept constrained class methods even in Haskell 98 mode.

Additionally, the example from the manual is always accepted when MultiParamTypeClasses are enabled, regardless of whether the ConstrainedMethodTypes extension is on (also likely a bug).


In the type of elem:

elem     :: Eq a => a -> s a -> Bool

a and s are class type variables, and Eq a is a constraint on the class type variable a. As the manual says, Haskell 98 prohibits such constraints (FWIW, it also prohibits multi-parameter type classes). Therefore, the following code shouldn't be accepted in the Haskell 98 mode (and I think it's also prohibited in Haskell 2010):

class Compare a where
  comp :: Eq a => a -> a -> Bool

And indeed, GHC 6.12.1 rejects it:

Prelude> :load Test.hs
[1 of 1] Compiling Main             ( Test.hs, interpreted )

Test.hs:3:0:
    All of the type variables in the constraint `Eq a'
    are already in scope (at least one must be universally quantified here)
        (Use -XFlexibleContexts to lift this restriction)
    When checking the class method: comp :: (Eq a) => a -> a -> Bool
    In the class declaration for `Compare'
Failed, modules loaded: none.
Prelude> :set -XConstrainedClassMethods
Prelude> :load Test.hs
[1 of 1] Compiling Main             ( Test.hs, interpreted )
Ok, modules loaded: Main.

The idea is that superclass constraints should be used instead:

class (Eq a) => Compare a where
  comp :: a -> a -> Bool

Regarding semantics, you can easily check whether a class method constraint implicitly adds a superclass constraint with the following code:

{-# LANGUAGE ConstrainedClassMethods #-}
module Main where

class Compare a where
  comp :: (Eq a) => a -> a -> Bool
  someMethod :: a -> a -> a

data A = A deriving Show
data B = B deriving (Show,Eq)

instance Compare A where
  comp = undefined
  someMethod A A = A

instance Compare B where
  comp = (==)
  someMethod B B = B

Testing with GHC 6.12.1:

*Main> :load Test.hs
[1 of 1] Compiling Main             ( Test.hs, interpreted )
Ok, modules loaded: Main.
*Main> comp A

<interactive>:1:0:
    No instance for (Eq A)
      arising from a use of `comp' at <interactive>:1:0-5
    Possible fix: add an instance declaration for (Eq A)
    In the expression: comp A
    In the definition of `it': it = comp A
*Main> someMethod A A
A
*Main> comp B B
True

Answer: no, it doesn't. The constraint applies only to the method that has a constrained type. So you are right, it's the possibility #2.