This document describes the preferred coding style for Juvix. When something isn't covered by this guide you should stay consistent with the existing code. See also CONTRIBUTING.md.
Set up HLint in your code environment and follow its code suggestions as much as you can.
For anything code related: modules, functions and variables, filenames etc. we will use the American English spelling.
Module and directory names should be written as nouns:
Translation
Contextualization
Desugaring
We should avoid prefixes in module names as much as possible, this is what
sub-directories are for, i.e. Frontend.Contextualize
instead of
FrontendContextualize
.
Directory names should start with a small letter, unless they match with a module in the Haskell code:
library/frontend <-- frontend is not part of the module structure.
library/frontend/src/Juvix <-- Juvix is a module.
When pattern matching a data constructor, using variables even for unused
fields does improve the readability of the code. To avoid numerous
-Wunused-matches
warnings, we prefix unused variables with _
:
func (RawDatatype _ _ _ lvl _) = ...
func_better (RawDatatype _n _pos _a lvl _cons) = ...
Our formatter is Ormolu (the latest version on Stack). The main reason we chose Ormolu is that it works for all the extensions we use. See this post and this post for more motivations and rationale.
Unless otherwise specified below, follow Tweag's style guide.
Maximum line length is 80 characters. There should be no trailing whitespace anywhere in your code.
- In Emacs, you can add the following code to your
init.el
file to enforce this:
(add-hook 'haskell-mode-hook (lambda () (set-fill-column 80)))
(add-hook 'haskell-mode-hook
(lambda ()
(add-hook 'before-save-hook 'delete-trailing-whitespace t t)))
-
In VScode, use the Rewrap extension to enable hard word wrapping.
-
In Vim, follow this to enable hard word wrapping.
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. For example:
plus4 n = n + 4 -- whitespace on either side of `+`
(\x -> x + 4) -- no space after the lambda
When adding comments as part of a declaration, we will be using the layout as enforced by our formatter, ormolu:
data Parameterisation primTy primVal
= Parameterisation
{ -- | Check if a value is of a given type.
hasType :: primVal -> PrimType primTy -> Bool,
-- | Set of builtin types.
builtinTypes :: Builtins primTy,
}
Ormolu prefers to have ,
and ->
at the end of a line, and will rewrite any
usage of -- ^
into -- |
. Special care has to be taken that, if used -- ^
,
will be at the place at the correct place. The following is incorrect, and will
not be successfully parsed by the code formatter:
op ::
Int -> -- ^ left hand side of operator.
Int -> -- ^ right hand side of operator.
Int
Note that the comments are not pointing to any argument, as there are placed
after the ->
. To fix this, the ->
should be placed at the beginning of the
lines, and the code formatter will work correctly.
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.
Always use explicit import lists or qualified
imports for standard
and third party libraries. The Juvix Standard Library is an exception.
Qualified imports help two fold:
- easily determine what code code comes from where and refactoring dependencies
- Enforce consistency between different Haskell modules. For example:
Before:
module Usage where
data UsageType = ...
usage = ...
module Nat where
data NatType = ...
nat = ...
After:
module Usage where
data Type = ...
t = ...
module Nat where
data Type = ...
t = ...
Modules should export their datatypes and functions explicitly. The order of the exports should preferably follow the same order as they are defined in the file. Re-exported modules should always follow at the end, for example:
module Mod
( Mod (..),
modFunc,
module SubMod,
)
where
Typically the exports should be kept to a minimum. When exporting datatypes it
is OK to export all constructors using (..)
.
The following idioms have become standard in the code base:
- The type
T
in some moduleFoo
denotes the main type of the module. - The function
op
denotes the main functionality of a module and is stateless. - The function
exec
denotes the main functionality of a module and is stateful.
-Wall
is turned on. Keep warnings to the minimum.