I use read "123" :: Int
to read in Int from String. This works great for a simple case such as "123"
; But it fails when the input is "12.34"
.
What do people usually do in such cases?
Edit: I also would like to know if there is a simple way to parse numbers with thousand separator such as "12,345"
2 Answers 2
Read a floating point number.
Consider reading a floating point number first, then rounding it as fits your application.
λ (round . read) "12.34"
12
Note that it can read a number without a dot as well:
λ (round . read) "12"
12
There are more rounding methods.
Prelude
offers several functions for transforming fractions into integral numbers: beside the obvious round
, there are floor
, ceiling
and truncate
. There are subtleties to them.
Rounding methods have polymorphic powers.
If you enable -Wtype-defaults
, you will see that the number "12.34" is read as a Double
and then transformed to Integer
. If you inspect round
though, you will see that it is quite general:
λ :type round
round :: (RealFrac a, Integral b) => a -> b
So, for instance, you may retrieve an Int
.
λ (round . read) "12.34" :: Int
12
You can give type arguments to your functions.
You can use the TypeApplications
language extension to get concise and flexible code. For instance, if you wish to parse an exotic looking Rational
to a Word8
:
λ round @_ @Word8 . read @Rational $ "2833 % 11"
2
You can avoid run time errors.
You can get a run time error from any of the above, if someone supplies gibberish. One tiny typo is enough to bring the whole program down. And the problem is remedied already: we now have readMaybe
in Prelude
. See:
λ fmap round . readMaybe @Rational $ "339 % 108"
Just 3
λ fmap round . readMaybe @Rational $ "3.14159"
Nothing
I think readMaybe
should be preferred to read
whenever possible.
Limitations of instance Read
of basic number types.
The Read
instances available in Prelude
are geared towards the lexical syntax of Haskell, which means there is a single unified reading. It supports some notational conventions, like exponential notation, by default, optionally admits some more, like binary literals, and eschews others. For example, omitting 0
before the decimal separator is widely accepted in some countries, but it is not supported by any Read
instance in base
. The use of thousands separators, while it greatly facilitates the human understanding of large numbers, is, to my knowledge, also ignored. There is no effort whatsoever to support various regional standards and conventions in base
, and, as far as I know, there is no "go-to" library to achieve more flexible support for parsing numbers at the moment.
So, if you require something "unusual" — that is, exceeding the Haskell standard — the advice is twofold:
- Use a parsing library to construct a specialized parser. There is a parser combinator module in
base
, along with a wide choice of advanced and modern parsing libraries on Hackage. - Use a simple hack. For instance, if you wish to support thousands separators, you may do something like
filter (/= ',')
. Some widely used tools in Haskell are built on hacks of this sort.
In the latter case, you should beware of possible ambiguity. For example, if you simply remove all non-number characters from the input string, you will cripple hexadecimals and exponential notation:
λ (round . read) "2.6356e5"
263560
λ (round . read . filter isNumber) "2.6356e5"
263565
I wish one day we will have stronger localization support. I think this comes with wide adoption, and Haskell is yet a local language.
3 Comments
filter (/= ',')
.
12.34
is not anInt
. You can parse it to aFloat
, or aDecimal
."12.34"
? 12? 1234? Program dies? An error that you can dispatch on but doesn't kill the program? A split of the string into"12"
and".34"
? ...something else?.
in12.345
is also a thousands separator? So I ask again: what result do you want when given the input"12.345"
?