Cogs
Cogs

Reputation: 11

Adding different number types in Haskell

I'm have been using Haskell for a while and I'm having some trouble with Numeric Types. Most of the time I can solve the after trying around a bit but this time i have been stumped for over two hours by this trivial piece of code.

I have these Functions:

takeLessEqual x = takeWhile (<=x)

leftHalf x = takeLessEqual x $ (map (\x -> ((x+0.5)*(x+0.5) + 0.75))) [1..]
-- Produces the list [3.0,7.0,13.0,21.0,31.0,43.0 ... (some number < x)]

rightHalf x = takeLessEqual x $ (map (\x -> if even x then x*x + 1 else x*x)) [1..]
-- Produces the list [1,5,9,17,25,37,49 ... (some number < x)]   

total x = (sum $ rightHalf x) + (sum $ leftHalf x)
-- total 10 Should produce some number 25 or 25.0

It loads without error in to ghci, but when i try to evaluate:

*> leftHalf 10 
[3.0,7.0]
it :: (Ord a, Fractional a, Enum a) => [a]

*> rightHalf 10
[1,5,9]
it :: Integral a => [a]

*> total 10

<interactive>:150:1: error:
• Ambiguous type variable ‘a0’ arising from a use of ‘it’
  prevents the constraint ‘(Fractional a0)’ from being solved.
  Probable fix: use a type annotation to specify what ‘a0’ should be.
  These potential instances exist:
    instance Fractional Double -- Defined in ‘GHC.Float’
    instance Fractional Float -- Defined in ‘GHC.Float’
    ...plus one instance involving out-of-scope types
    (use -fprint-potential-instances to see them all)
• In the first argument of ‘print’, namely ‘it’
  In a stmt of an interactive GHCi command: print it

And I have tried adding type annotations at various points and converting types with toInteger and fromIntegral with no success.

What am I doing wrong and how do I fix it?

Upvotes: 1

Views: 468

Answers (1)

David Young
David Young

Reputation: 10783

Firstly, I would like to recommend that you definitely follow GHC's suggestion of adding type signatures. I recommend always giving type signatures to top-level definitions. This will make the error messages significantly more clear.

If we go ahead and do that, assuming that the Fractional type you want is Double and the Integral type is Int, we have

takeLessEqual :: Ord a => a -> [a] -> [a]
takeLessEqual x = takeWhile (<=x)

leftHalf :: Double -> [Double] -- This must be some `Fractional` type since we are doing things like adding by 0.5
leftHalf x = takeLessEqual x $ (map (\x -> ((x+0.5)*(x+0.5) + 0.75))) [1..]

rightHalf :: Int -> [Int]      -- This must be some `Integral` type since we are using `even`
rightHalf x = takeLessEqual x $ (map (\x -> if even x then x*x + 1 else x*x)) [1..]

-- total :: ?
total x = (sum $ rightHalf x) + (sum $ leftHalf x)

Note that there is no type that is both Integral and Fractional, so we cannot use the same number type everywhere here.

The problem now is that the two sides of + in total are not the same type, even though they must be ((+) :: Num a => a -> a -> a).

Now, you need to decide if you want to end up using Doubles or Ints (or a combination. I will assume you want Double for both the input and the output):

total :: Double -> Double

Now, we see that rightHalf needs an Int, so we must use either ceiling, floor or round to convert the argument from a Double. The result we get back is a [Int] fed into sum, which gives us an Int (since sum :: Num a => [a] -> a).

Assuming ceiling gives the rounding we want, we end up with:

total x = (fromIntegral . sum . rightHalf $ ceiling x) + (sum $ leftHalf x)

On a stylistic note, I would also suggest writing ... + sum (leftHalf x) instead of ... + (sum $ leftHalf x).

In general, if you want to go from a fractional type to an integral type, you will want to use floor, ceiling or round. If you want to go from an integral type to anything else (usually a fractional type, but possible some different integral type), you will want fromIntegral.

You could do the conversions in a different spot (in leftHalf and/or rightHalf). It seems like those two functions should stay floating and integral respectively though, since you would have to choose a rounding method (when the function that calls those two should probably be able to decide on a rounding method).

Upvotes: 6

Related Questions