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

Greetings, I am happy to (finally) announce the first hackage release of brittany, a configurable haskell source code formatter based on ghc-exactprint [2]. https://hackage.haskell.org/package/brittany https://github.com/lspitzner/brittany *Introduction* 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. *Changelog* Since the "alpha-release" announced previously [5] the most important changes are: - 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. *Building* 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 priority. - 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. *Future/Outlook* 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 paths: 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 interested. 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. *Contributing* 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 https://github.com/lspitzner/brittany/ [2] https://mpickering.github.io/posts/2015-07-23-ghc-exactprint.html https://hackage.haskell.org/package/ghc-exactprint [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 brittany: > 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