views:

557

answers:

3

So, I have a pair of typeclasses that I'll be using a lot together, and I want to avoid specifying both each time. Basically, instead of putting

:: (Ord a, Fractional a, Ord b, Fractional b, ... Ord z, Fractional z) =>

at the beginning of all my type specifications, I'd rather put

:: (OrdFractional a, OrdFractional b, ... OrdFractional z)

So, my initial idea on how to do this was to just declare a new typeclass

module Example where

class (Fractional a, Ord a) => OrdFractional a

example :: (OrdFractional a, OrdFractional b) => (a,b) -> (a,b) -> (a,b) -> Bool
example (x1,y1) (x2,y2) (x3,y3) = (x1/x2 < x2/x3) && (y1/y2 < y2/y3)

But this didn't work as automagically as I wished it would:

% ghci
Prelude> :l Example.hs
Ok, modules loaded: Example.
Prelude Example> example (1::Float,3::Float) (2,2) (3,1)

<interactive>:1:0:
    No instance for (OrdFractional Float)
      arising from a use of `example' at <interactive>:1:0-39
    Possible fix:
      add an instance declaration for (OrdFractional Float)
    In the expression: example (1 :: Float, 3 :: Float) (2, 2) (3, 1)
    In the definition of `it':
        it = example (1 :: Float, 3 :: Float) (2, 2) (3, 1)

Manually creating instances seems like a drag so, next, I thought I might try to automatically create instances:

module Example where

class OrdFractional a
instance (Fractional a, Ord a) => OrdFractional a

example :: (OrdFractional a, OrdFractional b) => (a,b) -> (a,b) -> (a,b) -> Bool
example (x1,y1) (x2,y2) (x3,y3) = (x1/x2 < x2/x3) && (y1/y2 < y2/y3)

But the compiler didn't like that:

ghc -c Example.hs

Example.hs:4:0:
    Illegal instance declaration for `OrdFractional a'
        (All instance types must be of the form (T a1 ... an)
         where a1 ... an are type *variables*,
         and each type variable appears at most once in the instance head.
         Use -XFlexibleInstances if you want to disable this.)
    In the instance declaration for `OrdFractional a'

So is there a way I can do this?

+3  A: 

What you want is a class alias. There is a proposal to add it to Haskell at http://repetae.net/recent/out/classalias.html

CesarB
+1  A: 

When the compiler says "Use -XFlexibleInstances", you should try adding

{-# LANGUAGE FlexibleInstances #-}

to the top of your source (and go read the documentation to learn what it does, of course!).

In this specific case, this will make your code work:

{-# LANGUAGE FlexibleInstances, UndecidableInstances #-}

Flexible instances are required in order to enable the => context on the instance head, and undecidable instances are required because the compiler, when handling an OrdFractional a context, can end adding Fractional a and Ord a to the context -- which doesn't directly help with finally determining a, and under suitably horrible circumstances, typechecking may diverge; the compiler really doesn't like that. (You probably wouldn't like it if the compiler went on forever or ran out of memory, either.)

ephemient
+1  A: 

No.

Your solution of a superclass implying the other classes is the closest to what you want that is possible in Haskell. Even though that requires manual instances of that new class it is sometimes used, for example in the rewriting library.

As CesarB mentioned class aliases do what you want (and more), but they're just a proposal that's been around for years now and have never been implemented, probably because there are numerous problems with it. Instead, various other proposals have popped up, but none of those were implemented either. (For a list of those proposals, see this Haskellwiki page.) One of the projects at Hac5 was to modify the GHC to include a small subset of class aliases called context synonyms (which do exactly what you are asking for here and nothing more), but sadly it was never finished.

Martijn