Skip to content

Latest commit

 

History

History
224 lines (157 loc) · 5.67 KB

STYLEGUIDE.md

File metadata and controls

224 lines (157 loc) · 5.67 KB

Juvix's Haskell Style Guide

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.

HLint

Set up HLint in your code environment and follow its code suggestions as much as you can.

General naming convention

For anything code related: modules, functions and variables, filenames etc. we will use the American English spelling.

Modules and directories

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.

Pattern matches

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) = ...

Formatting

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.

Line Length

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.

Whitespace

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

Declaration comments

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

Imports should be grouped in the following order:

  1. standard library imports
  2. related third party imports
  3. 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:

  1. easily determine what code code comes from where and refactoring dependencies
  2. 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 = ...

Exports

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 (..).

Idioms

The following idioms have become standard in the code base:

  • The type T in some module Foo 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.

Warnings

-Wall is turned on. Keep warnings to the minimum.