September 4, 2019
This document contains a description of the preferred style for writing Curry programs. The described style is oriented to the Haskell Style Guide von Johan Tibell, but it has been adapted in various points.
In principle, this style guide is a guideline from which one can deviate if there are good reasons for it.
The maximum line length is 80 characters. 120 characters is an alternative as modern screens allow wider lines.
Tabulator stops are not allowed in source programs, since the actual indentation space of a tabulator stop might influence the semantics of the program. This is also true for other white spaces aside of spaces and line breaks. Instead, use blanks for indentation. Code blocks should be intended with 2 spaces. Most text editors can be configured so that tabs are automatically replaced by (at most) two spaces. Most of the time bodies of constructs can be indented either by 2 from the keyword or by 2 from the outer block (in the following example the function f1
). If the body starts in the same line as the keyword, no indentation is required since the body should be aligned anyway.
= do
f0
a
b
c
= do
f1
a
b
c
= do a
f2
b c
The keyword where
is indented by one space and the subsequent local definitions by a further space. Hence, the local definitions are intended, as other code blocks, by 2 spaces. If there is only one short local definition, it can be written directly after the keyword where
. Several local value definitions should be aligned at the equality symbol if they have a comparable number of parameters. Local definitions should be aligned like top-level definitions (see below):
main :: IO ()
= do
main <- getLine
line putStrLn $ answer line
where
= "Your input: " ++ s
answer s
= x + y
f y where x = 1
= x + val1 + secondVal
g x where
= 1
val1 = if isZero x then 1 else 2
secondVal
-- local operation
| x == 0 = True
isZero x | otherwise = False
There should be one blank line between two top-level definitions. There are no blank lines between type signature and the defining rules. Similarly, comments of top-level definitions are not separated from the actual definition with a blank line. Between blocks of infix declarations blank lines should however be omitted.
-- f increments the argument by one
f :: Int -> Int
= x + 1
f x
infixr 3 &&
infixr 2 ||
infixl 1 >>, >>=
infixr 0 $, $!, $!!, $#, $##, `seq`, &, &>, ?
-- fInv decrements the argument by one
= x - 1 fInv x
Trailing Spaces should be avoided. Binary operators, like (++)
, are surrounded with a single space on either side.
-- bad
"Not"++"good"
-- good
"Very" ++ "good"
It is allowed to omit the surrounding spaces from simple infix operators, like (+)
, or the infix list constructor (:)
. However, this is not required.
= n * (n+1) / 2
f n
head (x:_) = x
Similarly to written texts, a space follows a comma, but there is no space in front of the comma:
= [1, 2, 3]
aList = (True, "True", 1) aTuple
One can put a space after a lambda, in particular, if there is non-variable pattern after a lambda. If there is a variable after a lambda, one can omit the space:
map (\ (_:_) -> True)
map (\ x -> x + 1)
map (\x -> x + 1)
If the export list fits into one line, it can be written as follows:
module Set (Set, empty) where
Longer export lists should be aligned as follows:
module Data.Set
Set
(
, empty
, singleton
, memberwhere )
Optional, one can also put several names into line:
module Data.Set
Set, empty, singleton
(
, member, union, intersectwhere )
If there are types exported with constructors, one should separate the type name and the constructors with a blank:
module Tree (Tree (..), BinTree (Leaf, Branch)) where
The list of imported modules should be ordered in the following three categories:
List
, IO
The list of imports in each category should be sorted alphabetically. With the exception of the prelude, all used entities from imported modules should be explicitly or qualified
imported. If the list of imported entities is long, one can omit this general rule.
import List (isInfixOf)
import qualified Set as Set
import SecondParty.Module1 (fun)
import ThirdParty.Module1 (($$$))
import MyUtilsModule -- import everything
data
DeclarationsThe constructors of a data
declaration should be vertically aligned:
data Tree a =
Leaf
| Branch a (Tree a) (Tree a)
If there are only a few constructors that fits into one line, one can write the data
declaration as follows:
data Bit = Zero | One
Records (data
declarations with named selectors) should be vertically aligned as follows:
data Person = Person
firstName :: String
{ lastName :: String
, age :: Int
, }
class
DeclarationsDeclarations in a class declaration should be aligned and indented.
Example a where
class c1 :: a
f1 :: a -> String
f2 :: String -> a
f3 :: String -> String
= f1 . f2 f3
instance
DeclarationsDeclarations in an instance declaration should be aligned and indented similarly to class declarations.
Example SomeConstruct where
instance = blob
c1
= show a
f1 x = parse s f2 s
deriving
The keyword deriving
is aligned with behind the rest of the data declaration body. If the declaration is written in one line indenting by 2 in the next line is also allowed. The body of deriving should be formatted like tuple declarations.
data Tree a = Leaf
| Branch a (Tree a) (Tree a)
Show
deriving ( Eq
, Example
, Test
, Something
,
)
data Bit = Zero | One
Show) deriving (
In records deriving
should be written behind the closing brace.
data Person = Person
firstName :: String
{ lastName :: String
, age :: Int
,Show, Eq, Example) } deriving (
In type signatures one should put a blank before and after the function arrow ->
.
map :: (a -> b) -> [a] -> [b]
The type signature should be written in one line if it fits. If the type signature is long or if one want to put a comment after the individual types, one should align the function arrows:
uncurry10 :: (a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> k)
-> (a, b, c, d, e, f, g, h, i, j)
-> k
area :: Int -- width
-> Int -- height
-> Int -- area
If an operation is defined by several rules, one should vertically align the parameters with the same position if possible:
and True True = True
and _ _ = False
The equal signs =
should also be aligned.
The start (e.g. =
, |
) of right hand sides of a function should be aligned.
X = ...
g Y = ...
g | s == 1 = ...
g s | s == 2 = ...
| otherwise = ...
The equal signs =
should also be aligned.
Guards should immediately follow the patterns or they should be indented in the following line. In any case, they should be vertically aligned:
| x == y = z
f x y z | otherwise = z + 1
g x y z| x == y && not z = 1
| otherwise = 0
If the conditions are very long, the equal signs can start in the next line below the condition:
f x y z| thisIsAVeryLongConditionWhichNeedsAllTheSpaceAvailableInTheLine x y z
= 42
| otherwise
= 0
If an operation is defined by rules with and without guards, the guards |
and equal signs =
should be vertically aligned:
f :: [a] -> Int -> [a]
= []
f [] _ :xs) n | n<=0 = []
f (x| otherwise = x : f xs (n-1)
if-then-else
As a general rule, one should try to use patterns and guards instead of if-then-else
expressions. Short if-then-else
expressions can be written in one line (if this fits into the maximal line length):
= g (if x then 0 else 1) 42 f x
Otherwise, one should indent the then
and else
branch, which are vertically aligned:
= if ...
foo then ...
else ...
If the condition is short, one can also write the if
and then
part in one line, but align the then
and else
parts:
= if ... then ...
foo else ...
case
ExpressionsThe alternatives in a case
expression should be aligned in the following form:
= case something of
foobar Just j -> foo
Nothing -> bar
or
= case something of
foobar Just j -> foo
Nothing -> bar
The arrows ->
should be aligned to improve readability.
let
ExpressionsShort let
expressions with a single local declaration can be written in one line (if the line is not too long):
= let z = x + y in z * z
squareSum x y
| let f free in father g f && father f c = True grandfather g c
Long let
expressions or let
expressions with more then one local declaration should be aligned so that the keywords let
and in
are in the same column:
:xs) = let (l,r) = split x xs
qsort (xin qsort l ++ (x:qsort r)
=
doubleSquareSum x y let z = x + y
= z * z
sq in sq + sq
The equal signs of all local declarations should be vertically aligned. The same rule is used for let
expressions in do
blocks (where there is no in
expression).
do
BlocksThe statements in a do
blocks should start immediately after the keyword do
or they are indented in the next line:
= do name <- getLine
echo putStrLn name
= do
greet putStr "How is your name? "
<- getLine
name putStrLn ("Hello " ++ name ++ "!")
The element of long lists should be aligned as follows:
=
exceptions InvalidStatusCode
[ MissingContentHeader
, InternalServerError
, ]
One can also omit the first line break:
= [ North
directions East
, South
, West
, ]
Short lists can be written in one line:
= [1, 2, 3] short
The same rules apply to tuple definitions:
= (1, True)
t = ( InvalidStatusCode
ignored MissingContentHeader
, )
Superfluous brackets should be avoided:
= 1 + 2 * 3
seven
= if cond x then 0 else 1 f x
instead of
= (1 + (2 * 3))
seven
= if (cond x) then 0 else 1 f x
In case of specific infix operators (e.g., not defined in the prelude) or if one is not sure about the precedence or want to document the predence, one can write brackets.
Superfluous structures are to be avoided such as unnessecary braces and expressions or combining methods get functions that are already predefined. The following guidelines are only a handful of examples.
-- incorrect
= if (x == True) then ...
f x -- correct
= if x then ...
f x
-- incorrect
= if x then True else False
f x -- correct
= x f x
-- incorrect
... not (a = b) ...
... not (a /= b) ...
-- correct
... a /= b ...
... a == b ...
-- incorrect
... not (a <= b) ...
... not (a > b) ...
-- correct
... a > b ...
... a <= b ...
-- incorrect
... l == [] ...
... (/=) [] l ...
-- correct
... null l ...
... not (null l) ...
-- incorrect
... foldl || False list ...
... foldr && True list ...
-- correct
... Or list ...
... And list ...
-- incorrect
... \x -> x ...
-- correct
... id ...
-- incorrect
... \x y -> x ...
-- correct
... const ...
-- incorrect
... putStrLn (show someString) ...
-- correct
... print someString ...
Comments should be written in correct English. The identifiers used in a program should be also meaningful in English.
All top-level operations should have a comment and a type signature. This is a must for exported operations. One should use the syntax of CurryDoc for the comments of exported operations so that the program documentation can easily be generated.
--- Splits the list argument into a list of lists of related adjacent
--- elements.
--- @param eq - the relation to classify adjacent elements
--- @param xs - the list of elements
--- @return the list of lists of related adjacent elements
groupBy :: (a -> a -> Bool) -> [a] -> [[a]]
The comment of an operation should contain enough information so that one can use the operation without studying its implementation.
Data types should be commented in a similar way. Exported constructors should have individual comments:
--- The data type for representing XML expressions.
--- @cons XText - a text string (PCDATA)
--- @cons XElem - an XML element with tag field, attributes, and a list
--- of XML elements as contents
data XmlExp = XText String
| XElem String [(String,String)] [XmlExp]
For records with explicit selectors (labels), one should add a comment for each selector:
--- A natural person
--- @field firstName - First name, may contain several ones
--- @field lastName - Last name
--- @field height - Height in centimeters
data Person =
Person
firstName :: String
{ lastName :: String
, height :: Int
, }
Comments at the end of a line should be separated from the code by at least one space:
foo :: Int -> Int
= salt * 32 + 9
foo n where salt = 453645243 -- Magic hash salt.
Use camel case for names that consists of several logical units (words):
data BankAccount = ...
= 42 thisIsTheAnswer
To improve readability, one should not capitalize all letters in abbreviations. For instance, one should write showXmlDoc
instead of showXMLDoc
. An exception are abbreviations with only two letters, like IO
.
Parameter names and names of local declarations are usually short, but one has to take the following rule into account:
Entity with larger visibility regions have longer names.
The name x
can be used in a one-line operation but never as a top-level declaration.
One should also take into account the following conventions for short names:
f
denotes an operation of type a -> b
n
, m
, etc. denote natural numbers of type Int
x
, y
, z
often denote values of polymorphic types
A name with suffix s
denote several values like lists. Here are some examples: xs :: [a]
, fs :: [a -> b]
, xss :: [[a]]
p
denotes a predicate of type a -> Bool
A name with an apostroph, like x'
, denote modified values:
let acc' = updateAcc acc in ...
k
and v
are often used for keys and values in a mapping.
Code should be compilable with the parsing option
:set parser -Wall
without producing any warnings. If one uses a more logic-oriented programming style, operations are often defined with overlapping rules or incomplete pattern matching. In this case, one can explicitly omit the warnings by the parsing option
{-# OPTIONS_CYMAKE -Wno-incomplete-patterns -Wno-overlapping #-}
in the beginning module header. However, this should be done only in a program that depends on these features of Curry.