(Originally from Johan Tibell's style guide, with modifications from the Snap framework's style guide, additions from the GHC Coding Style Guidelines and further inspiration from the style used at Well-Typed LLP, as seen e.g. in distributed-process.)
This is a short document describing the preferred Haskell coding style at Tweag I/O. When something isn't covered by this guide you should stay consistent with the code in the other modules.
(section adapted from the GHC Coding Style Guidelines.)
It's much better to write code that is transparent than to write code that is short.
Often it's better to write out the code longhand than to reuse a generic abstraction (not always, of course). Sometimes it's better to duplicate some similar code than to try to construct an elaborate generalisation with only two instances. Remember: other people have to be able to quickly understand what you've done, and overuse of abstractions just serves to obscure the really tricky stuff.
The general rule is to stick to the same coding style as is already used in the file you're editing. If you must make stylistic changes, commit them separately from functional changes, so that someone looking back through the change logs can easily distinguish them.
Maximum line length is 80 characters. Comments should be wrapped accordingly. There should be no trailing whitespace anywhere in your code. This makes git diff output look ugly and causes spurious merge conflicts.
In Emacs, you can add the following code to your init.el
file to
enforce this:
(add-hook 'haskell-mode-hook (lambda () (set-fill-column 80)))
(add-hook 'haskell-mode-hook
(lambda ()
(add-hook 'before-save-hook 'delete-trailing-whitespace t t)))
Tabs are illegal. Use spaces for indenting. Use 2 spaces for each
indentation level. The only exception is for code blocks inside
a definition, which should be indented with 4 spaces. Indent the
where
keyword two spaces to set it apart from the rest of the code
and indent the definitions in a where
clause 2 spaces. Guards are
usually indented 2 spaces. Some examples:
sayHello :: IO ()
sayHello = do
name <- getLine
putStrLn $ greeting name
where
greeting name = "Hello, " ++ name ++ "!"
filter :: (a -> Bool) -> [a] -> [a]
filter _ [] = []
filter p (x:xs)
| p x = x : filter p xs
| otherwise = filter p xs
As a general rule, indentation of a line should not depend on the length of any identifier in preceding lines, only on layout constraints.
One blank line between top-level definitions. No blank lines between type signatures and function definitions. Add one blank line between functions in a type class instance declaration if the functions bodies are large. Use your judgement.
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. Don't insert space inside a parenthesized expression.
Don't vertically align type signatures, patterns in function declarations and comments. This just causes spurious merge conflicts.
If an application must spawn multiple lines to fit within the maximum line length, then write one argument on each line following the head, indented by one level:
let'sFold xs = do
foldr
(\x y -> ...)
Nothing
xs
But consider naming the arguments instead to avoid multi-line expressions.
Align the constructors in a data type definition. Example:
data Tree a
= Branch !a !(Tree a) !(Tree a)
| Leaf
Format records as follows:
data Person = Person
{ firstName :: !String -- ^ First name
, lastName :: !String -- ^ Last name
, age :: !Int -- ^ Age
} deriving (Eq, Show)
Align the elements in the list. Example:
exceptions =
[ InvalidStatusCode
, MissingContentHeader
, InternalServerError
]
Put pragmas immediately following the function they apply to. Example:
id :: a -> a
id x = x
{-# INLINE id #-}
In the case of data type definitions you must put the pragma before the type it applies to. Example:
data Array e = Array {-# UNPACK #-} !Int !ByteArray
LANGUAGE
pragmas should enable a single language extension per line,
for easy addition and deletion.
You may or may not indent the code following a "hanging" lambda. Use your judgement. Some examples:
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
Format export lists as follows:
module Data.Set
( -- * The @Set@ type
Set
, empty
, singleton
-- * Querying
, member
) where
Generally, guards should be preferred over if-then-else expressions, where possible. if-then-else is preferred to case analysis on a boolean. 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
can't be used, you can align if-then-else expressions like you would
normal expressions:
foo =
if ...
then ...
else ...
In monadic code, so long as you use the Haskell 2010 dialect and above, you can use the same alignment as above. A different alignment rule for monadic code is no longer necessary.
The alternatives in a case expression can be indented using either of the two following styles:
foobar = case something of
Nothing -> foo
Just j -> bar
or as
foobar =
case something of
Nothing -> foo
Just j -> bar
Imports should be listed in alphabetical order with no intervening
blank lines, except for any explicit Prelude
import, which must
always come last. The reason for this exception is that some redundant
import warnings are sensitive to the order of the Prelude
import.
Always 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.
Use your judgement when it comes to local application/library specific imports. On the one hand, they make for more maintainable code because identifiers that are removed from the imported module will be caught early and identifiers added to the imported module do not risk clashing with local identifiers. They also serve as documentation as to which parts of a module are actually required.
However, explicit import lists are also much more verbose, and slow down development. Moreover, in a collaborative environment, explicit import lists can cause spurious conflicts, since two otherwise unrelated changes to a file may both require changes to the same import list.
The qualifier for well known modules, such as ByteString
can be
shortened further, eg BS
. But in general, prefer descriptive
qualifiers rather than one letter ones. For example
import qualified Data.Map as Map -- good
import qualified Data.Map as M -- not so good
Comments should be placed immediately before the line(s) of code they pertain to.
Write proper sentences; start with a capital letter and use proper punctuation.
End-of-line comments should be separated from code by at least two spaces.
A copyright notice, if any is desired, should appear at the very first non-blank line in the file, so as to make its scope clear and the notice easy to find. Use Haddock fields to include it in API documentation. Example:
-- |
-- Copyright: (c) 1901-1910 Foo corp
-- License: All rights reserved.
Comment every top-level function (particularly exported functions), and provide a type signature; use Haddock syntax in the comments. Comment every exported data type. Function example:
-- | Send a message on a socket. The socket must be in a connected
-- state. Returns the number of bytes sent. Applications are
-- responsible for ensuring that all data has been sent.
send
:: Socket -- ^ Connected socket
-> ByteString -- ^ Data to send
-> IO Int -- ^ Bytes sent
For functions the documentation should give enough information to apply the function without looking at the function's definition.
Record example:
-- | Bla bla bla.
data Person = Person
{ age :: !Int -- ^ Age
, name :: !String -- ^ First name
}
For fields that require longer comments format them like so:
data Record = Record
{ -- | This is a very very very long comment that is split over
-- multiple lines.
field1 :: !Text
-- | This is a second very very very long comment that is split
-- over multiple lines.
, field2 :: !Int
}
Use inline identifier links economically. You are encouraged to add links for API names. It is not necessary to add links for all API names in a Haddock comment. We therefore recommend adding a link to an API name if:
- The user might actually want to click on it for more information (use your judgment), and
- Only for the first occurrence of each API name in the comment (don't bother repeating a link)
Use mixed-case when naming functions and camel-case when naming data types.
For readability reasons, don't capitalize all letters when using an
abbreviation. For example, write HttpServer
instead of HTTPServer
.
Exception: Two letter abbreviations, e.g. IO
.
Where appropriate, add an unabbreviated prefix to the name of record fields. Example:
-- | Messages consist of their typeRep fingerprint and their encoding
data Message = Message
{ messageFingerprint :: !Fingerprint
, messageEncoding :: !BSL.ByteString
}
This is not necessary for modules that export only one data type and are meant to be imported qualified.
Use singular when naming modules e.g. use Data.Map
and
Data.ByteString.Internal
instead of Data.Maps
and
Data.ByteString.Internals
.
Code should be compilable with -Wall -Werror
at a minimum, i.e.
there should be no warnings. -Wall
does not turn on all warnings, so
the following set:
-Wall
-Wcompat
-Wincomplete-record-updates
-Wincomplete-uni-patterns
-Wredundant-constraints
-Wnoncanonical-monad-instances
Use of unsafePerformIO
-based debugging facilites, such as
Debug.Trace
should not be committed to any stable branches. If you
need logging, use a proper method and ship it, so that further
development benefits from your debugging efforts.
Where appropriate, define lenses for all fields in a record. Use the
_
prefix to name fields. When using the lens package, use
makeClassy
where possible to generate lenses
rather than makeLenses
. This is to make it easy to export all lenses
for a record all at once.
module Person
( Person(..)
, HasPerson(..)
) where
data Person = Person
{ _firstName :: !String -- ^ First name
, _lastName :: !String -- ^ Last name
, _age :: !Int -- ^ Age
} deriving (Eq, Show)
makeClassy ''Person
For consistency, if a record has lenses defined, always use the lens
to get or set the field of a record (rather than _fieldName
). Field
names should only be used to initialize the record.