When you write the signature
getNumber' :: Num a => IO a
it means this action will need to be able to offer any result type a that the caller might request – provided the type is an instance of the Num class. So essentially, the action knows nothing about the type it has to produce, though it can use the methods of that particular Num instance:
class Num a where
fromInteger :: Integer -> a
(+) :: a -> a -> a
...
Note that this does not give you any tools to generate fractional/floating-pt. numbers, only integer ones. You could in fact write
getNumber' :: Num a => IO a
getNumber' = do
i <- readLn
return $ fromInteger i
but this is pretty useless, indeed it'll fail if you actually attempt to read something like 0.3 to a float – because that can't be pulled through the intermediate Integer type.
You could do this:
getNumber'' :: Fractional a => IO a
getNumber'' = do
q <- readLn
return $ fromRational q
in this case, the input will first be read as an arbitrary-precision rational type (which can deal with decimal-fraction input) and then converted to the desired final type, like Double1. However, this can not be an integer type, because those obviously could not handle the possible fractional inputs!
Maybe what you envision is this:
getNumber''' :: IO (∃a. Num a => a)
which would be an existential type. That's basically a constrained dynamic type, i.e. the type is not chosen at compile-type by inference from what the caller wants, but instead at runtime, choosing a suitable type that can properly deal with the particular string input (integer if possible, floating if needed).
Well, Haskell doesn't have2 existential types, and for good reasons. Its standard parametric polymorphism is rather more useful because you actually get guarantees that some type will be exactly what you specify at compile time. Value should be integral? Make it an Integer; if then a decimal-fraction turns up in the input you get a meaningful error message right a way. Value might be fractional? Make it Rational or Double; these of course include integral values as well.
At any rate, there's no real reason to constrain the action to any numerical class. If you make it polymorphic at all, you should constrain it only as much as needed for the implementation (i.e., Read). To sum up: simply use readLn, don't write any getTʏᴘᴇ action at all.
1As a general rule, never use Float except when you're sure Double is not what you want.
2Well, it has a (generally disrecommended) workaround: existentially qualified record constructors.
{-# LANGUAGE GADTs #-}
data SomeNum where
SomeNum :: Num a => a -> SomeNum
getNumber'''' :: IO SomeNum
getNumber = readLn.readLn.x :: T <- actionsyntax needs theScopedTypeVariablesextension, but with that (or the equivalent writingnum <- getNumber :: IO Int) you should be fine).getFloatbecauseNumis a type class. Types (such aFloat) and type classes are two completely and fundamentally different concepts. – I elaborate somewhat in my answer.