2

Imagine I want to define a vector space typeclass. I do the following (inspired by Yampa):

{-# LANGUAGE TypeFamilies           #-}
{-# LANGUAGE FlexibleContexts       #-}
{-# LANGUAGE FlexibleInstances      #-}
{-# LANGUAGE UndecidableInstances   #-}

class Fractional (Groundfield v) => VectorSpace v where
    type Groundfield v
    (^+^)      :: v -> v -> v
    zeroVector :: v
    (^*)       :: v -> Groundfield v -> v

That works perfectly. Now for some common instances. Of course, a tuple of fractionals is a vector space:

instance Fractional a => VectorSpace (a,a) where
    type Groundfield (a,a) = a
    (a, b) ^+^ (c, d)      = (a + c, b + d)
    zeroVector             = (0,0)
    (a, b) ^* c            = (a * c, b * c)

Or even simpler, a fractional is a vector space over itself:

instance Fractional a => VectorSpace a where
    type Groundfield a = a
    (^+^)              = (+)
    zeroVector         = 0
    (^*)               = (*)

Each of the instances itself is perfectly valid. But if I put them in the same module, I get this problem:

VectorSpielwiese.hs:15:10:
  Conflicting family instance declarations:
    Groundfield (a, a) -- Defined at VectorSpielwiese.hs:15:10
    Groundfield a -- Defined at VectorSpielwiese.hs:21:10

I realise that matching on a in the second instance definition catches a tuple pattern as well. But I would have expected that I've already matched that pattern if I write the instance definition for (a,a) before. Apparently not! How can I achieve this?

Turion
  • 5,684
  • 4
  • 26
  • 42
  • 4
    You cannot, unless you change the second instance to have a newtype wrapper, or change `GroundField` to a data family. I doubt either of these are what you are looking for. The approach which is generally taken in this sort of situation is to have a "default" implementation which corresponds to this "trivial" vector space, and then you simply write `instance VectorSpace Float; instance VectorSpace Double;...`. You will probably need `DefaultSignatures` for this. – user2407038 Feb 09 '16 at 12:36
  • 2
    That's how [the standard vector space class does it](http://hackage.haskell.org/package/vector-space-0.10.2/docs/Data-VectorSpace.html): it gives individual “primitive instances” for `Float`, `Double`, ... `CIntMax` and only makes the compound instances polymorphic. – leftaroundabout Feb 09 '16 at 13:07
  • @leftaroundabout, yes, that's when I started forking and thought to myself "This is a lot of repetition. Let's improve." – Turion Feb 09 '16 at 21:38
  • @Turion: yeah, it's really very mechanical repetition, you could well implement it with a simple CPP macro. I agree this isn't so nice... basically what we want is a single instance that works for a _closed set_ of types. [See this post](http://stackoverflow.com/questions/17849870/closed-type-classes), perhaps you can use it for a better implementation of the instances. – leftaroundabout Feb 10 '16 at 12:50

1 Answers1

1

With the help of GHC's (relatively) new closed type families, I think we can pull it off. The following all typechecks:

type family Groundfield v where
    Groundfield (a,a) = a
    Groundfield a = a

class Fractional (Groundfield v) => VectorSpace v where
    (^+^)      :: v -> v -> v
    zeroVector :: v
    (^*)       :: v -> Groundfield v -> v

instance (Fractional (Groundfield a), Num a, a ~ Groundfield a) => VectorSpace a where
    (^+^)              = (+)
    zeroVector         = 0
    (^*)               = (*)

instance Fractional a => VectorSpace (a,a) where
    (a, b) ^+^ (c, d)      = (a + c, b + d)
    zeroVector             = (0,0)
    (a, b) ^* c            = (a * c, b * c)
sclv
  • 38,665
  • 7
  • 99
  • 204
  • Wow! But what if the two instances are in separate modules? – Turion Mar 12 '16 at 09:25
  • 1
    The closed type family forces all the instances of Groundfield to be declared at once. But instances of VectorSpace can be declared anywhere. – sclv Mar 12 '16 at 20:32