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 wherePut 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)
wherePut imports one empty line after the module header. Always put imports on separate lines, and sort them alphabetically:
module Foo where
import X
import YAlways 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.ProcessWrite 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 ZPrefer 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 = zotAlways 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 = zotPrefer 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 * zzThis 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 child2Or:
parent child1
child2Or, when preferred, due to line length constraints:
parent
child1
child2Never mix and match single-line versus multi-line:
parent child1 child2
child3
child4Never dangle children undearneath and behind the parent:
parent
child1
child2The =, -> 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 yAlways treat the do as the parent:
len = do foo
bar
muor
len =
do foo
bar
muor
$(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
muNever use (>>) where do will do:
main = do bar
muNot
main = bar >> muNever 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 * yIf you can't resist using ($), never mix it with (.) like this:
foo = foo . bar . mu $ zot bobUse 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 bobThis is okay.
foo = meaning zot bob
where meaning = foo . bar . muThis 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 xInstead prefer a where:
main = x
where x = yWrite let expressions inside a do in a bottom-up style:
main = do a <- return 1
let x = 123
y = x * a
print yWhen 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 yAlign 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
child2But prefer to bring right-hand-sides (of =, ->) down. Bring the
whole expression down:
fooBarMu =
parent child1
child2If you need to save space, bring the children down:
fooBarMu =
parent
child1
child2Write 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