Zoey Hewll
Zoey Hewll

Reputation: 5385

Different type constraints for the same instance

I'm defining my own complex number data type as a learning exercise, and I've run into trouble overloading abs along with the other members of Num. As far as I know, only one instance definition is allowed per typeclass, but if I could I'd do something like this:

instance Num a => Num (Complex a) where
    (+) (Complex ra ia) (Complex rb ib) = Complex (ra + rb) (ia + ib)
    (-) (Complex ra ia) (Complex rb ib) = Complex (ra - rb) (ia - ib)
    (*) (Complex ra ia) (Complex rb ib) = Complex (ra*rb - ia*ib) (ra*ib + rb*ia)
    fromInteger r = Complex (fromInteger r) 0

instance Floating a => Num (Complex a) where
    abs (Complex r i) = Complex (sqrt $ r^2 + i^2) 0

or

instance Floating a => Floating (Complex a) where
    abs (Complex r i) = Complex (sqrt $ r^2 + i^2) 0

Because none of the members other than abs require Floating types, and I don't want to limit them to only Floating types, but the abs function is super important, and I don't want to unnecessarily exclude it.
Is there some way I can have the functions (+), (-), and (*) work on all numeric types, while still implementing abs?

According to 7.6.3.4. Overlapping instances in the GHC system guide, multiple instances can overlap if they differ on the type constraint(?) outside of the context (eg instance C [a] and instance C [Int]), with the compiler choosing the most specific instance for a given case, but it doesn't mention anything about only the context differing (eg instance C [a] and instance Integral a => C [a]).

Upvotes: 5

Views: 599

Answers (1)

Alec
Alec

Reputation: 32309

The main source of pain here is that the Prelude's number hierarchy was defined to not be too complex - for most things, it works just fine. This is one of those edge cases where is doesn't really (although as @leftaroundabout points out, I'm not sure there are many applications for a Complex over something that is not Floating).

Your options are to

  • Add a Floating a constraint on Num (Complex a). This is what feels most natural to me and makes the most sense from a type class perspective - shoehorning in instance Num a => Num (Complex a) breaks the Num abstraction because it has no notion of abs.
  • Use a finer-grain numeric hierarchy. The numeric-prelude comes to mind. In that, you will find the following (spread out over multiple modules):

    class (Field.C a) => Algebraic.C a where
      sqrt :: a -> a
    
    class (Ring.C a) => Field.C a where
      (/)           :: a -> a -> a
      recip         :: a -> a
      fromRational' :: Rational -> a
      (^-)          :: a -> Integer -> a
    
    class (Ring.C a) => Absolute.C a where
      abs    :: a -> a
      signum :: a -> a
    
    class (Additive.C a) => Ring.C a where
      (*)         :: a -> a -> a
      one         :: a
      fromInteger :: Integer -> a 
      (^)         :: a -> Integer -> a
    
    class Additive.C a where
      zero     :: a
      (+), (-) :: a -> a -> a
      negate   :: a -> a
    

    In your case, you would be making instances instance Additive.C a => Additive.C (Complex a), instance Ring.C a => Ring.C (Complex a), and instance Algebraic.C a => Absolute.C (Complex a).

  • If I haven't yet managed to convince you to abandon this madness, feel free to check out this page on advanced overlap. Aside from being complex and boilerplate heavy (and needing to turn on a ton of language extensions), this solution isn't quite general (you'll still have to hand-pick which types go to which instance).

Upvotes: 5

Related Questions