Reputation: 11
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
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 Double
s or Int
s (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