A document describing how I like Haskell code to be written. This is how I write my code and expect code submitted to my projects to be in. I'll probably link this to you if I don't sound particularly happy with a patch you sent me.
Tip: this guide is very easy to follow when you have an editor like Emacs with structured-haskell-mode, which opinionatedly applies this style guide automatically.
I may write an automatic formatter with HSE to formalize this style at some point.
Indent two spaces. No tabs. Four spaces is also okay, but make sure to configure it in your editor so that it's enforced consistently.
Prefer 80 columns, but don't waste your time re-working code just to fit within it. There, 120 is acceptable too. Don't try to shoehorn code that shouldn't be broken up (like long strings) onto multiple lines. Let them be.
Always document the module header:
-- | What this module does.
module Foo where
Put the where
on the same line unless there are exports.
If there are exports then write them like this, with the where
coming on the line after.
module Foo
(a
,b
,c)
where
Put imports one empty line after the module header. Always put imports on separate lines, and sort them alphabetically:
module Foo where
import X
import Y
Always group import lists starting with your own project-local imports first, because they are more important and fewer in number:
module Foo.Bar where
import Foo.Zot
import Foo.Bob
import Control.Monad
import Data.Text
import Data.List
import Data.Maybe
import System.IO
import System.Process
Write import lists like this:
import X (foo,bar)
And if they don't fit on one line, like this:
import X (foo
,bar)
But prefer that if you have more than one, instead use separate import lines:
import X (foo)
import X (bar)
If you have many imports, prefer explicit qualification:
import qualified X as Z
Prefer to import types unqualified:
import qualified Data.Text as T
import Data.Text (Text)
Unless they overlap, in which case it will be T.Text
.
All declarations should be surrounded by a blank line. Declarations should not have blank lines in them. If your code is so complicated, split it up into names.
Always order declarations in the order that they're used, top-down:
main = foo bar
foo = bob
bar = zot
Always add type-signatures to top-level functions once they are written, and document them:
-- | Main entry point.
main :: IO ()
main = foo bar
-- | Pretty print a name.
foo :: String -> IO ()
foo = bob
-- | Default dummy string.
bar :: String
bar = zot
Prefer shorter functions to longer functions:
main =
do foo bar (z * zz) …
zot bob (z * zz) …
(Imagine longer code.)
This is improved by:
main =
do openConnection
makeCake
where openConnection = foo bar y …
makeCake = zot bob y …
y = z * zz
This better documents what the function is doing.
When the code is larger, this is further improved by:
main =
do openConnection
makeCake
where y = z * zz
openConnection y = foo bar y …
makeCake y = zot bob y …
Because decoupling allows for more code-reuse and easier to understand context.
Always layout sum types so that the =
and |
line up. Always
document data types. Always put parentheses around derivings.
-- | Lorem ipsum amet patate.
data Foo
= X
| Y
| Z
deriving (A)
Always layout non-sum record types like this, and document the fields.
-- | Some record type.
data Foo = Foo
{ fooBar :: X -- ^ Bar stuff.
, fooMu :: Y -- ^ Mu stuff.
, fooZot :: Z -- ^ Zot stuff.
}
deriving (B)
Always indent the parent before the children:
parent child1 child2
Or:
parent child1
child2
Or, when preferred, due to line length constraints:
parent
child1
child2
Never mix and match single-line versus multi-line:
parent child1 child2
child3
child4
Never dangle children undearneath and behind the parent:
parent
child1
child2
The =
, ->
syntactic sugar are exceptions to the rule because they
denote separation from the actual parent, not a parent of their own:
foo =
bar
case x of
Foo bar mu ->
a b c
case y of
X a
| p x ->
bob foo
| otherwise ->
gogo gadget
\x y ->
z y
Always treat the do
as the parent:
len = do foo
bar
mu
or
len =
do foo
bar
mu
or
$(do runIO (putStrLn "Hello, World!")
return [])
There is also the extreme space-saving layout following the general parent-child layout guide:
len =
do
foo
bar
mu
Never use (>>)
where do
will do:
main = do bar
mu
Not
main = bar >> mu
Never use operators when a simple English name is provided:
len = fmap length getLine
demo = over _1 (+2) (5,4)
Not:
len = length <$> getline
demo = _1 -- whatever the lens operator is to do `over'
Never use ($)
to avoid parentheses:
len = foo $ bar mu zot
len = foo (bar mu zot)
fork = forkIO $ do go go
fork = forkIO (do go go)
Always space out multi-operator expressions:
foo = x y * z * y
If you can't resist using ($)
, never mix it with (.)
like this:
foo = foo . bar . mu $ zot bob
Use parens:
foo = (foo . bar . mu) zot bob
(Tee hee!)
Prefer to use composition where functions are expected, not intermediate expressions:
foo = (foo . bar . mu) zot bob
This is okay.
foo = meaning zot bob
where meaning = foo . bar . mu
This is better.
Always indent where clauses two spaces:
main =
do hello
world
where go = print "Hello!"
Sometimes it's okay to also put a newline after the where and indent:
main = go
where
go = print "Hello!"
Never use let-expressions in a right-hand-side:
main = let x = y
in x
Instead prefer a where
:
main = x
where x = y
Write let
expressions inside a do
in a bottom-up style:
main = do a <- return 1
let x = 123
y = x * a
print y
When in a do
, let … in
must be laid out differently:
main = do a <- return 1
let x = 123
y = x * a
in print (x * y)
print y
Align the in
with the other children, like in if … then … else
syntax.
First, write your code on the same line as the parent:
foobarMu = parent child1
child2
But prefer to bring right-hand-sides (of =
, ->
) down. Bring the
whole expression down:
fooBarMu =
parent child1
child2
If you need to save space, bring the children down:
fooBarMu =
parent
child1
child2
Write tuples, lists and records without spaces:
(a,b,c)
[a,b,c]
{x = a,y = b,z = c}
Layout multi-line tuples, lists and records with prefix comma:
(a
,b
,c)
[a
,b
,c]
{x = a
,y = b
,z = c}
Always line up then
and else
with the condition when laying out if
on multiple
lines:
if x
then y
else x