Google Style Guides

Google’s Haskell Style Guide

Revision 1.01

Background

Haskell is a mature, general purpose, purely functional language. It is noted for its strong static type system (with type inference), lazy (technically, ‘non-strict’) execution semantics, higher-order functions, parametric polymorphism, type classes, and monadic effects.

Haskell’s syntax is fairly concise, and most of the choices you might make about style are generally closely tied to the semantics of the code you wish to convey. As such, this guide tends to be somewhat less prescriptive than most, as there are just fewer arbitrary choices to make when coding Haskell.

Open-source projects developed by Google should conform to the requirements in this guide.

Structure

Haddock

The module and all exported top-level elements should have Haddock.

Haddock should directly precede the element it documents. It need not repeat argument names or types, as they are readily apparent from the declaration.

Write proper sentences; start with a capital letter and use proper punctuation.

Exception: Extremely simple type synonyms or other very obvious declarations need not have any Haddock.

type Forest p a = [Tree p a]    -- simple enough to not need Haddock

-- | Returns values in the Forest from entries that cover the given prefix.
-- The value list is ordered from most general to most specific entries.
forestLookup :: (Prefix p) => p -> Forest p a -> [a]

Use in-line links economically in Haddock. Links are encouraged, but it is not necessary to add links for all API names. Add a link if:

Exports

In addition to the rules above, don’t re-export modules as a “convenience”. However, it is acceptable to re-export modules in these two circumstances:

Exception: It is acceptable to not have an explicit exports list when the bulk of the exported definitions are generated with Template Haskell, since explicitly naming them in the export list would be counter-productive.

Imports

In general, use the last component of the module name as the qualified name.

It is acceptable to use the same prefix for related modules.

Exception: Some modules are designed to be reasonably imported without an import list, such as Data.Bits, and this is acceptable.

Very short names for qualified imports are acceptable for the following modules, as they are in very wide use in Haskell source:

import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BL
import qualified Data.HashMap.Strict as HM    -- or Data.HashMap.Lazy as needed
import qualified Data.HashSet as HS
import qualified Data.Map as M
import qualified Data.Set as S
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL

Putting these all together:

import Data.Bits                             -- okay for some modules
import Data.Maybe (mapMaybe, maybeToList)    -- better to list what you use...
import qualified Data.Ratio as Ratio         -- ...or import qualified
import qualified Data.Text as T              -- short name allowed for this
import qualified Data.Text.Encoding as T     -- reuse name for related modules
import qualified Data.Text.IO as T
import qualified Text.Parsec as Parsec
import qualified Text.Parsec.Expr as Parsec

import AnInternalProject.Data.WhoopTree      -- okay to import all, but discouraged

Pragmas

GHC options and LANGUAGE pragmas go at the very top of the file.

The OPTIONS_GHC directive should only be used for specific options that relate to the source file, such as -fno-warn-orphans for test code. General compilation options, like -O2, belong in the .cabal file.

Features enabled by the LANGUAGE pragma should always be listed in each source file that needs them, and never turned on globally via flags in the the .cabal file. This is so future developers know what language extensions are in effect when reading the code.

INLINE, INLINEABLE, and similar pragmas should only be used when you’ve tested that they actually improve speed in cases that you care about. When used, they should follow directly the declaration they apply to.

The following GHC language extensions are non-controversial and can be used as needed. That said, consider if use of the extension improves the code in proportion to the extra burden it gives to future readers:

Other GHC extensions should be avoided, as they may or may not be stable, increase the burden on readers to know them, and may force clients to use them.

Warnings

Code should compile cleanly with no warnings from GHC.

GHC emits very useful warnings when given the -Wall argument. Code should compile with both -Wall and -Werror, ensuring warnings are not ignored.

(You get the same effect in GHCi by adding :set -Wall ... to your ~/.ghc/ghci.conf file.)

In the rare cases when it’s necessary to declare an instance in a module which contains neither the corresponding class declaration nor the corresponding type declaration, warnings about orphans can be suppressed by adding {-# OPTIONS_GHC -fno-warn-orphans #-} line at the top of the relevant file.

Style

Layout

Use layout, rather than braces and semicolons.

Except for one-liners, use layout rather than braces and semicolons. It makes for clearer code.

Type Declarations

All top level functions should have type declarations.

Even though Haskell supports type inference, all top-level functions should include type declarations. If nothing else, type declarations provide good documentation.

Where reasonable, use the most polymorphic possible type.

Comments

Use -- for all comments except pragmas, or where the comment must be in the middle of a line.

If there is text on the line before the dashes, there should be at least two spaces immediately before the dashes. If there is text after the dashes, there should be a space immediately after the dashes.

As per the section on Haddock, all top level elements should have a preceding comment for the purpose of Haddock. The aim is to explain the purpose of the function, not the implementation.

The implementation should be clear from the code, but comments in the code should be used to explain any non-obvious reasoning for why something was done a particular way, or is particularly tricky.

if…then…else

Consider using guards and pattern matches over if…then…else.

Generally, pattern matches and guards are clearer at the outer level of a function than nested if and case constructs. However, elsewhere if might be more clear. Use your judgement.

nth :: Int -> [a] -> Maybe a
nth i xs = if null xs || i < 0
    then Nothing
    else if i == 0
        then Just (head xs)
        else nth (i-1) (tail xs)
nth :: Int -> [a] -> Maybe a
nth i _     | i < 0 = Nothing
nth _ []            = Nothing
nth 0 (x:xs)        = Just x
nth i (_:xs)        = nth (i-1) xs

Idioms

Functional programming has given rise to a large number of new programming idioms. The following idioms are encouraged:

Partial Functions

Partial functions are those that are not defined for all possible arguments.

Strenuously avoid partial functions from the Prelude and base libraries. Commonly used examples are: head, (!!), minimum, fromJust, and read.

Exception: In cases where pattern matching, guards, or other logic immediately nearby makes it certain that these functions are applied safely, it is acceptable to call them.

defaultChoice :: [String] -> String
defaultChoice [] = "beige"
defaultChoice opts = minimum opts
  -- okay because pattern match ensures non-empty

digitsReversed :: Integer -> Integer
digitsReversed i = (* signum i) . read . reverse . show $ abs i
  -- okay because show of a non-negative value only produces digits

Do not write partial functions.

Exception: If a function internal to a module should never reach some case due to invariants maintained by the module, then it is acceptable to call error for those cases, and preferred to returning inaccurate results, or heuristically restoring the invariant:

data PTrie = PT { ptSubs :: [(String, PTrie)] }
  -- Invariant this module maintains: There is no non-empty prefix that is
  -- shared by any two sub-PTries of a given node.

insert :: String -> PTrie -> PTrie
insert s pt = case filter (not . null . greatestCommonPrefix s) $ ptSubs pt of
  [] -> ...   -- no node matches
  [a] -> ...  -- one node matches, need to recurse or split
  _ -> error "PTrie.insert PTrie has subs with a common prefix"

Point-Free

Point-free style is fine and often leads to concise code that focuses on what the code does, rather than on what it does it to.

However, avoid over-using it. For example, do not use contorted point free just to handle multiple arguments:

f = (g .) . h       -- makes most of us puzzle it out
f x = g . h x       -- most readers will get this immediately
f x y = g $ h x y   -- depending on the functions, this may or may not be better

Combinators

Libraries designed to be combinators produce very powerful tools for other developers. Examples to learn from are Parsec and PrettyPrint.

QuickCheck

Tests should be written with QuickCheck whenever possible. This form of testing is surprisingly effective. Use HUnit for special cases that need explicit testing. Tie them all together with Test Framework.

It will often be required to write instances of Arbitrary. These should go in the corresponding _Test module. (With the corresponding option to turn off the warning about orphan instances.) Such instances should be sure to weight cases equally, or if they don’t, explain why.

Naming

General Rules

Use mixed-case identifiers, without underscores.

Haskell’s naming rules are fairly strict, and are not reproduced here, as the language enforces adherence.

Underscores are to be avoided, as mixed-case is the predominant style in the Haskell community. The following uses of underscores are acceptable:

Use the common idiom of appending tick marks (') to names to represent altered values like: fib and fib'.

Use the common idiom of plural for the lists: For example, a pattern match against a list might be (a:as). (See also Short Variables, below.)

simpleMetricComputer :: Double -> Set Things -> Double
simpleMetricComputer _ things = ...
    -- it isn't clear what the first argument is

map :: (a -> b) -> [a] -> [b]
map _f [] = []
map f (a:as) = f a : map f as
    -- no need to name _f in first clause, as it is clear from the second clause
simpleMetricComputer :: Double -> Set Things -> Double
simpleMetricComputer _sizeHint things = ...
    -- clear what the first argument is for, and that it isn't used

map :: (a -> b) -> [a] -> [b]
map _ [] = []
map f (a:as) = f a : map f as
    -- first argument in first clause is clear from context and second clause

Short Variables

Short and single letter variable names are fine.

Where the scope of a variable name is over just a few lines, short or single letter variable names are acceptable and even encouraged. Generally, they help expose the structure of the code.

nextAfter :: Eq element => element -> [element] -> Maybe element
nextAfter match (current:next:remainder) =
    if current == match then Just next
                        else nextAfter match (next:remainder)
nextAfter _ _ = Nothing
nextAfter :: Eq a => a -> [a] -> Maybe a
nextAfter m (x:y:ys) =
    if x == m then Just y
              else nextAfter m (y:ys)
nextAfter _ _ = Nothing

Modules

Use singular when naming modules.

Use Data.Map and Data.ByteString.Internal instead of Data.Maps and Data.ByteString.Internals.

Abbreviations

Abbreviations in names should be mixed-case.

HTTPResponse
IPBlock
HttpResponse
IpBlock

Exception: There are a few cases where there is considerable precedent for a particular case usage of a term. In this case, it is acceptable to retain a specific choice. Note that this does not apply to things like HTTP which are all caps in common usage simply because they are an abbreviation.

GREoIPsec  -- acceptable because specific precedent for this form exists

Formatting

Vertical Spacing

Vertical white space is to be used sparingly, ensuring that more context can be seen in a screenful.

In general, one blank line is enough to separate declarations. Two blank lines should be used between the module imports and the first top level declaration, and between major sections of the file, if needed. No blank lines are required between declarations in a where clause, but are sometimes used if there are many or longer declarations.

{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE TupleSections #-}

-- | Run-length encoding types and functions.
--
-- Run-length encoded sequences are a quick and efficient compression scheme
-- that is so on and so forth....

module RunLength
    ( RunList
    , rleEncode
    , relDecode
    ) where

import Data.List (replicate)


type RunList a = [(Int, a)]

-- | Run-length encode a sequence of elements.
rleEncode :: (Eq a) => [a] -> RunList a
rleEncode [] = []
rleEncode (a:as) = go 1 a as
  where
    go n a [] = [(n,a)]
    go n a (b:bs) | a == b    =         go (n+1) b bs
                  | otherwise = (n,a) : go 1     b bs
-- | Run-length decode a RunList.
rleDecode :: RunList a -> [a]
rleDecode = concatMap (uncurry replicate)

Horizontal Spacing

The aim is to have horizontal spacing help show the structure of the expressions. It is acceptable, and even encouraged, to use horizontal spacing to make the structural relationship between clauses clear.

-- | Run-length encode a sequence of elements.
rleEncode :: (Eq a) => [a] -> RunList a
rleEncode [] = []
rleEncode (a:as) = go 1 a as
  where                        -- see rules for "where" below
    go n a [] = [(n,a)]
    go n a (b:bs) | a == b    =         go (n+1) b bs
                  | otherwise = (n,a) : go 1     b bs

Data declarations should also follow these rules:

data Tree a = Branch a (Tree a) (Tree a)
            | Leaf

data HttpException
    = InvalidStatusCode Int
    | MissingContentHeader

Indentation

As indents serve an important semantic function in Haskell, the indents are on 4 space intervals.

Parts of an expression that are continued on another line, are indented one indent from the indent of the preceding line, not from the construct that they are continuting. (Though see the preceding section about using alignment to make structure clear, and use your judgement.)

arbitrary = arbitrary >>= \b -> if b
    then do
        cidr <- choose (0, 32)
        ip <- pick 32 cidr
        return $ makeBlock (IPv4 ip) cidr
    else do
        cidr <- choose (0, 64)
        hi <- pick 64 cidr
        lo <- pick 64 (cidr - 64)
        return $ makeBlock (IPv6 hi lo) cidr

Comments

Separate end-of-line comments from the code using 2 spaces. Align comments for data type definitions:

data CommandSummary = CommandSummary
    { ciName :: T.Text        -- ^ name and short summary on one line
    , ciSynopsis :: T.Text    -- ^ invocation template, like "foo [-f] file..."
    , ciOptions :: [([T.Text], T.Text)]
                              -- ^ a list of pairs of options synonyms and their
                              -- summary description
    }

where

To separate the local declarations from the main expression, use a hanging indent for where. For all other uses (module, class, and instance declarations), use trailing where.

module Main.TestRouters
    ( TestPrefix, TestMetric(..)
    , prop_routeSameAsNaive
    ) where

class PrefixLength a where
    prefixLength :: a -> Int

instance Arbitrary TestMetric where
    arbitrary = arbitraryBoundedEnum
    shrink = shrinkNothing

updateFlowTable :: (Ord m, Eq p) =>
    FlowMod m p t -> FlowTable m p t -> FlowTable m p t
updateFlowTable u tbl = case u of
    AddFlow m p t -> let (cs,ds) = break ((<= m) . fst) (clean p)
                     in cs ++ (m, (p, t)) : ds
    DeleteFlow p  -> clean p
  where
    clean p = filter ((/= p) . fst . snd) tbl

deriving

The deriving clause can trail the declaration, or go on a line of its own.

data Light = Stop | Slow | Go
    deriving (Eq, Ord, Enum, Bounded, Show)

newtype Max a = Max a deriving (Eq, Ord, Show)

data HttpStats
    = StatusOk
    | StatusRedirectMoved
    | StatusRedirectTemporary
    | StatusNotFound
    deriving (Eq, Show)

data Employee = Employee
    { empName :: String
    , empId :: Integer
    , empDepartment :: Integer
    } deriving (Eq, Ord, Show)

case…of

The of token appears on the same line as the case token. Indent the cases.

Align the -> arrows when it helps readability.

foobar = case something of
    Just j  -> foo
    Nothing -> bar

let…in

The in token appears on the line after the line with the let token, lined up with it.

access add = let (s,a) = add `divMod` segmentSize
                 (p,b) = a `divMod` pageSize
             in getPageByte b . getSegmentPage p <$> getSegment s

If short, the whole let expression can be all on one line:

formatItem x = let (a,b) = x `divMod` 1000 in show a ++ '/' : show b

if…then…else

Indent the then and else.

The then and else are indented from the indent level of the line containing the if, not from the if itself.

foo = if ....
    then ...
    else ...

foo = do
    someCode
    if condition
        then someMoreCode
        else someAlternativeCode

foo = bar $ \qux -> if predicate qux
    then doSomethingSilly
    else someOtherCode

The same rule applies to do blocks nested within:

foo = do
    instruction <- decodeInstruction
    skip <- load Memory.skip
    if skip == 0x0000
        then do
            execute instruction
            addCycles $ instructionCycles instruction
        else do
            store Memory.skip 0x0000
            addCycles 1

Exception: If the expressions are short, it is acceptable to have the whole expression on one line:

adjective n = if odd n then "odd" else "even"

List Formatting

Use “leading comma” list format for multiple line spanning lists.

Lists and other constructs that are surrounded by parenthesis or braces, and are extended over several lines, should be formatted such that a comma starts the subsequent lines, and the closing punctuation is on a line by itself. This applies to export lists, import lists, list values, record field declarations, and record values.

It is acceptable to have several values on a single line, “comma space” separated if they are grouped logically together.

exceptions =
    [ InvalidStatusCode
    , MissingContentHeader
    , InternalServerError
    ]
data Person = Person
    { firstName :: !String  -- ^ First name
    , lastName  :: !String  -- ^ Last name
    , age       :: !Int     -- ^ Age
    } deriving (Eq, Show)
module CPU
    ( CpuState(..), powerOnState

    , zeroPage
    , bitN, bitV, bitD, bitI, bitZ, bitC

    , assignBit
    , assignZN

    , setZN, setAZN, setXZN, setYZN
    , setZVNbit, setACZVN, setCZN

    , fetch, store
    , clearBus
    , fetchIndirectAddr
    , nextPC, fetchPC

    , push, pull, pushAddr, pullAddr, pushP, pullP
    ) where

Operators & Punctuation

Use one space around all operators. Use one space after commas and semicolons. Use no space after a backslash token.

sumSqDelta = dSumSqDelta + delta' * (val' - mean)

(rib', fib') = process updates rib

\s -> s ++ reverse s      -- note: \ is not an operator, no trailing space

quarterlyIncome `mod` 10  -- treat identifiers used as an operator the same

foldr (*) 1 list          -- treat operator in parenthesis as an identifier

For operator sections, don’t add a space on the “unapplied” side:

map (++ " likes noodles") people

Exception: For tuples and common operations consisting of single character values, it is acceptable to elide the space after the comma or operator when it improves readability:

swap (a,b) = (b,a)

foldl' (\v d -> 10*v + digitToInt d) 0

go n m (a:as) | m == a = go (n+1) m as

Parting Words

Use common sense and BE CONSISTENT.

If you are editing code, take a few minutes to look at the code around you and determine its style, and mirror it.

The point of having style guidelines is to have a common vocabulary of coding so people can concentrate on what you are saying, rather than on how you are saying it. We present global style rules here so people know the vocabulary. But local style is also important. If code you add to a file looks drastically different from the existing code around it, the discontinuity throws readers out of their rhythm when they go to read it. Try to avoid this.

OK, enough writing about writing code; the code itself is much more interesting. Have fun!