|Version 19 (modified by benl, 4 years ago)|
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
- Tabs are 8 spaces. The reason being that when you cat a text file to a unix console they come out as 8 spaces. No further discussion will be entered into on this point.
- We prefer literal tabs at the start of lines instead of hard spaces.
- 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. For n lines of code there are potentially n^2 bugs (at least), so we want to know exactly what each line is trying to achieve.
- 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 chars wide. If you want sub dividers then use 80 or 40 columns. If you have more than a few dividers in a module then consider breaking it up into smaller modules.
- 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')
- 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.
- 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
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 =
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.
Avoid nested functions
Only define nested functions when the nested one is a small worker function, such as something you want to map across a list.
Functions that are continuations of other functions should be written like this:
mainFun | checkBadness = error "fails" | otherwise = do x <- someSetupThing y <- someSetupThing2 mainFun_good x y mainFun_good x y = do restOfThing1 restOfThing2
Note that the continuation is named after the initial one, with a "_good" suffix that identifies the preconditions that the initial one sets up.