Good Haskell coding standards

Solution 1:

Really hard question. I hope your answers turn up something good. Meanwhile, here is a catalog of mistakes or other annoying things that I have found in beginners' code. There is some overlap with the Cal Tech style page that Kornel Kisielewicz points to. Some of my advice is every bit as vague and useless as the HaskellWiki "gems", but I hope at least it is better advice :-)

  • Format your code so it fits in 80 columns. (Advanced users may prefer 87 or 88; beyond that is pushing it.)

  • Don't forget that let bindings and where clauses create a mutually recursive nest of definitions, not a sequence of definitions.

  • Take advantage of where clauses, especially their ability to see function parameters that are already in scope (nice vague advice). If you are really grokking Haskell, your code should have a lot more where-bindings than let-bindings. Too many let-bindings is a sign of an unreconstructed ML programmer or Lisp programmer.

  • Avoid redundant parentheses. Some places where redundant parentheses are particularly offensive are

    • Around the condition in an if expression (brands you as an unreconstructed C programmer)

    • Around a function application which is itself the argument of an infix operator (Function application binds tighter than any infix operator. This fact should be burned into every Haskeller's brain, in much the same way that us dinosaurs had APL's right-to-left scan rule burned in.)

  • Put spaces around infix operators. Put a space following each comma in a tuple literal.

  • Prefer a space between a function and its argument, even if the argument is parenthesized.

  • Use the $ operator judiciously to cut down on parentheses. Be aware of the close relationship between $ and infix .:

    f $ g $ h x == (f . g . h) x == f . g . h $ x
    
  • Don't overlook the built-in Maybe and Either types.

  • Never write if <expression> then True else False; the correct phrase is simply <expression>.

  • Don't use head or tail when you could use pattern matching.

  • Don't overlook function composition with the infix dot operator.

  • Use line breaks carefully. Line breaks can increase readability, but there is a tradeoff: Your editor may display only 40–50 lines at once. If you need to read and understand a large function all at once, you mustn't overuse line breaks.

  • Almost always prefer the -- comments which run to end of line over the {- ... -} comments. The braced comments may be appropriate for large headers—that's it.

  • Give each top-level function an explicit type signature.

  • When possible, align -- lines, = signs, and even parentheses and commas that occur in adjacent lines.

  • Influenced as I am by GHC central, I have a very mild preference to use camelCase for exported identifiers and short_name with underscores for local where-bound or let-bound variables.

Solution 2:

Some good rules of thumbs imho:

  • Consult with HLint to make sure you don't have redundant braces and that your code isn't pointlessly point-full.
  • Avoid recreating existing library functions. Hoogle can help you find them.
    • Often times existing library functions are more general than what one was going to make. For example if you want Maybe (Maybe a) -> Maybe a, then join does that among other things.
  • Argument naming and documentation is important sometimes.
    • For a function like replicate :: Int -> a -> [a], it's pretty obvious what each of the arguments does, from their types alone.
    • For a function that takes several arguments of the same type, like isPrefixOf :: (Eq a) => [a] -> [a] -> Bool, naming/documentation of arguments is more important.
  • If one function exists only to serve another function, and isn't otherwise useful, and/or it's hard to think of a good name for it, then it probably should exist in it's caller's where clause instead of in the module's scope.
  • DRY
    • Use Template-Haskell when appropriate.
    • Bundles of functions like zip3, zipWith3, zip4, zipWith4, etc are very meh. Use Applicative style with ZipLists instead. You probably never really need functions like those.
    • Derive instances automatically. The derive package can help you derive instances for type-classes such as Functor (there is only one correct way to make a type an instance of Functor).
  • Code that is more general has several benefits:
    • It's more useful and reusable.
    • It is less prone to bugs because there are more constraints.
      • For example if you want to program concat :: [[a]] -> [a], and notice how it can be more general as join :: Monad m => m (m a) -> m a. There is less room for error when programming join because when programming concat you can reverse the lists by mistake and in join there are very few things you can do.
  • When using the same stack of monad transformers in many places in your code, make a type synonym for it. This will make the types shorter, more concise, and easier to modify in bulk.
  • Beware of "lazy IO". For example readFile doesn't really read the file's contents at the moment the file is read.
  • Avoid indenting so much that I can't find the code.
  • If your type is logically an instance of a type-class, make it an instance.
    • The instance can replace other interface functions you may have considered with familiar ones.
    • Note: If there is more than one logical instance, create newtype-wrappers for the instances.
    • Make the different instances consistent. It would have been very confusing/bad if the list Applicative behaved like ZipList.

Solution 3:

  • I like to try to organize functions as point-free style compositions as much as possible by doing things like:

    func = boo . boppity . bippity . snd
        where boo = ...
              boppity = ...
              bippity = ...
    
  • I like using ($) only to avoid nested parens or long parenthesized expressions

  • ... I thought I had a few more in me, oh well