Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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]).

like image 465
Zoey Hewll Avatar asked Feb 06 '17 12:02

Zoey Hewll


People also ask

How many types of constraints are there?

There are five types of constraints: A NOT NULL constraint is a rule that prevents null values from being entered into one or more columns within a table. A unique constraint (also referred to as a unique key constraint) is a rule that forbids duplicate values in one or more columns within a table.

Can a generic class have multiple constraints?

Multiple interface constraints can be specified. The constraining interface can also be generic.

What are generic constraints?

The where clause in a generic definition specifies constraints on the types that are used as arguments for type parameters in a generic type, method, delegate, or local function. Constraints can specify interfaces, base classes, or require a generic type to be a reference, value, or unmanaged type.

What is the purpose of the class constraint on a type parameter?

Simply put this is constraining the generic parameter to a class (or more specifically a reference type which could be a class, interface, delegate, or array type).


1 Answers

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).

like image 77
Alec Avatar answered Oct 06 '22 01:10

Alec