I'm pretty new to Haskell, so I hope this isn't a stupid question. I have this data type:
data N = I Int | D Double deriving (Show, Eq)
I'm trying to write a function with the signature (Num a) => (a -> a -> a) -> N -> N -> N
which applies the function to the numbers within the N
s and returns an N with the result. If the N
s are both D
s, it should just apply the function and return a D
; if one is an I
and the other is a D
, it should convert the Int
in the I
to a Double
, apply the function to the two Double
s, and return a D
; and if both are I
s, it should apply the function and return an I
. Here's the (broken) code I have so far:
widen :: N -> N -> (N, N)
widen (I i) d@(D _) = (D (fromIntegral i), d)
widen d@(D _) i@(I _) = widen i d
widen x y = (x, y)
numOp :: (Num a) => (a -> a -> a) -> N -> N -> N
numOp op x y = case widen x y of (D x', D y') -> D $ x' `op` y'
(I x', I y') -> I $ x' `op` y'
I get an error on both lines of numOp
, though. The first one is:
Could not deduce (a ~ Double)
from the context (Num a)
bound by the type signature for
numOp :: Num a => (a -> a -> a) -> N -> N -> N
at <line num>
In the second argument of `($)', namely x' `op` y'
In the expression: D $ x' `op` y'
In a case alternative: (D x', D y') -> D $ x' `op` y'
And the second:
Couldn't match type `Double' with `Int'
Expected type: Int
Actual type: a
In the second argument of `($), namely x' `op` y'
In the expression: I $ x' `op` y'
In a case alternative: (I x', I y') -> I $ x' `op` y'
I'm pretty sure I understand what both errors mean; I think the first one is saying that the information in my type signature isn't enough for GHC to assume that op
returns a Double
, which is required by the D
value constructor, and the second one is saying that since the first line implies that a
is Double
, this line can't use a value of type a
as though it's an Int
. I don't have any idea where to start looking for the right way to do this, though.
If it helps, the reason I'm trying to get this to work is that I'm following along with the Write Yourself a Scheme tutorial; all the examples in the tutorial (specifically in the Evaluation section) only deal with integers, but as an exercise I'd like to add the ability to support both integral and floating point numbers so that e.g. (+ 1 2.5 2.5)
returns 6.0
and (+ 1 2 3)
returns 6
. If I'm thinking about this the wrong way or there's an easier way to accomplish it, I'd love to hear suggestions.
The signature
numOp :: (Num a) => (a -> a -> a) -> N -> N -> N
says that numOp
takes any monomorphic function of type a -> a -> a
for every specific instance of Num
and two N
s and from that computes an N
. So for example, a function of type
Complex Float -> Complex Float -> Complex Float
or
approxRational :: RealFrac a => a -> a -> Rational
(specialised to a = Rational
) would be legitimate first arguments.
What you need is a polymorphic function that can handle all Num
instances as the first argument, i.e. the rank 2 type
numOp :: (forall a. Num a => a -> a -> a) -> N -> N -> N
(you need the RankNTypes
language extension for that).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With