[Haskell] [ANN] brittany - haskell source code formatting tool - hackage release

lennart spitzner hexagoxel at hexagoxel.de
Fri Aug 4 13:01:51 UTC 2017


I am happy to (finally) announce the first hackage release of brittany, a
configurable haskell source code formatter based on ghc-exactprint [2].



Brittany aims to nicely layout the code and retain empty lines and comments as
they appear in the input. For that it uses a fundamentally different approach
(see [7]/theory) than other formatters. The project is not finished, yet it
already is usable and produces better results than other formatters in many
cases. Of course these words come from its creator, so see for yourself: I
have included some examples at the bottom of this mail; see [3] and [4] for
some more.

I wish to stress that this project will only progress at a very slow speed
barring support from the community. I hereby offer to work on this and ask
for funding. See the "future" paragraph below.


Since the "alpha-release" announced previously [5] the most important changes

- Release under the AGPL-v3;
- Release on hackage;
- Make horizontal alignment less aggressive by default (see example [6]);
- Add high-level documentation (see [7]). This might be of interest to anyone
  considering to write a formatter (haskell or even otherwise);
- Improve testsuite setup;
- Improve layouting in many many cases and fix various bugs;
- Add a library interface (this integrates with haskell-ide-engine [8]);

Brittany still..

- does not touch anything other than top-level type signatures and
  bindings (imports, classes, instances, data decls, .. are not modified);
- contains some bugs (but it checks its own output for validity and aborts);
- does not handle many "uncommon" syntactic constructs/extentions
  (e.g. arrow notation);

In preparation of this release the `butcher` and `czipwith` packages were
published on hackage as well. I will cover those in separate announcements.


Brittany currently requires ghc-8.0 (no support for previous ghc versions is
planned, but 8.2 will be). Now that this package is on hackage, cabal users
should be able to install it directly; stack users will currently have to clone
and go from there (a stack.yaml is provided). Detailed building guides for
cabal, cabal-new and stack are in the project README.

Inclusion in stackage is coming as soon as all dependencies are included.

*Known issues*

- Bad performance for large inputs (really noticeable at >1k loc). The cause
  is already determined - quadratic instead of linear complexity in one
  specific function (see issue #34 [9]). Should be fixable and has high
- The config (file) lacks documentation.
- Comments are not always reproduced exactly when reformatting. There is a lack
  of testcases for comments in the testsuite. There is the known case of
  comments in List/MonadComprehensions that currently breaks idempotency (the
  comments move further left each roundtrip through brittany) where a solution
  will most likely involve some non-trivial changes in ghc-exactprint
  (see [10]).
- As mentioned above: only certain module elements are transformed, and
  not all syntactical constructs are supported. re-layouting of imports, data
  decls, classes and instances are on the radar, but all require a good amount
  of work.
- CPP is not officially supported. This is mostly a WONTFIX. One can enable it
  by force, and it works to a degree, but it is relatively easy to find cases
  where it will break (conditional code not being reformatted, leading to
  syntax errors and theoretically also to change semantics)).
- No fine-grained (per file or even per-function) control/configuration, e.g.
  it is not yet possible to add some pragma/comment to disable reformatting
  this one carefully layouted-by-hand function that brittany messes up.
- No adaption to ghc-8.2 yet.


As far as I can tell brittany already works rather well - I use it all the
time, even though I still run into issues from time to time. Still, brittany is
not finished (these projects never really are, are they?).

Up on my list are ghc-8.2 adaption, fixing the performance bug mentioned above,
layouting of data decls, layouting of instances, and controlling
behaviour/config via flags in the source (as comments).
But implementing new functionality costs a lot of time, even with the DSL used
internally: One has to understand the GHC AST (which is not always documented),
think of all possible interactions ("do users really use record syntax when
having more than one constructor?" "are GADT records a thing?"), write the
layouting transformation and test it.

I have already invested a good amount of my free time into this project but its
scope is too large and I decided to put a limit on my personal investment.
This means that barring some form of support from the community I don't see new
features being implemented at any certain pace. This leaves the following

1) This project is supported monetarily: I hereby offer to (part-time) work on
   this provided appropriate funding. I am not sure who exactly might be
   interested in providing this kind of support - I am no student anymore so
   GSoC is out of the question. Feel free to contact me on- or off-list if

2) (More) Contributors add tests, report (and maybe fix) bugs and most
   importantly implement new functionality (layouting for any syntactic
   constructs of the haskell language not yet supported, etc.)

   I realize that this is not a trivial project to dive into, and there is a
   good amount of stuff one has to get familiar with before one can start
   making use of the abstractions (the DSL) provided.

   On the other hand, I _think_ that the project source code is relatively
   well-structured (although in-source comments are really sparse, admittedly)
   and the high-level (markdown) documentation <strike>will scare everyone
   away due to their length</strike> will allow understanding the project
   relatively quickly.

3) The project will progress only rather slowly.

Regardless I plan to continue maintaining this project, fix bugs as they come
up and also work on expanding functionality when I find the time.


In ascending order of involvement, you can contribute by:

- Reporting issues, especially instances of bad layouting (especially when it
  is not a pure matter of opinion);
- Add to the testsuite, specifically to the `tests.blt` file of the `littests`
  test. It contains a large number of tests already, but the coverage is not
  very good.
- If you want to really help implementing more stuff, the high-level docs [7]
  are the right place to get a rough overview, especially the theory document
  [11]. I have not documented how to write new layouters yet, so for now I
  recommend looking at the existing ones, e.g. in `Expr.hs`.

-- lennart

[1] https://hackage.haskell.org/package/brittany
[2] https://mpickering.github.io/posts/2015-07-23-ghc-exactprint.html
[3] https://github.com/lspitzner/brittany/blob/da692a4341399390018fb03773e15865d967fb8c/doc/showcases/BrittanyComparison.md
[4] https://github.com/lspitzner/brittany/tree/master/doc/showcases
[5] https://mail.haskell.org/pipermail/haskell-cafe/2016-September/124793.html
[6] https://github.com/lspitzner/brittany/blob/da692a4341399390018fb03773e15865d967fb8c/doc/showcases/Layout_Alignment.md#items-that-are-not-single-line-break-up-alignment
[7] https://github.com/lspitzner/brittany/blob/master/doc/implementation/index.md
[8] https://github.com/haskell/haskell-ide-engine
[9] https://github.com/lspitzner/brittany/issues/34
[10] https://github.com/alanz/ghc-exactprint/issues/53
[11] https://github.com/lspitzner/brittany/blob/master/doc/implementation/theory.md


some layouting examples - would be (re)produced in exactly this way by

> main = do
>   now <- getCurrentTime
>   let (_, _, week) = toWeekDate . utctDay $ now
>   putStrLn $ ("it's "++) $ case week of
>     6 -> "the weekend"
>     7 -> "the weekend"
>     _ -> "a weekday"
>   localtime <- utcToLocalZonedTime now
>   let hr = todHour . localTimeOfDay . zonedTimeToLocalTime $ localtime
>   case hr of
>     _ | hr < 12   -> putStrLn "it's before noon"
>       | otherwise -> putStrLn "it's after noon"

> -- Newlines are used sparingly: Only after "do" and when the
> -- `liftBaseOpDiscard` application would lead to overflowing 80 columns.
> main :: IO ()
> main = do
>   pool <- createPostgresqlPool (toS databaseConnectionString) 10
>   initiate $ \chan -> forever $ do
>     flip runDbConn pool $ do
>       makeSureQueueIsFull chan
>       void $ liftBaseOpDiscard (consumeMsgs chan responseQueue Ack)
>                                (uncurry processMsg)
>     threadDelay 1000000

> -- Alignment of patterns
> go []                 ""     = True
> go [WildCard        ] ""     = True
> go (WildCard   :rest) (c:cs) = go rest (c : cs) || go (WildCard : rest) cs
> go (Union globs:rest) cs     = any (\glob -> go (glob ++ rest) cs) globs
> go []                 (_:_)  = False
> go (_:_)              ""     = False

More information about the Haskell mailing list