A new formatting library. Doesn't try to be fancy. Also has some nice formatters that other formatting libraries don't have.
The idea is very simple. Let's implement some formatters that are just
ordinary functions of type a -> Builder
:
> hexF 93
"5d"
And a bunch of operators ( |
, |
) for concatenating strings:
> let b = 93
> "Got another byte (0x" |hexF b| ")"
"Got another byte (0x5d)"
And if we want to be able to produce String
, Text
, Builder
, etc with
them, let's make those operators polymorphic in result type:
> "Got another byte (0x" |hexF b| ")" :: String
"Got another byte (0x5d)"
> "Got another byte (0x" |hexF b| ")" :: Text
"Got another byte (0x5d)"
Finally, to make the library more useful, let's provide formatters that other libraries often miss – for instance, something for indentation, something for formatting lists and maps, and something to format arbitrary types with generics:
> data Point = Point {x, y :: Int} deriving Generic
> fmt $ genericF (Point 1 2)
Point:
x: 1
y: 2
If you want to see more examples, look at the docs.
Other commonly used libraries are text-format, formatting
and printf
(which isn't a library but comes from Text.Printf
in
base).
-
printf
takes a formatting string and uses some type tricks to accept the rest of the arguments polyvariadically. It's very concise, but there are some drawbacks – it can't produceText
(you'd have toT.pack
it every time) and it doesn't warn you at compile-time if you pass wrong arguments or not enough of them. -
text-format takes a formatting string with angle brackets denoting places where arguments would be substituted (the arguments themselves are provided via a tuple). If you want to apply formatting to some of the arguments, you have to use one of the provided formatters. Like
printf
, it can fail at runtime, but at least the formatters are first-class (and you can add new ones). -
formatting takes a formatting template consisting of pieces of strings interleaved with formatters (this ensures that arguments always match their placeholders). It provides lots of formatters and generally seems to be the most popular formatting library here. Unfortunately, at least in my experience writing new formatters can be awkward and people sometimes have troubles understanding how
formatting
works.
There are also some other libraries, e.g. category-printf
and xformat. They don't seem better or more powerful than formatting
to me, but admittedly I haven't looked at them in depth. xformat in
particular seems to run into a problem with ambiguous types when
OverloadedStrings
is enabled.
Finally, there's a cottage industry of libraries using Template Haskell (they tend to have “interpolation” in their names rather than “format” or “printf”). One bad thing about all of them is that you can't use GHC's parser in Template Haskell and so either they have to do with interpolating variables only, or call into a separate Haskell parser (e.g. haskell-src-exts, but most don't bother). Here's an example of formatting done with interpolate:
[i|There are #{n} million bicycles in #{city}.|]
The benchmark code can be
found here. fmt
is usually twice as fast as formatting
, and on par with text-format
. This example was used for benchmarks:
"There are " |n| " million bicycles in " |city| "."
Library | Text | String |
---|---|---|
fmt | 0.4 mcs | 1.1 mcs |
formatting | 0.6 mcs | 1.4 mcs |
text-format | 0.3 mcs | 1.1 mcs |
printf |
2.0 mcs | 1.6 mcs |
show and <> |
0.9 mcs | 0.5 mcs |
-
Something that would cut a string by adding ellipsis to the center:
Foo bar ba...qux blah
. -
Something to format a floating-point number without any scientific notation (
floatF
starts using scientific notation after 1e21). -
Write
RULES
to make it faster forString
(and perhaps forText
as well).
-
Is there any way to replace
|
and|
with just one operator? (I tried but it doesn't seem to be possible if you want the library to keep working with enabled-XOverloadedStrings
.) -
Should support for terminal coloring be added? If so, how should it be designed?
-
Is there any way to allow
IO
inside| |
brackets? (With the result being anIO
action that prints the string.) -
How should
listF
,mapF
, etc format lists/maps? Also, should we be more clever about multiline detection, or less clever (e.g. by providing something that will always use several lines and also something that will never use several lines)? CurrentlylistF
doesnk't try to be clever at all, but perhaps it should. -
Can we somehow make formatters overloaded instead of always outputting
Builder
? Currently, if you wantlistF xs
to be aText
, you have to write eitherfmt (listF xs)
or"" |listF xs| ""
, which doesn't seem nice. However, making formatters overloaded breaks type inference. -
It'd be nice to have something for wrapping lists (to fit into 80 chars or something), and also something for truncating lists (e.g. you might want long lists to be displayed as
[foo, bar, baz, ... (187 elements omitted)]
). This is hard because a) the design space is big, and b) there are too many combinations (listTrimmedF
,blockListTrimmedF
,listTrimmedF'
, etc). -
By the way, is the
| |
operator abhorrent and should be removed? What about||
and||
? -
How should tuples be formatted? I'm uneasy about the current syntax.
-
Should formatters be changed to never add a trailing newline?
-
Two spaces for list indentation, or four?
-
Should
genericF
's syntax for constructors be changed?