1
votes

I'm new to Haskell and I'm having trouble understanding exactly which type annotations or which type signature is needed to get this to work. This test function itself is useless, but if I can understand how to get it to work, I think it'd help me to understand haskell types better.

test 0 = -1
test n = test (floor (n / 10))

This question seems to be related: Haskell: Why does RealFrac not imply Fractional?

But I can't figure out how to get my example to work. Here's the error I see when I run test 0:

<interactive>:470:1:
Could not deduce (Integral a10) arising from a use of ‘test’
from the context (Num a)
  bound by the inferred type of it :: Num a => a
  at <interactive>:470:1-6
The type variable ‘a10’ is ambiguous
Note: there are several potential instances:
  instance Integral Foreign.C.Types.CChar
    -- Defined in ‘Foreign.C.Types’
  instance Integral Foreign.C.Types.CInt
    -- Defined in ‘Foreign.C.Types’
  instance Integral Foreign.C.Types.CIntMax
    -- Defined in ‘Foreign.C.Types’
  ...plus 28 others
In the expression: test 0
In an equation for ‘it’: it = test 0

<interactive>:470:6:
Could not deduce (Num a10) arising from the literal ‘0’
from the context (Num a)
  bound by the inferred type of it :: Num a => a
  at <interactive>:470:1-6
The type variable ‘a10’ is ambiguous
Note: there are several potential instances:
  instance RealFloat a => Num (Data.Complex.Complex a)
    -- Defined in ‘Data.Complex’
  instance Data.Fixed.HasResolution a => Num (Data.Fixed.Fixed a)
    -- Defined in ‘Data.Fixed’
  instance forall (k :: BOX) (f :: k -> *) (a :: k).
           Num (f a) =>
           Num (Data.Monoid.Alt f a)
    -- Defined in ‘Data.Monoid’
  ...plus 42 others
In the first argument of ‘test’, namely ‘0’
In the expression: test 0
In an equation for ‘it’: it = test 0
2
Thanks for the great answers. They make great sense. Also, I should have read this before I posted! haskell.org/tutorial/numbers.html.Upgradingdave

2 Answers

6
votes

There are a few basic things you need to understand about Haskell numbers:

  1. There are no implicit numeric type conversions in Haskell

  2. There is a limited amount of operator overloading, but only for members of a type class

  3. There are a lot of numeric type classes, each of which supports a different set of operations

  4. Some type classes imply other ones; they all imply Num, for example, so the operators on Num work for all numbers

The essence of your problem is that you've got contradictory type constraints, but the compiler doesn't know enough to tell you that the constraint you need is nonsensical rather than just ambiguous or missing.

The contradiction arises because the "real division" operator / is in the Fractional type class. But you are also using floor, which has a return type constrained to the Integral class. So for this to make sense, you would need a numeric type that was both Integral and Fractional, which doesn't make sense.

There are a couple of ways to resolve the contradiction:

  1. Don't use "real division"; use div which is defined in the Integral class

  2. Use fromIntegral to convert the Integral argument to / to a suitable member of Fractional


When I'm working with numeric code and trying to figure out the constraints I often take this approach:

I slap some type annotations on the top-level functions with a concrete numeric type of the general sort I want; usually Int or Double depending on what sort of calculations. For your test function, I would write test :: Int -> Int. At this point, GHC would give me a straightforward error message:

No instance for (RealFrac Int) arising from a use of ‘floor’
In the first argument of ‘test’, namely ‘(floor (n / 10))’
In the expression: test (floor (n / 10))
In an equation for ‘test’: test n = test (floor (n / 10))

No instance for (Fractional Int) arising from a use of ‘/’
In the first argument of ‘floor’, namely ‘(n / 10)’
In the first argument of ‘test’, namely ‘(floor (n / 10))’
In the expression: test (floor (n / 10))

Now I know that / and floor are expecting Fractional types; I either need to use some explicit type conversion to satisfy them, try a different type, or find some other operators! If I try test :: Double -> Double I get a similar error:

No instance for (Integral Double) arising from a use of ‘floor’
In the first argument of ‘test’, namely ‘(floor (n / 10))’
In the expression: test (floor (n / 10))
In an equation for ‘test’: test n = test (floor (n / 10))

At this point, it's clear that a single type isn't going to satisfy these constraints. The algorithm looks like it wants an Integral, so I'll search the Prelude page for "division" and see what there is.

Fortunately, Integral provides two different division operations, quot and div. Looking at the descriptions, it looks like div matches up with the semantics of / followed by floor, so that can be used to both eliminate the type errors and simplify the function!

Now I can either leave it as an Int -> Int function, or I can generalize it again to Integral a => a -> a. Depends on whether I'm going to use it in some other context, or if it's part of something specific where it's clear there's no need for a more general type.

4
votes

The type of test is (Integral a1, Num a, RealFrac a1) => a1 -> a. This type is not usable as is because there are no usable types that are both Integral and RealFrac.

Why is that? You are using n / 10, the division requires Fractional arguments, and n is the argument of test, so the argument of test must be a Fractional. It must be even a RealFrac because you then feed the result of the division to floor, and floor accepts a RealFrac argument. OTOH you are using test (floor ...), and floor returns an Integral, so the argument of test must be an Integral as well!

If you want test to be of type, say, Integral a => a->a, you need to convert n from integral so that division and floor can be performed:

test n = test (floor (fromIntegral n / 10))