wiki:Development/CodeConventions

Version 32 (modified by benl, 7 years ago) (diff)

--

Coding Conventions

The golden rule is to follow whatever conventions are already being used in the module you're editing. If you're creating a new module then follow the conventions used in existing modules. If you're not sure what the conventions are, then ask.

We don't want multiple coding styles in DDC. Having different styles creates "accidental complexity", that is, syntactic differences in the source that don't equate to real functional differences.


General Rules for all Languages

Spacing

  • Tabs are 8 spaces. The reason being that when you cat a text file to a unix console they come out as 8 spaces. Keep your editor set to tabs-as-8-spaces or you'll end up committing misaligned code.
  • Corollary to the above: don't use tabs. Keep your editor set to 'soft-tabs' mode so that when you press the tab key it inserts 8 spaces.

Comments

  • Each top-level definition should have a comment explaining what it is for. One liners are fine.
  • Running comments in the bodies of functions are encouraged. Write down what you were expecting the code to do when you wrote it, so it reads like a story. Aim for 1 comment line every 5-10 code lines, depending on how complex the code is. This level of commenting would be overkill for a cookie-cutter program like a database GUI, but compilers are a completely different beast. See the CheckExp module in the type checker for a good example.
  • All top-level bindings should have a type signature. Exceptions can be made for functions that are continuations of others, as they will never need to be called from outside the module they are defined in.
  • Feel free to add dividers like:
    -- Optimiser Monad -----------------------------------------------------------------
    

Such dividers increase the structure of the code and help the reader find things, especially with syntax highlighting editors. Most dividers should be 100, 80 or 40 chars wide. Most code should fit in 80 columns, but it's ok to use 100 if needed. If you have more than a few dividers in a module then consider breaking it up into smaller modules.

Layout

  • If a function does several things in a regular way, then it should look like that in the source code. This means you should line up arguments to similar function calls. For example, use this:
     = do  someFunction      "Monday"    (23, 23)         (Just 'a')
           someOtherFunction "Tuesday"   ("this", "that") Nothing
           anotherFunction   "Wednesday" ('a', 'b') 
    

instead of this:

 = do  someFunction "Monday" (23, 23) (Just 'a')
       someOtherFunction "Tuesday" ("this", "that") Nothing
       anotherFunction "Wednesday" ('a', 'b') 
  • For the same reason, line up the '=' signs in groups of bindings. For example, use this:
      let  thing          = some stuff
           someOtherThing = some more stuff
           oneLastThing   = still more stuff
      in   ...
    

instead of this:

  let  thing = some stuff
       someOtherThing = some more stuff
       oneLastThing = still more stuff
  in   ...

Naming

  • Most names should use camel case style, but it's ok to use underscores in a postfix when naming different "versions" of a value. For example:
     let  pastaSalad        = initialSalad
          pastaSalad_spiced = addSpice pastaSalad "Oregano"
          pastaSalad_baked  = bakeThing 100 pastaSalad_spiced
          ...
     in   ... pastaSalad_baked ...
    
    Avoid using names like pastaSalad' and pastaSalad'', because the prime doesn't provide any additional documentation.
  • Name conversion functions like globOfTops :: [Top] -> Glob instead of topsToGlob :: [Top] -> Glob. The type may be the opposite way around compared to the name, but it makes the source code easier to read. Consider (globOfTops someTops) vs (topsToGlob someTops).
  • If part of a variable name reflects its type, then put that part out the front. For example, source code variables of type Shared.Var should be named something like vThing, with a v out the front. Sets or lists of variables should be named vsThing with vs out the front. Avoid using names like thingVar and thingVars. We're not mandating Hungarian Notation, but if you're going to name a variable after a type anyway, then put the type part out the front.

Structure

  • Keep the sizes of the modules small. If a module has more than about 400 lines then consider breaking it up. Modules with just 100 lines are fine. Using small modules makes them easier to digest, forces you to think about the overall structure of the code, and helps parallel builds.


Haskell / Disciple Specifics

Module signatures

Like the this, with the commas on the left:

module Thing 
        ( someFunction
        , someOtherFunction
        , moreStuff )
where
...


Try to put the do on the same line as the =

Use this:

fun x 
 = do   y <- thing
        return (x + y)

Instead of this:

fun x = do
        y <- thing
        return (x + y)

The second version creates extra visual noise. As function names have different lengths, the column position of the top-level do will tend to be different between functions.

Put the $ on the left

Like this:

foo x
 = bar cheeze
 $ whatever baz
 $ thingo

Instead of this:

foo x
 = bar cheeze $ 
   whatever baz $ 
   thingo

This is also to reduce visual noise. Notice how in the first version all the symbols line up.


Prefer let over where

Prefer this:

niceFunction x 
 = let dude = thingo x
       epic = goodness dude
   in  baz epic dude

Instead of this:

niceFunction x = baz epic dude
 where dude = thingo x
       epic = goodness dude

Using a "where" is ok when baz does something trivial, like initialise a loop. However, any time "baz" does something interesting you should use a let, otherwise the conceptual flow of the code is ruined. Using let over where also makes it easier to convert to strict Disciple code.