cl-format is an implementation of the incredibly baroque Common Lisp format function as specified in Common Lisp, the Language, 2nd edition, Chapter 22.
Format gives you an easy and powerful way to format text and data for output. It supports rich formatting of strings and numbers, loops, conditionals, embedded formats, etc. It is really a domain-specific language for formatting.
This implementation for clojure has the following goals:
For some people the answer to this question is that they are used to Common Lisp and, therefore, they already know the syntax of format strings and all the directives.
A more interesting answer is that cl-format provides a way of rendering strings that is much more suited to Lisp and its data structures.
Because iteration and conditionals are built into the directive structure of cl-format, it is possible to render sequences and other complex data structures directly without having to loop over the data structure.
For example, to print the elements of a sequence separated by commas, you simply say:
(cl-format true "~{~a~^, ~}" aseq)
(This example is taken from Practical Common Lisp by Peter Seibel.)
The corresponding output using Clojure's Java-based format function would involve a nasty loop/recur with some code to figure out about the commas. Yuck!
cl-format is 100% compatible with the Common Lisp standard as specified in CLtLv2. This includes all of the functionality of Common Lisp's format function including iteration, conditionals, text justification and rich options for displaying real and integer values. It also includes the directives to support pretty printing structured output.
If you find a bug in a directive, drop me a line with a chunk of code that exhibits the bug and the version of cl-format you found it in and I'll try to get it fixed.
I also intend to have good built-in documentation for the directives, but I haven't built that yet.
The following directives are not yet supported: ~:T and ~@:T (but all other forms of ~T work) and extensions with ~/.
The pretty printer interface is similar, but not identical to the interface in Common Lisp.
The custom dispatch table functionality is not fully fleshed out yet.
Next up:
Once cl-format is in your path, adding it to your code is easy:
(ns your-namespace-here
(:use clojure.contrib.pprint))
If you want to refer to the cl-format function as "format" (rather than using the clojure function of that name), you can use this idiom:
(ns your-namespace-here
(:refer-clojure :exclude [format])
(:use clojure.contrib.pprint))
(def format cl-format)
You might want to do this in code that you've ported from Common Lisp, for instance, or maybe just because old habits die hard.
From the REPL, you can grab it using (use):
(use 'clojure.contrib.pprint)
cl-format is a standard clojure function that takes a variable number of arguments. You call it like this:
(cl-format stream format args...)
stream can be any Java Writer (that is java.io.Writer) or the values
true, false, or nil. The argument true is identical to using
*
out*
while false or nil indicate that cl-format should return
its result as a string rather than writing it to a stream.
format is either a format string or a compiled format (see below). The format string controls the output that's written in a way that's similar to (but much more powerful than) the standard Clojure API format function (which is based on Java's java.lang.String.Format).
Format strings consist of characters that are to be written to the output stream plus directives (which are marked by ~) as in "The answer is ~,2f". Format strings are documented in detail in Common Lisp the Language, 2nd edition, Chapter 22.
args is a set of arguments whose use is defined by the format.
When you use a format string many times (for example, when you're outputting in a loop), you can improve your performance by compiling the format with compile-format. The result of compile format can be passed to cl-format just like a format string but it doesn't need to be parsed.
For example:
(def log-format (compile-format "~2,'0D/~2,'0D/~D ~2D:~2,'0D ~:[PM,AM]: ~A~%"))
(defn log [msg]
(let [[m d y h min am?] (some-date-decomposition-fn)]
(cl-format log-format m d y h min am? msg)))
Writers in Java have no real idea of current column or device page width, so the format directives that want to work relative to the current position on the page have nothing to work with. To deal with this, cl-format contains an extension to writer called PrettyWriter. PrettyWriter watches the output and keeps track of what column the current output is going to.
When you call format and your format includes a directive that cares about what column it's in (~T, ~&, ~<...~>), cl-format will automatically wrap the Writer you passed in with a PrettyWriter. This means that by default all cl-format statements act like they begin on a fresh line and have a page width of 72.
For many applications, these assumptions are fine and you need to do
nothing more. But sometimes you want to use multiple cl-format calls
that output partial lines. You may also want to mix cl-format calls
with the native clojure calls like print. If you want stay
column-aware while doingg this you need to create a PrettyWriter of
your own (and possibly bind it to *
out*
).
As an example of this, this function takes a nested list and prints it as a table (returning the result as a string):
(defn list-to-table [aseq column-width]
(let [stream (PrettyWriter. (java.io.StringWriter.))]
(binding [*out* stream]
(doseq [row aseq]
(doseq [col row]
(cl-format true "~4D~7,vT" col column-width))
(prn)))
(.toString (.getWriter stream))))
(In reality, you'd probably do this as a single call to cl-format.)
The constructor to PrettyWriter takes the Writer it's wrapping and (optionally) the page width (in columns) for use with ~<...~>.
The following function uses cl-format to dump a columnized table of the Java system properties:
(defn show-props [stream]
(let [p (mapcat
#(vector (key %) (val %))
(sort-by key (System/getProperties)))]
(cl-format stream "~30A~A~%~{~20,,,'-A~10A~}~%~{~30A~S~%~}"
"Property" "Value" ["" "" "" ""] p)))
There are some more examples in the clojure.contrib.pprint.examples package:
The floating point directives that show exponents (~E, ~G) show E for the exponent character in all cases (unless overridden with an exponentchar). Clojure does not distinguish between floats and doubles in its printed representation and neither does cl-format.
The ~A and ~S directives accept the colon prefix, but ignore it since () and nil are not equivalent in Clojure.
Clojure has 3 different reader syntaxes for characters. The ~@c directive to cl-format has an argument extension to let you choose: