In today’s Programming Praxis exercise, our goal is to fix something that annoys us in our programming environment. Let’s get started, shall we?
The first annoyance that came to mind for me is one I encountered in the previous exercise. While I love the Parsec library in general, it is not without its little niggles. Specifically, there were two points:
- The default method of parsing numbers is to use the functions defined on GenTokenParser, which requires two additional import lines and requires an extra parameters after the integer and float parsers.
- I normally use the operators from Control.Applicative for composing parsers, but they’re right-associative and have an impractical precedence, which results in lots of parentheses.
Today’s exercise was a good excuse to finally sit down and write a small library to fix these issues once and for all. And since I want to be able to easily use it in future projects and exercises, I’ve put it up on Hackage. Note: If you’re reading this shortly after I post it: documentation always takes a while to generate on Hackage, so you might need to wait a bit.
I won’t cover all the functions of the library here (that’s what the documentation is for), so instead we’ll see what the gas mileage program looks like when using this new library:
import Text.Parsec import Text.Parsec.Utils import Text.Printf
The showLog function is unchanged from last time.
showLog :: [(Float, Float)] -> [String] showLog es = "Miles Gals Avg" : "------ ---- ----" : zipWith (\(m2,g) (m1,_) -> printf "%.0f %.1f %.1f" m2 g ((m2 - m1) / g)) (tail es) es
The parser is nice and simple now.
logParser = many $ (,) .: float -: space +: float -: newline
And the main function is also simpler than it would otherwise have been, since we don’t need to deal with parse errors anymore.
main = mapM_ putStrLn . showLog =<< parseFile logParser "input.txt"