Skip to content

HaskellMN/haskell-style-guide

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 

Repository files navigation

haskell-style-guide

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.

Indentation

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.

Line length

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.

Modules

Always document the module header:

-- | What this module does.

module Foo where

Put the where on the same line unless there are exports.

Exports

If there are exports then write them like this, with the where coming on the line after.

module Foo
  (a
  ,b
  ,c)
  where

Imports

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.

Declarations

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

Functions

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.

Data types

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)

Expressions

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

Do-notation

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

Operators

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!)

Composition

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.

Where clauses

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!"

Let expressions

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.

Salvaging space

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

Collections

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}

If

Always line up then and else with the condition when laying out if on multiple lines:

if x
   then y
   else x

About

A document describing how I like Haskell code to be written

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy