Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a generic Complex type in haskell?

Tags:

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.

like image 372
ksceriath Avatar asked Oct 19 '17 09:10

ksceriath


1 Answers

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.

like image 176
leftaroundabout Avatar answered Sep 21 '22 07:09

leftaroundabout