Formatted text using printf
Tips and techniques for printing and logging
In this post, we'll take a small detour and look at how to create formatted text. The printing and formatting functions are technically library functions, but in practice they as used as if they were part of the core language.
F# supports two distinct styles of formatting text:
The standard .NET technique of "composite formatting" as seen in
String.Format,Console.WriteLineand other places.The C-style technique of using
printfand the associated family of functions such asprintfn,sprintfand so on.
String.Format vs printf
The composite formatting technique is available in all .NET languages, and you are probably familiar with it from C#.
Console.WriteLine("A string: {0}. An int: {1}. A float: {2}. A bool: {3}","hello",42,3.14,true)The printf technique, on the other hand, is based on the C-style format strings:
printfn "A string: %s. An int: %i. A float: %f. A bool: %b" "hello" 42 3.14 trueAs you have seen, the printf technique is very common in F#, while String.Format, Console.Write and so on, are rarely used.
Why is printf preferred and considered idiomatic for F#? The reasons are:
It is statically type checked.
It is a well-behaved F# function and so supports partial application, etc.
It supports native F# types.
printf is statically type checked
Unlike String.Format, printf is statically type checked, both for the types of the parameters, and the number.
For example, here are two snippets using printf that will fail to compile:
The equivalent code using composite formatting will compile fine but either work incorrectly but silently, or give a runtime error:
printf supports partial application
The .NET formatting functions require all parameters to be passed in at the same time.
But printf is a standard, well-behaved F# function, and so supports partial application.
Here are some examples:
And of course, printf can be used for function parameters anywhere a standard function can be used.
This also includes the higher order functions for lists, etc:
printf supports native F# types
For non-primitive types, the .NET formatting functions only support using ToString(), but printf supports native F# types using the %A specifier:
As you can see, tuple types have a nice ToString() but other user defined types don't, so if you want to use them with the .NET formatting functions, you will have to override the ToString() method explicitly.
printf gotchas
There are a couple of "gotchas" to be aware of when using printf.
First, if there are too few parameters, rather than too many, the compiler will not complain immediately, but might give cryptic errors later.
The reason, of course, is that this is not an error at all; printf is just being partially applied! See the discussion of partial application if you are not clear of why this happens.
Another issue is that the "format strings" are not actually strings.
In the .NET formatting model, the formatting strings are normal strings, so you can pass them around, store them in resource files, and so on. Which means that the following code works fine:
On the other hand, the "format strings" that are the first argument to printf are not really strings at all, but something called a TextWriterFormat. Which means that the following code does not work:
The compiler does some magic behind the scenes to convert the string constant "A string: %s" into the appropriate TextWriterFormat. The TextWriterFormat is the key component that "knows" the type of the format string, such as string->unit or string->int->unit, which in turn allows printf to be typesafe.
If you want to emulate the compiler, you can create your own TextWriterFormat value from a string using the Printf.TextWriterFormat type in the Microsoft.FSharp.Core.Printf module.
If the format string is "inline", the compiler can deduce the type for you during binding:
But if the format string is truly dynamic (e.g. stored in a resource or created on the fly), the compiler cannot deduce the type for you, and you must explicitly provide it with the constructor.
In the example below, my first format string has a single string parameter and returns a unit, so I have to specify string->unit as the format type. And in the second case, I have to specify string->int->unit as the format type.
I won't go into detail on exactly how printf andTextWriterFormat` work together right now -- just be aware that is not just a matter of simple format strings being passed around.
Finally, it's worth noting that printf and family are not thread-safe, while Console.Write and family are.
How to specify a format
The "%" format specifications are quite similar to those used in C, but with some special customizations for F#.
As with C, the characters immediately following the % have a specific meaning, as shown below.
We'll discuss each of these attributes in more detail below.
Formatting for dummies
The most commonly used format specifiers are:
%sfor strings%bfor bools%ifor ints%ffor floats%Afor pretty-printing tuples, records and union types%Ofor other objects, usingToString()
These six will probably meet most of your basic needs.
Escaping %
The % character on its own will cause an error. To escape it, just double it up:
Controlling width and alignment
When formatting fixed width columns and tables, you need to have control of the alignment and width.
You can do that with the "width" and "flags" options.
%5s,%5i. A number sets the width of the value%*s,%*i. A star sets the width of the value dynamically (from an extra parameter just before the param to format)%-s,%-i. A hyphen left justifies the value.
Here are some examples of these in use:
Formatting integers
There are some special options for basic integer types:
%ior%dfor signed ints%ufor unsigned ints%xand%Xfor lowercase and uppercase hex%ofor octal
Here are some examples:
The specifiers do not enforce any type safety within the integer types. As you can see from the examples above, you can pass a signed int to an unsigned specifier without problems. What is different is how it is formatted. The unsigned specifiers treat the int as unsigned no matter how it is actually typed.
Note that BigInteger is not a basic integer type, so you must format it with %A or %O.
You can control the formatting of signs and zero padding using the flags:
%0ipads with zeros%+ishows a plus sign% ishows a blank in place of a plus sign
Here are some examples:
Formatting floats and decimals
For floating point types, there are also some special options:
%ffor standard format%eor%Efor exponential format%gor%Gfor the more compact offande.%Mfor decimals
Here are some examples:
The decimal type can be used with the floating point specifiers, but you might lose some precision. The %M specifier can be used to ensure that no precision is lost. You can see the difference with this example:
You can control the precision of floats using a precision specification, such as %.2f and %.4f. For the %f and %e specifiers, the precision affects the number of digits after the decimal point, while for %g it is the number of digits in total. Here's an example:
The alignment and width flags work for floats and decimals as well.
Custom formatting functions
There are two special format specifiers that allow to you pass in a function rather than just a simple value.
%texpects a function that outputs some text with no input%aexpects a function that outputs some text from a given input
Here's an example of using %t:
Obviously, since the callback function takes no parameters, it will probably be a closure that does reference some other value. Here's an example that prints random numbers:
For the %a specifier, the callback function takes an extra parameter. That is, when using the %a specifier, you must pass in both a function and a value to format.
Here's an example of custom formatting a tuple:
Date formatting
There are no special format specifiers for dates in F#.
If you want to format dates, you have a couple of options:
Use
ToStringto convert the date into a string, and then use the%sspecifierUse a custom callback function with the
%aspecifier as described above
Here are the two approaches in use:
Which approach is better?
The ToString with %s is easier to test and use, but it will be less efficient than writing directly to a TextWriter.
The printf family of functions
There are a number of variants of printf functions. Here is a quick guide:
F# function
C# equivalent
Comment
printf and printfn
Console.Write and Console.WriteLine
Functions starting with "print" write to standard out.
eprintf and eprintfn
Console.Error.Write and Console.Error.WriteLine
Functions starting with "eprint" write to standard error.
fprintf and fprintfn
TextWriter.Write and TextWriter.WriteLine
Functions starting with "fprint" write to a TextWriter.
sprintf
String.Format
Functions starting with "sprint" return a string.
bprintf
StringBuilder.AppendFormat
Functions starting with "bprint" write to a StringBuilder.
kprintf, kfprintf, ksprintf and kbprintf
No equivalent
Functions that accept a continuation. See next section for a discussion.
All of these except bprintf and the kXXX family are automatically available (via Microsoft.FSharp.Core.ExtraTopLevelOperators). But if you need to access them using a module, they are in the Printf module.
The usage of these should be obvious (except for the kXXX family, of which more below).
A particularly useful technique is to use partial application to "bake in" a TextWriter or StringBuilder.
Here is an example using a StringBuilder:
And here is an example using a TextWriter:
More on partially applying printf
Note that in both cases above, we had to pass a format parameter when creating the partial application.
That is, we had to do:
rather than the point-free version:
This stops the compiler complaining about an incorrect type. The reason why is non-obvious. We briefly mentioned the TextWriterFormat above as the first parameter to printf. It turns out that printf is not actually a particular function, like String.Format, but rather a generic function that has to be parameterized with a TextWriterFormat (or the similar StringFormat) in order to become "real".
So, to be safe, it is best to always pair a printf with a format parameter, rather than being overly aggressive with the partial application.
The kprintf functions
The four kXXX functions are similar to their cousins, except that they take an extra parameter -- a continuation. That is, a function to be called immediately after the formatting has been done.
Here's a simple snippet:
Why would you want this? A number of reasons:
You can pass the result to another function that does something useful, such as a logging framework
You can do things such as flushing the TextWriter
You can raise an event
Let's look at a sample that uses a external logging framework plus custom events.
First, let's create a simple logging class along the lines of log4net or System.Diagnostics.Trace. In practice, this would be replaced by a real third-party library.
Next in my application code, I do the following:
Create an instance of the logging framework. I've hard-coded the factory method here, but you could also use an IoC container.
Create helper functions called
logInfoandlogErrorthat call the logging framework, and in the case oflogError, show a popup message as well.
Finally, when we run the test function, we should get the message written to the console, and also see the popup message:
You could also create an object-oriented version of the helper methods by creating a "FormattingLogger" wrapper class around the logging library, as shown below.
The object-oriented approach, although more familiar, is not automatically better! The pros and cons of OO methods vs. pure functions are discussed here.
Last updated
Was this helpful?