As an uni assignment, I was supposed to write a function with the type declaration:
pi_approx :: Int -> Double
and this was my first attempt:
pi_approx :: Int -> Double
pi_approx x = let total = sum [1 / (y^2) | y <- [1..x]]
in sqrt (6 * total)
which threw the below error:
pi_approx.hs:4:8: error:
* Couldn't match expected type `Double' with actual type `Int'
* In the expression: sqrt (6 * total)
In the expression:
let total = sum [1 / (y ^ 2) | y <- ...] in sqrt (6 * total)
In an equation for `pi_approx':
pi_approx x = let total = sum ... in sqrt (6 * total)
|
4 | in sqrt (6 * total)
| ^^^^^^^^^^^^^^^^
I went step by step trying to understand why the interpreter treats it as an Int:
level1 :: Fractional a => a -> a
level1 x = 1 / (x^2)
So far so good.
level2 :: (Enum a, Fractional a) => a -> [a]
level2 x = [level1 y | y <- [1..x]]
Also as expected.
level3 :: (Enum a, Fractional a) => a -> a
level3 x = sum (level2 x)
No Int to be seen...
level4 :: (Enum a, Fractional a) => a -> a
level4 x = 6 * (level3 x)
and finally
level5 :: (Floating a, Enum a) => a -> a
level5 x = sqrt (level4 x)
And having gone down this rabbit hole I'm no closer to my answer. I got it running with fromIntegral
pi_approx :: Int -> Double
pi_approx x = let total = sum [(fromIntegral 1) / ((fromIntegral y)^ (fromIntegral 2)) | y <- [1..x]]
in sqrt (6*total)
but it got me nowhere nearer to understanding what caused the error in the first version. Could someone explain what I'm missing? Why was sqrt (6*total)
treated as an Int
?
If you need to figure out what the type of an object is in a Haskell program, I hope this is helpful. Note that if you are in GHCI, you can just put :type before your expression to determine the expression's type, or use :set +t to see the type of every expression in GHCI.
Double is the double-precision floating point type, a good choice for real numbers in the vast majority of cases. (Haskell also has Float , the single-precision counterpart of Double , which is usually less attractive due to further loss of precision.)
You can't. Once a variable has a value, it has that value forever. Haskell is not like C.
() is very often used as the result of something that has no interesting result. For example, an IO action that is supposed to perform some I/O and terminate without producing a result will typically have type IO () .
Because of the way typeclasses are type checked, the type error you were shown is not the most helpful one.
[ 1 / (y^2) | y <- [1..x] ]
So it is probably no surprise that, because x
is an Int
, [1..x]
is a list of Int
s and so y
is an Int
. Nor is it surprising that y^2
is an Int
. Where GHC loses us is when it decides that therefore 1 / (y^2)
is an Int
.
The reason is this: the type of the divide operator is
(/) :: (Fractional a) => a -> a -> a
That is, it takes the same type on both sides, and also returns it. So as soon as the type of y^2
is known to be Int
, we infer that 1
and the entire expression are Int
also. Only later, during the constraint checking phase, GHC will make sure that Int
is Fractional
, which of course it is not. For example:
recp :: Int -> Int
recp x = 1 / x
Will give you a better error
• No instance for (Fractional Int) arising from a use of ‘/’
• In the expression: 1 / x
In an equation for ‘recp’: recp x = 1 / x
But it didn't get that far when checking your function, because there was a type unification failure first.
Sometimes if you can't figure out an error it is helpful to remove the signature and see what type is inferred (more often, it's helpful to add more type signatures, but not always)
ghci> :t pi_approx
pi_approx :: (Floating a, Enum a) => a -> a
Without the signature, the function actually type-checks. It works, too:
ghci> pi_approx 100
3.1320765318091053
Here 100
is defaulted to be a Double
to satisfy the Fractional
constraint, and the whole definition goes through with everything as Double
s.
It's just that it doesn't accept Int
s:
ghci> pi_approx (100 :: Int)
<interactive>:1:1: error:
• No instance for (Floating Int) arising from a use of ‘pi_approx’
• In the expression: pi_approx (1 :: Int)
All of the level functions you've defined have the type a -> a
(or a -> [a]
) with some constraints on a
attached to them. So in all cases the type that comes out will be the type that went in (or a list thereof).
So when calling each of your level
functions there are two possible outcomes: either the call is ill-typed because the type of the argument does not meet the constraints or you'll get back a value of the same time (or a list thereof). Either way it's impossible that you'd put in an Int
and get back a Float
.
So sqrt (6*total)
was treated as an Int
because, as we've just discussed, the type that comes out will be the same type that goes in and the type that came in was Int
. Now the type that came in didn't actually meet the constraints, so you might expect an error about that. And without the type signature, you'd get exactly that error (once the function is called with an argument of type Int
). But with the type signature, the error about the return type not matching is detected before the constraints are checked, so that's why you get that error.
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