The Haskell approach is to have different types for Complex Float
and Complex Int
rather than trying to unify them into one type. With type classes you can define all of these types at once:
data Complex a = C a a
instance Num a => Num (Complex a) where
(C x y) + (C u v) = C (x+u) (y+v)
(C x y) * (C u v) = C (x*u-y*v) (x*v+y*u)
fromInteger n = C (fromInteger n) 0
...
This at once defines Complex Int
, Complex Double
, Complex Rational
, etc. Indeed it even defines Complex (Complex Int)
.
Note that this does not define how to add a Complex Int
to a Complex Double
. Addition (+) still has the type (+) :: a -> a -> a
so you can only add a Complex Int
to a Complex Int
and a Complex Double
to another Complex Double
.
In order to add numbers of different types you have to explicitly convert them, e.g.:
addIntToComplex :: Int -> Complex Double -> Complex Double
addIntToComplex n z = z + fromIntegral n
Have a look at http://www.haskell.org/tutorial/numbers.html section 10.3 for more useful conversion functions between Haskell's numeric type classes.
Update:
In response to your comments, I would suggest focusing more on the operations and less on the types.
For example, consider this definition:
onethird = 1 / 3
This represents the generic "1/3" value in all number classes:
import Data.Ratio
main = do
putStrLn $ "as a Double: " ++ show (onethird :: Double)
putStrLn $ "as a Complex Double: " ++ show (onethird :: Complex Double)
putStrLn $ "as a Ratio Int: " ++ show (onethird :: Ratio Int)
putStrLn $ "as a Complex (Ratio Int): " ++ show (onethird :: Complex (Ratio Int))
...
In a sense Haskell let's the "user" decide what numeric type the expression should be evaluated as.