Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Applying a function with arguments that could be either Ints or Doubles

Tags:

types

haskell

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 Ns and returns an N with the result. If the Ns are both Ds, 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 Doubles, and return a D; and if both are Is, 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.

like image 957
jcsmnt0 Avatar asked Dec 31 '12 01:12

jcsmnt0


1 Answers

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 Ns 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).

like image 157
Daniel Fischer Avatar answered Oct 10 '22 04:10

Daniel Fischer