Search code examples
parsinghaskellparsec

How can I parse a float with a comma in place of the decimal point?


I want to parse Float values from a file where they are stored using comma as the decimal separator. Thus i need a function myParse :: String -> Float such that, for instance, myParse "23,46" == 23.46.

I have some ideas about how to do this, but they all seem overcomplicated, for example:

Is there a simpler way, or do I really need to use a parsing library? In the second case, could you please paste some suggestions in order to get me started? The monomorphism restriction scares me, and I believe that there has to be a way to do this without using language extensions.


Solution

  • Replacing , by . and then call read is straightforward enough; you just need to remember to use your own specialized function instead of plain old read:

    readFloatWithComma :: String -> Float
    readFloatWithComma = read . sanitize
      where
        sanitize = map (\c -> if c == ',' then '.' else c)
    

    In GHCi:

    λ> readFloatWithComma "23,46"
    23.46
    

    Regarding the parsec approach, despite what the article you link to suggest, the monomorphism restriction needs not be a worry, as long as you have type signatures for all your top-level bindings. In particular, the following code doesn't need any language extensions to compile properly (at least, in GHC 7.10.1):

    import Text.Parsec
    import Text.Parsec.String         ( Parser )
    import Control.Applicative hiding ( (<|>) )
    
    infixr 5 <++>
    (<++>) :: Applicative f => f [a] -> f [a] -> f [a]
    a <++> b = (++) <$> a <*> b
    
    infixr 5 <:>
    (<:>) :: Applicative f => f a -> f [a] -> f [a]
    a <:>  b = (:) <$> a <*> b
    
    number :: Parser String
    number = many1 digit
    
    plus :: Parser String
    plus = char '+' *> number
    
    minus :: Parser String
    minus = char '-' <:> number
    
    integer :: Parser String
    integer = plus <|> minus <|> number
    
    float :: Parser Float
    float = fmap rd $ integer <++> decimal <++> exponent
        where rd       = read :: String -> Float
              decimal  = option "" $ ('.' <$ char ',') <:> number
              exponent = option "" $ oneOf "eE" <:> integer
    

    In GHCi:

    λ> parseTest float "23,46"
    23.46