Coding style is an art rather than a science, and individuals often have their own preferred style. Nevertheless, it is important to agree upon a common style when sharing code with others.

The following style guidelines (borrowed from UPenn) will help. Some of them can be checked automatically by the hlint tool, so it is worth running it on your code.

Required Elements

  • RD

    Every top-level function must have an explicit type declaration.

    For example, write

    myfunc :: Integer -> Integer -> [Integer] -> [Integer]
    myfunc x y z = (x+y) : z
    

    instead of just

    myfunc x y z = (x+y) : z
    

    These declarations will sometimes be fairly obvious (from the function definition itself, its name, nearby comments, or whatever), but getting into the habit of writing them all the time is good discipline. And, good type signatures lead to better error messages.

  • RT

    Every significant piece of functionality should have testing code demonstrating that it works.

    In particular, every solution to a homework exercise must include a “test driver” that shows how it behaves on several examples. These test cases will often be given to you. If that is the case, you do not need to add more.

  • RC

    Submitted code must compile (cleanly).

    Any code you submit must be accepted by GHC without errors or warnings. Never submit anything that you have changed, no matter how small the change, without checking it with GHC. Make sure your code begins with the line {-# OPTIONS -Wall -fwarn-tabs #-} or something similar, according to the instructions in the assignment. If it doesn’t, we may add it.

    You may find some errors due to our supplied cabal file. If this is the case try cabal v2-clean and then rebuild your assignment with cabal v2-build.

  • RH

    Include a header comment.

    It must contain at least your name(s), the date, and the number of the assignment.

Formatting

  • FI

    Use consistent indentation.

    There are several reasonable styles for indenting code in Haskell. Choose one and stick with it.

  • FT

    No tab characters.

    Do not use the tab character (0x09). Instead, use spaces to control indenting. This is because the width of a tab is not uniform across all computers, and what looks good on your machine may look terrible on ours, especially if you have mixed spaces and tabs. The homework assignments include a flag that warns against inadvertant tab insertion. Don’t remove this flag.

  • F8

    80-column lines.

    No line in your program should be longer than 80 characters. Although most screens are wide enough now to display much longer lines, there are still many editors whose default width is 80 characters, and 80-character lines are the natural width for printing programs in reasonable-sized fonts.

Naming

  • ND

    Use descriptive names.

    Choose names that reflect the intended use of the value referred to by the name. Names are documentation.

    As a general rule, short names (one or a few characters) are appropriate for variables with small scopes, like local definitions or parameters to functions, whereas longer names are appropriate for global definitions, such as top-level functions.

  • NC

    Follow standard Haskell naming conventions.

    For long names composed of multiple words, use “camelCase.” Smash the words into a long string and capitalise the first letters of the second and following words — e.g., veryLongFunctionName instead of very_long_function_name or verylongfunctionname.

    When pattern matching against a list, if you call the head x, then use xs for the tail.

    For better readability, don’t capitalize all letters when using an abbreviation. For example, write HttpServer instead of HTTPServer. Exception: Two-letter abbreviations, such as IO.

Comments

  • C

    Use comments.

    Most top-level declarations (data types and functions) should be preceded by a short comment, written with Haddock syntax.

    -- | Sums a list of integers
    sum :: [ Integer ] -> Integer
    sum = foldl (+)
    
  • CW

    Comments should say what the code does, not how it does it.

    It should be obvious how the code works, just by looking at it. If this is not the case, you need to rewrite the code.

  • CO

    Do not over-comment.

    Very many or very long comments (especially within the body of a function) are more distracting than helpful. Long comments may appear at the top of a file or section of code, if you need to explain the overall design of the code or refer to sources that have more information about the algorithms or data structures. All other comments in the file should be as short as possible. Judicious choice of variable names can help minimize the need for comments.

    Avoid comments that state the obvious:

    -- | This function increments its argument
    inc :: Integer -> Integer
    inc x = x+1
    
  • CE

    Use proper English.

    Comments need not always be written in complete sentences, but when they are, standard rules of English grammar apply. Spelling also counts.

Functionality

  • FS

    Write simple functions.

    “Functions should be short and sweet, and should do just one thing. They should fit on one or two screenfuls of text (the ISO/ANSI screen size is 80x24, as we all know) and do one thing and do that well.” — from the Linux kernel coding style.

  • FD

    No dead code.

    Do not leave unused or commented out code in your submission. It detracts from the story line of your program. If you are worried about saving this code for later, copy it to a different file or use version control.

  • PF

    Avoid partial functions.

    Your code should always return an answer, not error. Some prelude and standard library functions are partial. Avoid these in your code!

    In particular, never use: head, tail, or fromJust. There is always a better way.

Pattern Matching

  • PI

    No incomplete Cases.

    Incomplete pattern matches are flagged with compiler warnings, which may be treated as errors for grading purposes. Thus, if your program exhibits this behavior, the problem will get no points.

  • PF

    Match in the function arguments.

    Tuples, records and datatypes can be deconstructed using pattern matching. If you simply deconstruct the function argument before you do anything useful, it is better to pattern match in the function argument. Consider these examples:

    -- BAD
    f arg1 arg2 = ... where
      x = fst arg1
      y = snd arg1
      z = fst arg2
    --  GOOD
    f (x,y) (z,_) = ...
    
  • PP

    Avoid Unnecessary Projections.

    Prefer pattern matching to projections with function arguments or value declarations. Using projections is okay as long as it is infrequent and the meaning is clearly understood from the context. The above rule shows how to pattern-match in the function arguments.

  • PN

    Combine nested cases.

    Rather than nest case expressions, you can combine them by pattern matching against a tuple, provided the tests in the case expressions are independent. Here is an example:

    -- BAD
    case x of
    Red -> case y of
             Red  -> True
             Blue -> False
    Blue -> case y of
             Red  -> False
             Blue -> True
    -- GOOD
    case (x,y) of
    (Red,   Red) -> True
    (Blue, Blue) -> True
    (   _,    _) -> False
    

Verbosity

  • VL

    Don’t rewrite library functions.

    The Haskell library has a great number of functions and data structures — use them! Often students will recode filter, map, and similar functions. Hoogle can help you find them. Exception: Sometimes we may ask you to rewrite certain library functions for practice.

  • VI

    Misusing if.

    Remember that the type of the condition in an if expression is Bool. If the result type of the if expression is also Bool, then you probably should not be using if at all. Consider replacing the expression on the left with the one on the right:

    BAD                                GOOD
    if e then True else False          e
    if e then False else True          not e
    if e then e else False             e
    if not e then x else y             if e then y else x
    if x then True else y              x || y
    if x then y else False             x && y
    if x then False else y             not x && y
    if x then y else True              not x || y
    
bars search times arrow-up