Search code examples
haskellnewtype

Haskell: Implementing Num instaces for user-defined newtypes


I have a Pair newtype which is just a tuple of Doubles and I want to be able to use the arithmetic operators with this type. Here's my code, which doesn't compile:

module Test where                                   

newtype Pair = Pair (Double, Double)                

instance Num Pair where                             
  Pair (x1, y1) + Pair (x2, y2) = Pair (x1+x2, y1+y2
  Pair (x1, y1) - Pair (x2, y2) = Pair (x1-x2, y1-y2
  Pair (x1, y1) * Pair (x2, y2) = Pair (x1*x2, y1*y2
  abs (Pair (x, y)) = Pair (abs x, abs y)           
  signum (Pair (x, y)) = Pair (signum x, signum y)  
  fromInteger i = Pair (fromInteger i, fromInteger i)

func :: Pair -> Double -> Pair                      
func p d = p * d                                    

Here's the erro GHC throws:

[1 of 1] Compiling Test             ( test.hs, interpreted )

test.hs:14:16:
Couldn't match expected type `Pair' with actual type `Double'
In the second argument of `(*)', namely `d'
In the expression: p * d
In an equation for `func': func p d = p * d
Failed, modules loaded: none.

I had thought that defining from Integer and * would have been enough here, can someone advise what I'm doing wrong?

UPDATE

If I add the following instance:

instance Fractional Pair where                          
  Pair (x1, y1) / Pair (x2, y2) = Pair (x1/x2, y1/y2)   
  fromRational r = Pair (fromRational r, fromRational r)

then my function still doesn't compile, but in ghci, I can do

> Pair (1.0, 2.0) * 3.4
Pair (3.4,6.8)

but not:

> Pair (1.0, 2.0) * 3.4 :: Double

<interactive>:206:1:
Couldn't match expected type `Double' with actual type `Pair'
In the return type of a call of `Pair'
In the first argument of `(*)', namely `Pair (1.0, 2.0)'
In the expression: Pair (1.0, 2.0) * 3.4 :: Double

I'm still struggling to understand why this is happening.


Solution

  • You're trying to use implicit fromInteger on an argument, which makes zero sense.

    Firstly, fromInteger (or fromRational) is only implicit on Literals. Ie:

    6 = fromInteger 6
    4.1 = fromRational 4.1
    

    But this is not true:

    a = fromInteger a  -- Not true!
    

    Your argument d is not a literal, so you will have to manually use realToFrac :: (Real a, Fractional b) => a -> b:

    func :: Pair -> Double -> Pair
    func p d = p * realToFrac d