ksceriath
ksceriath

Reputation: 195

How to create a generic Complex type in haskell?

I want to create a Complex type to represent complex numbers.

Following works:

Prelude> data Complex = Complex Int Int

Prelude> :t Complex
Complex :: Int -> Int -> Complex

How can I change this to accept any Num type, instead of just Int.

I tried following:

Prelude> data Complex a = Num a => Complex a a

but got this:

* Data constructor `Complex' has existential type variables, a context, or a specialised result type
    Complex :: forall a. Num a => a -> a -> Complex a
    (Use ExistentialQuantification or GADTs to allow this)
* In the definition of data constructor `Complex'
  In the data type declaration for `Complex'

I'm not really sure what to make of this error. Any help is appreciated.

Upvotes: 8

Views: 1712

Answers (3)

leftaroundabout
leftaroundabout

Reputation: 120711

Traditional data in Haskell is just that: data. It doesn't need to know anything about the properties of its fields, it just needs to be able to store them. Hence there's no real need to constrain the fields at that point; just make it

data Complex a = Complex !a !a

(! because strict fields are better for performance).

Of course when you then implement the Num instance, you will need a constraint:

instance (Num a) => Num (Complex a) where
  fromInteger = (`Complex`0) . fromInteger
  Complex r i + Complex ρ ι = Complex (r+ρ) (i+ι)
  ...

...in fact, you need the much stronger constraint RealFloat a to implement abs, at least that's how the standard version does it. (Which means, Complex Int is actually not usable, not with the standard Num hierarchy; you need e.g. Complex Double.)

That said, it is also possible to bake the constraint in to the data type itself. The ExistentialTypes syntax you tried is highly limiting though and not suitable for this; what you want instead is the GADT

data Complex a where
   Complex :: Num a => a -> a -> Complex a

With that in place, you could then implement e.g. addition without mentioning any constraint in the signature

cplxAdd :: Complex a -> Complex a -> Complex a
cplxAdd (Complex r i) (Complex ρ ι) = Complex (r+ρ) (i+ι)

You would now need to fulfill Num whenever you try to construct a Complex value though. That means, you'd still need an explicit constraint in the Num instance.

Also, this version is potentially much slower, because the Num dictionary actually needs to be stored in the runtime representation.

Upvotes: 13

lisyarus
lisyarus

Reputation: 15522

Type constructors cannot be constrained in pure Haskell, only functions can. So it is supposed that you declare

data Complex a = Complex a a

and then constrain functions, like

conjugate :: (Num a) => Complex a -> Complex a
conjugate (Complex x y) = Complex x (-y)

In fact, the type and constraint for conjugate can be derived by the compiler, so you can just define the implementation:

conjugate (Complex x y) = Complex x (-y)

However, if you really wish to constrain the type constructor Complex, you can turn on some extensions that enable it, namely ExistentialQuantification or GADTs, as the compiler suggests. To do this, add this line to the very beginning of your file:

{-# LANGUAGE ExistentialQuantification #-}

or

{-# LANGUAGE GADTs #-}

Those are called pragmas.

Upvotes: 3

Mark Seemann
Mark Seemann

Reputation: 233150

While you could, as the compiler message instructs, use ExistentialQuantification, you could also define the type like this:

data Complex a = Complex a a deriving (Show, Eq)

It's a completely unconstrained type, so perhaps another name would be more appropriate... This type seems to often be called Pair...

When you write functions, however, you can constrain the values contained in the type:

myFunction :: Num a => Complex a -> a
myFunction (Complex x y) = x + y

Upvotes: 2

Related Questions