cdornan / fmt

New formatting library
BSD 3-Clause "New" or "Revised" License
56 stars 6 forks source link

fmt

Hackage Build status BSD3 license

A new formatting library. Doesn't try to be fancy. Also has some nice formatters that other formatting libraries don't have.

About this library

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.

About other formatting libraries

Other commonly used libraries are text-format, formatting and printf (which isn't a library but comes from Text.Printf in base).

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}.|]

Benchmarks

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

Things to do

Easy things to implement

  1. Something that would cut a string by adding ellipsis to the center: Foo bar ba...qux blah.

  2. Something to format a floating-point number without any scientific notation (floatF starts using scientific notation after 1e21).

  3. Write RULES to make it faster for String (and perhaps for Text as well).

Questions to answer

  1. 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.)

  2. Should support for terminal coloring be added? If so, how should it be designed?

  3. Is there any way to allow IO inside +| |+ brackets? (With the result being an IO action that prints the string.)

  4. 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)? Currently listF doesnk't try to be clever at all, but perhaps it should.

  5. Can we somehow make formatters overloaded instead of always outputting Builder? Currently, if you want listF xs to be a Text, you have to write either fmt (listF xs) or ""+|listF xs|+"", which doesn't seem nice. However, making formatters overloaded breaks type inference.

  6. 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).

  7. By the way, is the |++| operator abhorrent and should be removed? What about +|| and ||+?

  8. How should tuples be formatted? I'm uneasy about the current syntax.

  9. Should formatters be changed to never add a trailing newline?

  10. Two spaces for list indentation, or four?

  11. Should genericF's syntax for constructors be changed?