4
\$\begingroup\$

This code is a Parser that parses numbers according to R5RS.

  • #b1001 - binary
  • #o2127 - octal
  • #h02d - hexadecimal
  • #d1231 - decimal
  • 3923 - decimal

It is working at the moment, the only problem is the parseNumberBase. I am really new to haskell, but it does not look very good to me.

  • How could I improve it? (readability wise)
  • It would also be nice to see a more "idiomatic" approach

import Data.Char (digitToInt)
import Numeric (readInt, readOct, readHex)
import Data.Maybe (listToMaybe, fromJust)

parseNumber :: Parser LispVal
parseNumber = parseNumberBase 'd'
          <|> do char '#'
                 base <- oneOf "bdoh"
                 parseNumberBase base

-- | Parses a number at a specific base
parseNumberBase :: Char -> Parser LispVal
parseNumberBase 'b' =
    do digits <- many1 (oneOf "01")
       return $ (Number . fromJust . readBinary) digits
parseNumberBase 'o' =
    do digits <- many1 octDigit
       return $ Number (fst (readOct digits !! 0))
parseNumberBase 'd' =
    do digits <- many1 digit
       return $ (Number . read) digits
parseNumberBase 'h' =
    do digits <- many1 hexDigit
       return $ Number (fst (readHex digits !! 0))
parseNumberBase _ =
    error "Wrong number base"

readBinary :: String -> Maybe Integer
readBinary =
    fmap fst . listToMaybe . readInt 2 (`elem` "01") digitToInt
\$\endgroup\$
  • \$\begingroup\$ You didn't include all code. What's Parser? \$\endgroup\$ – Zeta May 23 '16 at 9:10
2
\$\begingroup\$
do
   digits <- someParser
   return $ someFunction digits

is just

someFunction <$> someParser

We can make parseNumberBase a lot shorter with that information:

-- | Parses a number at a specific base
parseNumberBase :: Char -> Parser LispVal
parseNumberBase 'b' = Number . fromJust . readBinary <$> many1 (oneOf "01")
parseNumberBase 'o' = Number . fst . head . readOct  <$> many1 octDigit     -- see below
parseNumberBase 'd' = Number . read                  <$> many1 digit
parseNumberBase 'h' = Number . fst . head . readHex  <$> many1 hexDigit     -- see below
parseNumberBase _   = error "Wrong number base"

readFunc digits !! 0 is head (readFunc digits), so we were able to get rid of !! too.

That being said, parseNumberBase has a lot of responsibility. Split it into multiple parsers, and it's suddenly a lot easier to grasp:

parseBinary :: Parser LispVal
parseBinary = Number . fromJust . readBinary <$> many1 (oneOf "01")

parseDecimal :: Parser LispVal
parseDecimal = Number . read <$> many1 digit

parseHexadecimal :: Parser LispVal
parseHexadecimal = Number . fst . head . readHex <$> many1 hexDigit

parseOct :: Parser LispVal
parseOct = Number . fst . head . readOct  <$> many1 octDigit

We can now test all those functions in isolation. We can now write parseNumberBase as another parser:

parseNumber :: Parser LispVal
parseNumber = parseDecimal <|> char '#' *> parseNumberBase

parseNumberBase :: Parser LispVal
parseNumberBase =  char 'h' *> parseHexadecimal 
               <|> char 'd' *> parseDecimal 
               <|> char 'o' *> parseOct
               <|> char 'b' *> parseBinary
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.