This is a Style Guide for all the projects within the euler-hs repository. This is a nice-to-follow guide, still it's not pushed hardly to avoid unnecessary conflicts and friction. You should decide on your own about how to structure your code. Still remember that your code will be read by other people.
- Recommended line length is 80 characters.
- Maximum line length is 120 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.
- Tabs are illegal. Use spaces for indenting.
- Recommended to use 2 spaces for each indentation level.
- Allowed to use 4 spaces if this seems more appropriate for certain situations.
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.
Avoid hanging let-expressions in do blocks. This makes it harder to refactor and brings more unstructured noise into the code, as well as makes resolving conflicts in git harder.
Avoid:
someFunc = do
let a = calcA 10
b = calcB 20
pure (a + b)
someFunc = do
let
a = calcA 10
b = calcB 20
pure (a + b)
Prefer:
someFunc = do
let a = calcA 10
let b = calcB 20
pure (a + b)
Two blank lines between top-level definitions, and a line of 78 "-" characters to delineate top-level definitions from each other. 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 judgment.
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.
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)
data Person
= Person
{ firstName :: !String -- ^ First name
, lastName :: !String -- ^ Last name
, age :: !Int -- ^ Age
} deriving (Eq, Show)
| SomeThing
{ foo :: !String -- ^ Foo
, bar :: !String -- ^ Bar
, baz :: !String -- ^ Baz
}
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
Right-hand side of ->
can be moved to the next line
foobar =
case something of
Nothing -> foo
Just j ->
someLongLongLong $ bar $ foo $ baz [1,2,3]
->
should be aligned
->
in case should be aligned
...
case foo of
Bar -> ()
BazBaz -> ()
<-
in do should be aligned
...
do
a <- Bar
bar <- BarBazaz
pure ()
=
in guards should be aligned
foo a b
| a < b = ()
| otherwise = ()
Imports should be listed in alphabetical order with no intervening
blank lines, except for any explicit Prelude
import, which must
always come first. 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.
End-of-line comments should be separated from code by at least two spaces.
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 camel-case when naming values (fooBar
) and data
types (FooBar
).
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
.
N.B. This section is under construction.
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.