Shinobu’s Haskell Coding Style (SHCS)

I’ve been coding in Haskell for a couple years. Now, Haskell does not really have an established coding style, like C (e.g., the excellent Linux Kernel Coding Style (LKCS)). This is partly due to the extremely flexible nature of the indentation syntax alone — Haskell enjoys the unique honor of having significant whitespace, but at the same time allowing the use of optional curly braces and semicolons for “imperative-styled” do-notation (do-notation is quite common in Haskell). I present to you my own coding style, aptly named “Shinobu’s Haskell Coding Style” — it stresses, above all, consistency and predictable indentation. Since I love the LKCS so much, I decided to use tabs — how you set the actual length of the tab is up to you and your editor-of-choice, but I personally use 4-space-wide tabs.

A picture is worth a thousand words, so here is a “picture” of SHCS in action, with ample pseudocode for your reading pleasure:

{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE OverloadedStrings #-}
module Foo.Bar where

import Some.Standard.System.Library
import Some.Standard.System.Library2 (func)

import Foo
import Quux

-- | A haddock-compatible comment.
funcWithLongTypeSignature
	:: Eq a
	=> Int
	-> Int
	-> Int
	-> Int
	-> IO Int
funcWithLongTypeSignature a b c d = do
	let a' = foo a
	e <- blah a' b
	let
		c' = foo c
		d' = foo d
	f <- bleh c' d'
	-- A comment about (someTest f). Blah blah blah blah blah blah blah blah
	-- blah blah blah.
	when (someTest f)
		. func1
		. func2
		$ func3 arg
	when (someTest2 f)
		. func1
		. func2
		. func3
		. func4
		$ arg
	return $ e + f
	where
	e a b = do
		...
		return x
	f c d = case convert c of
		Z n -> return n
		_ -> return y
		where
		-- Break up 80+ character lines into parts.
		convert c = hoolaaloolaa'
			. hoolaaloolaa''
			. hoolaaloolaa'''
			$ hoolaaloolaa'''' c
		-- This version is also acceptable, if it increases legibility:
		--
		-- convert c = hoolaaloolaa'
		--	. hoolaaloolaa''
		--	. hoolaaloolaa'''
		--	. hoolaaloolaa''''
		--  $ c

someLongList :: [(Int, String)]
someLongList =
	[ (1, "a")
	, (2, "b")
	, (3, "c")
	, (4, "d")
	, (5, "e")
	, (6, "f")
	]

listComprehension :: [Int]
listComprehension =
	[ (a, b)
	| a <- [1..]
	, b <- [2..]
	, a + b < 100
	]

baz :: Int -> Int -> Char
baz a b c
	| a == 0 = 'o'
	| b == 100 = if True
		then if True
			then 'p'
			else 's'
		else 'q'
	| c == 22 = 'z'
	| otherwise = f 'k'
	where
	f c = blah $ bleh c


longStringConcatenation :: String
longStringConcatenation = "The quick "
	++ "fox "
	++ "jumps "
	++ "over "
	++ "the "
	++ "lazy "
	++ "evaluation."

data Geometry = Geometry
	{ geoGeo :: Int
	, geoGeoGeo :: Double
	} deriving (Eq)

instance Show Geometry where
	show Geometry{..} = show geoGeo
		++ show geoGeoGeo
		++ "!"

recordConstruction :: Geometry
recordConstruction = Geometry
	{ geoGeo = 1
	, geoGeoGeo = 2.0
	}

data SomeEnum
	= SEA
	| SEB
	| SEC
	| SED
	| SEE
	deriving (Eq, Enum, Ord, Show)

quaff :: SomeEnum -> Int
quaff s = case s of
	SEA -> 1
	SEB -> 2
	SEC -> 3
	SED -> 4
	SEE -> 5

WordPress renders the indentation tabs as spaces — sorry about that. Anyway, my greatest “triumph” was when I discovered that you could write the “where” clause at the same level of indentation as all the functions below it (e.g., functionWithLongTypeSignature). This reduced a lot of my code’s indentation, because I prefer to use where clauses wherever possible.

Here are the rules:

  • Place LANGUAGE extensions at the beginning of the file, with one extension per line.
  • Import standard, “system” modules first on its own block. Add all of your own modules on their own block below it. List all modules in alphabetical order.
  • 80 character limit. Try very hard to keep lines within 80 characters. This rule is especially pertinent to type signatures. Speaking of which…
  • Write explicit type signatures for all top-level functions. You may also want to write type signatures for functions underneath “where” clauses (but I think you have to use the ScopedTypeVariables extension to do this).
  • Use the “leading comma” rule when writing long lists or data structure definitions (e.g., the “Geometry” example above).
  • Indent long string concatenations (++).
  • Use GHC with at least the -Wall and -Werror flags.
  • When writing a case expression for a type that is like a C enum (e.g., SomeEnum above), explicitly write all the possible enumeration symbols for that type, instead of writing (n – 1) of them and using the “_” catch-all syntax at the end.
  • Use spaces between arithmetic components (e.g., “(n + 1)” instead of “(n+1)”.
  • Avoid use of curly braces/semicolons for do-notation blocks.
  • Avoid use of let statements, and instead use where clauses. The use of a where clause forces you to write in a top-down manner, with the “overall picture” first, followed by smaller component functions. I’d rather be faced with a one-liner function followed by 100 helper functions under a where clause, rather than first seeing the 100 helper functions in a gigantic let expression, followed by the one-liner.
  • Try to avoid nesting where clauses as much as possible.
  • When confronted with a long line, break it up into smaller parts (by function, if you’re composing many of them, or by argument, if it’s just one function with many arguments) and indent the “tail” of these parts, each on their own line (see “convert c” in the example).
  • Prefer to use the dollar symbol operator ($) instead of using parentheses. E.g., (foo $ bar x) instead of (foo (bar x)).
  • Instead of (foo (bar (baz (quux x)))), write (foo . bar . baz $ quux x). I believe this is an “unstated” style rule among Haskellers, as it is. And so I formally state it here.
  • Catch-all rule: if the code is short enough, it is optional whether you write it in one line or multiple (indented) lines (e.g., list comprehensions, lists, etc.)

These are just a handful of rules, but they go a long way to make things look consistent. And, because the indentation is predictable, it makes the code easier to read.

Advertisements