If something is not clear please examine the code base this is attached to in order to get a feel for how things should be written.
Modifed version of https://github.com/tibbe/haskell-style-guide/blob/master/haskell-style.md
Maximum line length is 80 characters. This forces a style where you make things readable.
Rare exception: When pattern matching long/nested data types.
Tabs are illegal. Use spaces for identing. Indent your code blocks with 2 spaces.
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
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 function 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.
Single newtype or data constructors can be written on a single line.
newtype UserId = UserId Int
data Person = Person String String Int
If more than one constructor align the data constructors one line down and indented 2 spaces.
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
]
Small easy to read lists can remain on one line. Example:
example = [ Foo, Bar, Baz ]
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 and pattern matches should be preferred over if-then-else clauses, where possible. 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:
foo =
if ...
then ...
else ...
bar =
if ...
then do
...
else do
...
Single simple expressions:
foobar =
case something of
Just j -> foo
Nothing -> bar
Complex expressions:
foobar =
case something of
Just j -> do
foo a b c
Nothing -> do
bar d e f
Aligning arrows to help readability is ok.
Imports should be grouped in the following order:
- standard library imports
- related third party imports
- 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.
Use camel case (e.g. functionName) when naming functions and upper camel case (e.g. DataType) 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.
Keep variables purposefully short and un-descriptive. If documenting what a variable does is required, prefer type synonyms instead. Example:
type FirstName = Text
type LastName = Text
type Age = Int
mkPerson :: FirstName -> LastName -> Age -> Person
mkPerson f l a = Person f l a
Prefer keeping the short descriptive logic immediatley after the definition.
foo :: String -> [String]
foo s = bar . baz $ s
where
bar = ...
baz = ...
If type signatures are too long format each argument one per line. Example:
drawAnimation :: SDL.Renderer
-> Maybe SpriteSheet
-> Maybe ScreenPosition
-> Maybe [Sprite]
-> Animation
-> IO ()