A Month of Haskell, Day 6 - Text.Printf
Here’s a quick one for the sixth entry in this series. The Text.Printf module won’t completely change how you program, but it’s an extremely useful module that needs to be more well known. Because it’s part of the base system, you don’t need to install anything to make use of it.
Printing a string that includes a lot of values in Haskell can be pretty tedious:
import Data.Version(showVersion)
import System.Info
main :: IO ()
main = do
putStrLn $ os ++ ", running on " ++ arch ++ "\n" ++ "Haskell compiler " ++ compilerName ++
", version " ++ showVersion compilerVersion
return ()Luckily, the base system includes a module that acts an awful lot like the C printf function (or the shell printf command) which you are likely already very familiar with.
Type signatures:
printf :: PrintfType r => String -> rJust like the C version, it takes a format string and a variable number of arguments that are converted before being printed. The above could also be written like so:
import Data.Version(showVersion)
import System.Info
import Text.Printf
main :: IO ()
main = do
printf "%s, running on %s\nHaskell compiler %s, version %s\n"
os arch compilerName (showVersion compilerVersion)
return () This does what you would expect. Unlike the C version, you can also easily just have
printf output to a string that you could later print. You could also do anything
else with it that you could do with strings - store in a database, transmit over the
network, and so forth.
Also similarly to C, there’s a hPrintf version that works more like fprintf - you
also pass it a handle for where it should print to. This version obviously does not
allow for printing to a string variable. Printing to the Haskell equivalent of stderr
looks like this:
import Data.Version(showVersion)
import System.IO(stderr)
import System.Info
import Text.Printf
main :: IO ()
main = do
hPrintf stderr "%s, running on %s\nHaskell compiler %s, version %s\n"
os arch compilerName (showVersion compilerVersion)
return ()The documentation is actually very complete. If you are at all familiar with printf from other languages, you will be able to make sense of its discussion of format characters and precision and field width. I don’t want to go into it here, because it is fairly boring stuff that’s easy to experiment with.
There’s one other very handy thing you can do with printf. Just like everything else
in Haskell, it will perform type checking on its arguments. In fact, all arguments
passed to printf must be an instance of the PrintfArg type class.
Type classes:
class PrintfArg a where
formatArg :: a -> FieldFormatter
parseFormat :: a -> ModifierParserAll the basic stuff is already an instance of this type class. But what if you invented some new string-like type and wanted to make sure it could also be passed as an argument?
Back on day 2 I did exactly that when I invented an upper case string type. This was just like a regular string, but it would automatically convert everything to upper case. It would be very handy to also be able to pass these things to printf. At the time, I glossed right over it but now it’s worth digging into.
All that is necessary to make something an instance of PrintfArg is to define a
formatArg function that converts the type into something that printf already
understands. Luckily, Text.Printf already defines a bunch of functions like
formatString, formatChar, and formatInteger so all you really need to do is
convert your new type into one of those and call the appropriate function, and
everything will work out.
The instance definition for UpperString looks like this:
instance PrintfArg UpperString where
formatArg = formatString . getUpperStringThe getUpperString function simply extracts the string itself out of the type.
That gets passed to the existing formatString function that takes a string
and converts it to what printf expects. The following code prints out what
you would expect:
main :: IO ()
main = do
let s = fromString "some text" :: UpperString
printf "%s\n" s