Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell: Variable Type confusion between Double and Int

Tags:

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?

like image 856
Fiendish_Foe Avatar asked May 13 '20 14:05

Fiendish_Foe


People also ask

How does type determine in Haskell?

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.

What does double mean in Haskell?

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

Can you change variables in Haskell?

You can't. Once a variable has a value, it has that value forever. Haskell is not like C.

What does () mean in Haskell?

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


2 Answers

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 Ints 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 Doubles.

It's just that it doesn't accept Ints:

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)
like image 109
luqui Avatar answered Oct 14 '22 03:10

luqui


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.

like image 33
sepp2k Avatar answered Oct 14 '22 03:10

sepp2k