To ensure consistency throughout the codebase and to aid any of you that are interested in contributing, please follow these instructions when structuring your code:
The most general rule is to keep everything aligned to a multiple of two spaces,
so we always wrap after = and in, like so:
let
foo =
if quux xs0 then
unlines xs0
else
unlines xs1
in
length fooNote the placement of the then immediately after the if condition, this
ensures the two space indentation is maintained because the positive case is
then indented on the following line.
This applies to data type definitions as well:
data Foo =
Foo {
fooFieldA :: Type
, fooB :: Type
} deriving (Show)
-- OR
data Foo =
Foo {
fooFieldA :: Type
, fooSecondField :: Type
} deriving (Show)data Bar =
A
| B
| C
deriving (Show)Long type signatures such as this are acceptable.
function :: (MonadTest m, Applicative f, Traversable t) => t a -> Text -> f (a -> b) -> m ()
function = ...But should a type signature become so long that is not easy to read on a single line, please use the following as guides. Remembering to maintain the two space indentation:
function :: (
MonadTest m
, Applicative f
, Traversable t
)
=> t a
-> Text
-> f (a -> b)
-> m ()
function = ...If the function uses a forall then include it before the first parenthesis:
function :: forall m f t b. (
MonadTest m
, Applicative f
, Traversable t
)
=> t a
-> Text
-> f (a -> b)
-> m ()
function = ...A line length limit of approximately 72-80 characters is suggested, but it is not a strict rule. A line that is 87 characters long because it needs to be, won't result in your code being rejected. :)
Avoid names like LogOfUpdates and use single word names like Journal. This
applies to functions, variables, data types, and everything else.
Avoid acronyms and abbreviations. Names such as x, x0, x1, y, xs are fine for locals where types are obvious.
Avoid:
type Bar = [String]Instead:
newtype Bar =
Bar {
unBar :: [String]
} deriving (Show)Avoid :
instance Monoid Foo where
mappend = (<>)
mempty = Foo memptyPut these on new lines, for example:
instance Monoid Foo where
mappend =
(<>)
mempty =
Foo memptyAvoid:
function :: MonadTest m => Foo -> Bar -> m ()
function foo bar = otherFunction foo newBar
where
newBar = someOtherFunction barInstead use a let expression:
function :: MonadTest m => Foo -> Bar -> m ()
function foo bar =
let
newBar =
someOtherFunction bar
in
otherFunction foo newBarNote that the two space indentation is maintained for the variables in the let binding.
Avoid pattern matching out variables and use if expressions over guards. The
following function would be rejected and you will be asked to change it to the
subsequent example.
function :: Foo -> Bar
function (Foo fooA nestedBar)
| null fooA = mempty
| otherwise = nestedBarUse an if or a case instead, bearing in mind that the indentation
requirements still apply.
function :: Foo -> Bar
function foo =
if null (fooA foo) then
mempty
else
nestedBar fooUse of the LambdaCase language extension is encouraged. Using this extension
you would rewrite the following function:
foo :: Quux -> String
foo (Foo xs) =
unlines xs
foo (Bar xs) =
unlines xsTo this layout:
foo :: Quux -> String
foo = \case
Foo xs ->
unlines xs
Bar xs ->
unlines xsUsing decimal numbers instead is preferred as they are harder to miss when reading the code:
Avoid: x',x''.
Instead: x0, x1.
This works better with how Hedgehog tests are normally written and allows for more general use, and leaning on parametricity is always helpful.
Avoid:
... :: ... -> PropertyT IO ()
--
... :: ... -> Gen aInstead:
... :: MonadTest m => ... -> m ()
--
... :: MonadGen g => ... -> g aIf you require IO within the m then use the MonadIO constraint:
Instead:
... :: (MonadIO m, MonadTest m) => ... -> m ()The general order of module declarations is:
- GHC/HADDOCK options pragmas
- LANGUAGE pragmas
- Module haddock documentation
- Module declaration
Any change to the layout of imports should aim for the minimal number of changes
to lines and spacing. Adding a single function to an import list and then
applying stylish-haskell can result in a huge diff when it comes to reviewing
the code. So try to use it sparingly.
The two space indentation rule still applies for module headers:
{-# OPTIONS_HADDOCK not-home #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}
module Hedgehog.Internal.Source (
LineNo(..)
, ColumnNo(..)
, Span(..)
, getCaller
-- * Re-exports from "GHC.Stack"
, CallStack
, HasCallStack
, callStack
, withFrozenCallStack
) whereALWAYS USE EXPLICIT IMPORT LISTS!
Do not:
import GHC.ConcUse qualified imports and make the alias something easily identifiable, avoid single letter aliases.
import qualified GHC.Conc as Conc
import qualified Text.Read as Read
import qualified Data.Text as TextIf necessary you can include periods in the alias name:
import qualified Data.ByteString as ByteString
import qualified Data.ByteString.Lazy as ByteString.Lazy
import qualified Data.Text as Text
import qualified Data.Text.Lazy as Text.LazyIn cases where the API doesn't overlap, this is better:
import qualified Control.Monad.Trans.State.Lazy as Lazy
import qualified Control.Monad.Trans.State.Strict as Strict
import qualified Control.Monad.Trans.Writer.Lazy as Lazy
import qualified Control.Monad.Trans.Writer.Strict as Strict
import qualified Data.ByteString as Strict
import qualified Data.ByteString.Lazy as LazyIf you need to use types from a module then they should be in their own import declaration:
import GHC.Conc (TVar)
import qualified GHC.Conc as ConcIf the function is available to users then it needs to have documentation. Where appropriate this should include examples of use.