Haskell Programming Info
Haskell Programming Info
Programming guidelines shall help to make the code of a project better readable and maintainable
by the varying number of contributors.
Contents
[hide]
1 Docume
ntation
2 File
Format
3 Naming
Conventio
ns
4 Good
Programm
ing
Practice
4.1 Partial
functions
4.2 Let or
where
expression
s
4.3 Code
reuse
4.4 Applic
ation
notation
4.5 List
Comprehe
nsions
4.6 Types
4.7 Record
s
4.8 IO
4.9 Trace
4.10 Impo
rts
4.11 Glasg
ow
extensions
and
Classes
5 Style in
other
languages
6 Final
remarks
1 Documentation
Comments are to be written in application terms (i.e. user's point of
view). Don't use technical terms - that's what the code is for!
Comments should be written using correct spelling and grammar in
complete sentences with punctation (in English only).
"Generally, you want your comments to tell WHAT your code does,
not HOW. Also, try to avoid putting comments inside a function
body: if the function is so complex that you need to separately
comment parts of it, you should probably" (... decompose it)
Put a haddock comment on top of every exported function and data
type! Make sure haddock accepts these comments.
2 File Format
All Haskell source
{- |
Module
:
automatically>
Description :
contents page>
Copyright
:
License
:
Maintainer : <email>
Stability
: unstable | experimental | provisional |
stable | frozen
Portability : portable | non-portable (<reason>)
<module description starting at first column>
-}
A possible compiler pragma (like {-# LANGUAGE CPP #-}) may
precede this header. The following hierarchical module name must
of course match the file name.
Make sure that the description is changed to meet the module (if
the header was copied from elsewhere). Insert your email address
as maintainer.
Try to write portable (Haskell98) code. If you use e.g. multiparameter type classes and functional dependencies the code
becomes "non-portable (MPTC with FD)".
The \$Header\$ entry will be automatically expanded.
Lines should not be longer than 80 (preferably 75) characters to
avoid wrapped lines (for casual readers)!
Don't leave trailing white space in your code in every line.
Expand all your tabs to spaces to avoid the danger of wrongly
expanding them (or a different display of tabs versus eight spaces).
Possibly put something like the following in your ~/.emacs file.
(custom-set-variables '(indent-tabs-mode nil))
If you notice that you're doing the same task again, try to
generalize it in order to avoid duplicate code. It is frustrating to
change the same error in several places.
Use these only when "short and sweet". Prefer map, filter, and foldr!
Instead of:
[toUpper c | c <- s]
write:
map toUpper s
Consider:
Prefer proper data types over type synonyms or tuples even if you
have to do more constructing and unpacking. This will make it
easier to supply class instances later on. Don't put class constraints
on a data type, constraints belong only to the functions that
manipulate the data.
Using type synonyms consistently is difficult over a longer time,
because this is not checked by the compiler. (The types shown by
the compiler may be unpredictable: i.e. FilePath, String or [Char])
Take care if your data type has many variants (unless it is an
enumeration type.) Don't repeat common parts in every variant
since this will cause code duplication.
Bad (to handle arguments in sync):
data Mode f p = Box f p | Diamond f p
Good (to handle arguments only once):
data BoxOrDiamond = Box | Diamond
data Mode f p = Mode BoxOrDiamond f p
Consider (bad):
data Tuple a b = Tuple a b | Undefined
versus (better):
data Tuple a b = Tuple a b
and using:
Maybe (Tuple a b)
(or another monad) whenever an undefined result needs to be
propagated
4.7 Records
For (large) records avoid the use of the constructor directly and
remember that the order and number of fields may change.
Take care with (the rare case of) depend polymorphic fields:
data Fields a = VariantWithTwo
{ field1 :: a
, field2 :: a }
The type of a value v can not be changed by only setting field1:
v { field1 = f }
Better construct a new value:
VariantWithTwo { field1 = f } -- leaving field2
undefined
Or use a polymorphic element that is instantiated by updating:
empty = VariantWithTwo { field1 = [], field2 = [] }
empty { field1 = [f] }
Several variants with identical fields may avoid some code
duplication when selecting and updating, though possibly not in a
few depended polymorphic cases.
However, I doubt if the following is a really good alternative to the
above data Mode with data BoxOrDiamond.
data Mode f p =
Box
{ formula :: f, positions :: p }
| Diamond { formula :: f, positions :: p }
4.8 IO
Try to strictly separate IO, Monad and pure (without do) function
programming (possibly via separate modules).
Bad:
x <- return y
...
Good:
let x = y
...
Don't use Prelude.interact and make sure your program does not
depend on the (not always obvious) order of evaluation. E.g. don't
read and write to the same file:
This will fail:
do s <- readFile f
writeFile f $ 'a' : s
because of lazy IO! (Writing is starting before reading is finished.)
4.9 Trace
Standard library modules like Char. List, Maybe, Monad, etc should
be imported by their hierarchical module name, i.e. the base
package (so that haddock finds them):
import Data.List
import Control.Monad
import System.Environment
The libraries for Set and Map are to be imported qualified:
import qualified Data.Set as Set
import qualified Data.Map as Map
4.11 Glasgow extensions and Classes