Formatting + Style

in Haskell

Topics

  • Motivation
  • TVision Haskell Style Guide
  • Formatters
    • Stylish, Brittany, Hindent, Ormolu
  • Lint
    • HLint
  • Code Documentation
    • Haddock
  • Style
    • TVision Haskell Style Guide
  • ​Beyond
    • ​File Watchers

Motivation

  • Readability
  • Writability
  • Maintainability

The balance we choose impacts our code comprehension (hence correctness) and time spent onboarding/maintaining/iterating/reviewing/etc.

TVision Haskell Style Guide

Formatters

Stylish

  • Formats data type declarations and imports
steps:
  - simple_align:
      cases: true
      top_level_patterns: true
      records: true
  - imports:
      align: none
      list_align: after_alias
      long_list_align: new_line_multiline
      empty_list_align: inherit
      list_padding: 2
      separate_lists: true
  - language_pragmas:
      style: vertical
      align: true
      remove_redundant: true
  - trailing_whitespace: {}

Stylish - Alignment

Datatype Alignment

  • | is always aligned with =
  • , in fields is always aligned with {

  • } is likewise always aligned with {
data User = User
 { name :: String
 , homeAddress :: Address
 , age :: Int
 , cell :: PhoneNumber
 }
data User = User
 { name        :: String
 , homeAddress :: Address
 , age         :: Int
 , dob         :: Date
 }

Alignment can also be done on case statements and imports

Stylish - Imports

Import Sorting within manual groupings

import           Control.Applicative ((<$>), liftA2, liftA3))
import           System.Directory    (doesFileExist)

import           Data.Map            (Map, keys, (!))
import qualified Data.Map            as M
import System.Directory (doesFileExist)
import Control.Applicative (liftA3, (<$>), liftA2)

import qualified Data.Map as M
import      Data.Map    ((!), keys, Map)

Hindent

# hindent.yaml
tab-size: 2
line-length: 80
force-trailing-newline: true

Brittany

Brittany - Open Issues

  • Only the module header (imports/exports), type-signatures and function/value bindings are processed; other module elements (data-decls, classes, instances, etc.) are not transformed in any way;
    • Extends to e.g. bindings inside class instance definitions - they won't be touched (yet).
  • There are some known issues regarding handling of in-source comments.
    • There are cases where comments are not copied to the output (this will be detected and the user will get an error);
    • There are other cases where comments are moved slightly; there are also cases where comments result in wonky newline insertion (although this should be a purely aesthetic issue.)

Ormolu

  • "One True Style"
    • Like "Black" for Haskell
  • IMO writability++, readable--

HLint

HLint

  • A source linter for Haskell that provides a variety of hints on code improvements.
    • Can be customised and configured with custom rules, on a per-project basis.

Custom Rules

  • Custom errors can be added in order to match and suggest custom changes of code from the left hand side match to the right hand side replacement

     

- error: {lhs: fmap, rhs: map}
- error: {lhs: mconcat, rhs: concat}
- error: {lhs: mapM, rhs: traverse}
- error: 
 { lhs: getCurrentTime
 , rhs: getCurrentTimeMicroseconds}

Disabling Rules

# Everywhere
- ignore: {name: Use let}

# In specific module
- ignore: {name: Use let, within: MyModule}

Using HLint

$ hlint some-haskell-package
some-haskell-package/src/Main.hs:13:3-44: Warning: Functor law
Found:
  id <$> pure . someFunction
Perhaps:
  pure . someFunction
some-haskell-package/src/Main.hs:34:42: Suggestion: Redundant $
Found:
  $
Perhaps you should remove it.

Command line tool or editor configuration

apply-refact

hlint src/Main.hs --refactor \
  --refactor-options="--inplace"

HLint 3.0

  • Recently hit 3.0 (now 3.1.1)
  • Moved from using haskell-src-exts to ghc-parser

Code Documentation

Haddock

Beyond

Automation