Skip to content

Commit

Permalink
Ormolu formatting (#1108)
Browse files Browse the repository at this point in the history
* Add ormolu tooling and check

You can install ormolu integration in emacs by following the instructions here:
https://github.com/vyorkin/ormolu.el

* Apply ormolu formatting

* Remove redundant imports

* Update STYLE.md to reflect Ormolu usage

* Add run-formatter.sh

This script formats all input files with Ormolu

* Add Ormolu formatting check as part of style-check.sh

* Also run style checks on the unittest directory
  • Loading branch information
Munksgaard authored Sep 1, 2020
1 parent 53365cf commit 87cc177
Show file tree
Hide file tree
Showing 214 changed files with 45,581 additions and 35,206 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ jobs:
key: ${{ runner.os }}-${{ hashFiles('nix/sources.json') }}-style

- name: Style check
run: nix-shell --pure --run "tools/style-check.sh src"
run: nix-shell --pure --run "tools/style-check.sh src unittests"

- name: Unit tests
run: |
Expand Down
292 changes: 83 additions & 209 deletions STYLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,206 +18,150 @@ guidelines on some of the principles underlying the compiler design.
Style Rules
===========

Formatting
----------

### Line Length
Ormolu
------

Maximum line length is *80 characters*.
Futhark uses [Ormolu](https://github.com/tweag/ormolu/) to enforce consistency
in formatting style. The style is checked by our CI, so you should make sure any
pull requests comply with Ormolus formatting before trying to merge.

### Indentation
### Installation

Tabs are illegal. Use spaces for indenting. Indent your code blocks
with *2 spaces*. E.g, we write
Installing Ormolu can be done through Cabal, Stack, Nix or the Arch Linux
package manager. For instance, to install through Cabal, run the following
command:

```haskell
foo x = do
case whatever x of
Just bar ->
...
Nothing ->
...
```
cabal install ormolu
```

It is okay to use a different number of spaces for alignment with
other syntactic structures, e.g:
If you're running Nix or NixOS, you can just use `nix-shell` to enter a
development environment with ormolu already installed.

```haskell
case foo of Just x -> do a
b
Nothing -> do c
d
### Basic Usage

The following command formats a single file:

```
ormolu -i FILE.hs
```

Tasteful alignment can made code easier to read, but do not go
overboard. The functions `inputs` and `lambda` in
`Futhark.Analysis.HORepresentation.SOAC` are a good example of
pointless use of alignment, and are retained to serve as a reminder of
this.
This command can be used to format all Haskell files in Futhark, while
checking that the formatting is idempotent:

If in doubt, don't align. Spurious alignment makes the code feel weird
and off-key, and it can be remarkably ugly when not maintained. Alignment
in expressions is usually a bad idea.
```
./tools/run-formatter.sh src unittests
```

### Blank Lines
The idempotence check is mostly done to make sure Ormolu (which is still a young
tool in active development) doesn't introduce any unnecessary changes. Any
idempotence errors should be reported upstream.

One blank line between top-level definitions. No blank lines between
type signatures and function definitions, except in very large
functions, where each clause is also separated by whitespace. Add one
blank line between functions in a type class instance declaration if
the functions bodies are large. Use your judgement.
### Editor Integration

In large `do`-blocks, separate logically separate chunks of code with
a single blank line.
Emacs has [ormolu.el](https://github.com/vyorkin/ormolu.el) to help with
formatting. Once installed (as per the instructions on that page), it will
automatically format any open Haskell file on save.

### Whitespace
The Ormolu README lists further integrations for VS Code and vim.

Generally, surround binary operators with a single space on either
side. Use your better judgement for the insertion of spaces around
arithmetic operators but always be consistent about whitespace on
either side of a binary operator. Don't insert a space after a
lambda. Trailing whitespace is not permitted. Always have a space on
each side of '<-' and '='. E.g, this is bad:
### Limitations

```haskell
x<- foo
let y =bar
```
Ormolu doesn't handle all aspects of coding style. For instance, it will do no
significant rewrites of your code, like if-then-else to pattern matches or
enforcing the 80 character line limit, but it will ensure consistency in
alignment and basic formatting. Therefore, as a Futhark contributer, use Ormolu
to ensure basic style consistency, while still taking care to follow the more
general style rules listed below.

But this is good:
Formatting
----------

```
x <- foo
let y = bar
```
### Line Length

### Data Declarations
Maximum line length is *80 characters*.

Align the constructors in a data type definition. Example:
Ormolu doesn't enfore the 80 character line limit, so it is up to the user to
introduce the necessary line breaks in the code. However, Ormolu will take a
hint. Imagine you've got the following line of code:

```haskell
data Tree a = Branch !a !(Tree a) !(Tree a)
| Leaf
```
onKernels :: (SegOp SegLevel KernelsMem -> ReuseAllocsM (SegOp SegLevel KernelsMem)) -> Stms KernelsMem -> ReuseAllocsM (Stms KernelsMem)
```

For long type names or constructors, the following formatting is also
acceptable:
If the user introduces a newline before `ReuseAllocsM`, turning the above into
the following:

```haskell
data HttpException
= InvalidStatusCode Int
| MissingContentHeader
```
onKernels :: (SegOp SegLevel KernelsMem -> ReuseAllocsM (SegOp SegLevel KernelsMem)) -> Stms KernelsMem ->
ReuseAllocsM (Stms KernelsMem)
```

Format records as follows:
Ormolu will pick up the hint and reformat the entire declaration to this:

```haskell
data Person = Person
{ firstName :: !String -- ^ First name
, lastName :: !String -- ^ Last name
, age :: !Int -- ^ Age
} deriving (Eq, Show)
```
onKernels ::
(SegOp SegLevel KernelsMem -> ReuseAllocsM (SegOp SegLevel KernelsMem)) ->
Stms KernelsMem ->
ReuseAllocsM (Stms KernelsMem)
```

### List Declarations

Align the elements in the list. Example:

```haskell
exceptions =
[ InvalidStatusCode
, MissingContentHeader
, InternalServerError
]
```
### Blank Lines

Optionally, you can skip the first newline. Use your judgement.
In large `do`-blocks, separate logically separate chunks of code with
a single blank line.

```haskell
directions = [ North
, East
, South
, West
]
```

### Long Expressions

Long expressions should be split over multiple lines. If splitting at
an operator use your judgement as to whether the operator goes at the
end or beginning of a line. In the case of `$`, always put it at the
end of the line. If splitting a function call, use indentation to
make more readable.
Long expressions should be split over multiple lines.

If splitting a definition using the `$` operator, Ormolu will incrementally add
more indentation, which may sometimes be undesirable.

If splitting a definition, put a linebreak just after `=` or `<-`, and
indent the following expression with two spaces. E.g,
For instance, the following expression:

```haskell
someAtrociouslyLongVariableName <- someFunction $ someOtherFunction withSomeVar $ someThirdFunction something $ map somethingElse
```

Should be:
Will turn in to:

```haskell
someAtrociouslyLongVariableName <-
someFunction $
someOtherFunction withSomeVar $
someThirdFunction something $
map somethingElse
someThirdFunction something $
map somethingElse
```

This would also be acceptable:
If you'd rather keep everything equally nested, consider using the `&` operator
instead, which is like a reverse `$`. The code above is semantically identical
to this:

```haskell
someAtrociouslyLongVariableName <-
someFunction $ someOtherFunction withSomeVar $
someThirdFunction something $
map somethingElse
```

### Long Type Signatures

Formatting of long type signature are different from long expressions,
and should be done as

```haskell
analyseBindings :: Lore lore =>
[In.Binding lore]
-> ([Out.Binding lore] -> RangeM a)
-> RangeM a
someAtrociouslyLongVariableName <-
map somethingElse
& someThirdFunction something
& someOtherFunction withSomeVar
& someFunction
```

### Hanging Lambdas

You may or may not indent the code following a "hanging" lambda. Use
your judgement. Some examples:

```haskell
bar :: IO ()
bar = forM_ [1, 2, 3] $ \n -> do
putStrLn "Here comes a number!"
print n

foo :: IO ()
foo = alloca 10 $ \a ->
alloca 20 $ \b ->
cFunction a b
```

### Export Lists

Format export lists as follows:

```haskell
module Data.Set
(
-- * The @Set@ type
Set
, empty
, singleton
( -- * The @Set@ type
empty,
Set,
singleton,

-- * Querying
, member
member,
) where
```
### If-then-else clauses
Expand All @@ -241,87 +185,17 @@ foo True = c
Short cases should usually be put on a single line (when line length
allows it).

When writing non-monadic code (i.e. when not using `do`) and guards
and pattern matches can't be used, you can align if-then-else clauses
like you would normal expressions:

```haskell
foo = if ...
then ...
else ...
```

Otherwise, you should be consistent with the 2-spaces indent rule, and
the `then` and the `else` keyword should be aligned. Examples:

```haskell
foo = do
someCode
if condition
then someMoreCode
else someAlternativeCode
```

```haskell
foo = bar $ \qux -> if predicate qux
then doSomethingSilly
else someOtherCode
```

The same rule applies to nested do blocks:

```haskell
foo = do
instruction <- decodeInstruction
skip <- load Memory.skip
if skip == 0x0000
then do
execute instruction
addCycles $ instructionCycles instruction
else do
store Memory.skip 0x0000
addCycles 1
```

### Pattern matching

Prefer pattern-matching in function clauses to `case`. Consider using
[view patterns](https://ghc.haskell.org/trac/ghc/wiki/ViewPatterns),
but be careful not to go overboard.

### Case expressions

The alternatives in a case expression can be indented using either of
the two following styles:

```haskell
foobar = case something of
Just j -> foo
Nothing -> bar
```

or as

```haskell
foobar = case something of
Just j -> foo
Nothing -> bar
```

Align the `->` arrows when it helps readability.

Imports
-------

Imports should be grouped in the following order:

1. standard library imports
2. related third party imports
3. local application/library specific imports

Put a blank line between each group of imports. The imports in each
group should be sorted alphabetically, by module name.

Try to use explicit import lists or `qualified` imports for standard
and third party libraries. This makes the code more robust against
changes in these libraries. Exception: the Prelude.
Expand Down
1 change: 1 addition & 0 deletions shell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ pkgs.stdenv.mkDerivation {
pkgs.zlib.out
pkgs.cabal2nix
pkgs.ghcid
pkgs.ormolu
];
}
Loading

0 comments on commit 87cc177

Please sign in to comment.