Ramadoka
Ramadoka

Reputation: 364

how do I compare my data-type which might be either a Rational or Float

given that I have a data type of:

data Number = Float Float | Rational Integer Integer

why wouldn't this code works?

createComparator :: (a -> a -> Bool) -> Number -> Number -> Bool
createComparator comparator (Float f1) (Float f2) = comparator f1 f2
createComparator comparator (Float f) (Rational n d) = comparator f $ (fromIntegral n) / (fromIntegral d)
createComparator comparator (Rational n d) (Float f) = comparator ((fromIntegral n) / (fromIntegral d)) f
-- createComparator comparator (Rational n1 d1) (Rational n2 d2) = ... -- TODO

so, it basically take a comparator (>) or (<), and 2 Number, and return Bool.

e.g: 0.5 > 1/3 == True

or, 0.2 > 1/3 == False

but, I get this error.

Couldn't match expected type ‘a’ with actual type ‘Float’
  ‘a’ is a rigid type variable bound by
      the type signature for
        createComparator :: (a -> a -> Bool) -> Number -> Number -> Bool
      at Ramadoka/Parser/Number.hs:55:23
Relevant bindings include
  comparator :: a -> a -> Bool
    (bound at Ramadoka/Parser/Number.hs:56:20)
  createComparator :: (a -> a -> Bool) -> Number -> Number -> Bool
    (bound at Ramadoka/Parser/Number.hs:56:3)
In the first argument of ‘comparator’, namely ‘f1’
In the expression: comparator f1 f2

I could make it works by doing:

(|>|) :: Number -> Number -> Bool
(Float f1) |>| (Float f2) = f1 > f2
(Float f) |>| (Rational n d) = f > (fromIntegral n) / (fromIntegral d)
(Rational n d) |>| (Float f) = (fromIntegral n) / (fromIntegral d) > f
r1@(Rational _ _) |>| r2@(Rational _ _) = rationalCompare r1 r2 == GT

(|<|) :: Number -> Number -> Bool
(Float f1) |<| (Float f2) = f1 < f2
(Float f) |<| (Rational n d) = f < (fromIntegral n) / (fromIntegral d)
(Rational n d) |<| (Float f) = (fromIntegral n) / (fromIntegral d) < f
r1@(Rational _ _) |<| r2@(Rational _ _) = rationalCompare r1 r2 == LT

though, I'd still prefer to have:

(|>|) = createComparator (>)
(|<|) = createComparator (<)

Upvotes: 2

Views: 382

Answers (2)

Daniel Wagner
Daniel Wagner

Reputation: 153102

Turn on Rank2Types and write the type of the function this way:

createComparator :: (forall a. Ord a => a -> a -> Bool) -> Number -> Number -> Bool

As you wrote it, there is a particular type a for which you are passing in a comparator; whereas you want to pass in a function which is polymorphically a comparator for many types (hence must mention forall in its type).

Upvotes: 2

badcook
badcook

Reputation: 3739

Caveat: I do not have a Haskell compiler handy, so I can't check my work

The short answer is to just change the a to Float.

Let's play along though and see what happens. What your type signature is telling me is if I provide a function a -> a -> Bool for any possible a, your function will work. Even if I give you some ridiculous function such as String -> String -> Bool your function will do its job. Note that for any polymorphic a there is an implicit forall that Haskell hides on the left of your type signature.

"For any a you choose, you can give me an a -> a -> Bool and I'll give you a Number -> Number -> Bool."

forall a. (a -> a -> Bool) -> Number -> Number -> Bool

That's not what you want to say though presumably. You want your comparator to be of type Float -> Float -> Bool since you're converting your own personal Rational into a Float.

If you want to figure out how to get the polymorphism you originally desired, what you're looking for is higher rank types.

So before we start this journey, enable rank-n types with this at the top of your file.

{-# LANGUAGE RankNTypes #-}

You probably want to say something closer to the following, which involves moving the forall into a set of parentheses.

"If you give me a function such that for any chosen type a, I can compare the two and give you a Bool, then I can give you a Number -> Number -> Bool."

(forall a. a -> a -> Bool) -> Number -> Number -> Bool

Moving your forall into one layer of parentheses is what is known as a rank-2 type. If you nest another layer you get rank-3 and so on.

This still isn't what you want because the type constraint is too powerful. forall a. a -> a -> Bool must be constantly true or false to work for all possible a. What you probably want is a typeclass constraint to narrow down the a you care about.

(forall a. Ord a => a -> a -> Bool) -> Number -> Number -> Bool

In your particular case, even this is basically overkill and you could just go with Float as mentioned earlier, because that's ultimately what forall a. Ord a => a -> a -> Bool will specialize to.

And if you have that as your signature, I think you're done (don't have a Haskell compiler handy right now).

Upvotes: 3

Related Questions