Concurrent Factory Production Line Manager

The Haskell code below uses Software Transactional Memory to concurrently manage a factory with a set of production line, each with a set of raw materials.

The raw materials can be moved from one production line to the other. One production line is supplied with power at a time, the initial processing of raw materials in one production assembly can be done in parallel.

 

 

 

 

 

 

Haskell Style Guide

1. File Format

All Haskell source files start with a haddock header of the form:

A possible compiler pragma (like

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. multi-parameter type classes and functional dependencies the code becomes “non-portable (MPTC with FD)”.

The $Header$ entry will be automatically expanded.

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))

The last character in your file should be a newline. Under solaris you’ll get a warning if this is not the case and sometimes last lines without newlines are ignored.

You may use \url{http://hackage.haskell.org/package/scan} to check your file format.

The whole module should not be too long (about 400 lines).

2. Required Elements

  • Explicit Types Every top-level function must have an explicit type. For example, write

    instead of just:

    And, good type signatures lead to better error messages.

  • Testing Code: Every significant piece of functionality must have testing code demonstrating that it works.
  • Warning Options: Make sure your code begins with the line:

    Respect compiler warnings.

  • Header Comment: It must contain least your name, the date, and the purpose of the file.

    3. Formatting

  • Use consistent indentation: There are several reasonable styles for indenting code in Haskell. Choose one and stick with it.
  • 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.

    Indent your code blocks with 4 spaces. Indent the where keyword two spaces to set it apart from the rest of the code and indent the definitions in a where clause 2 spaces. Some examples:

  • 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.
  • Blank Lines: One blank line between top-level definitions.

    No blank lines between type signatures and function definitions.

    Add one blank line between functions in a type class instance declaration if the functions bodies are large. Use your judgement. Whitespace

    Surround binary operators with a single space on either side. Use your better judgement for the insertion of spaces around arithmetic operators but always be consistent about whitespace on either side of a binary operator. Don’t insert a space after a lambda. Data Declarations

    Align the constructors in a data type definition. Example:

    For long type names the following formatting is also acceptable:

    Format records as follows:

  • Alignment: When possible, align — lines, = signs, and even parentheses and commas that occur in adjacent lines.
  • Pragmas: Put pragmas immediately following the function they apply to.
  • Hanging Lambdas: You may or may not indent the code following a “hanging” lambda. Use your judgement. Some examples:
  • Export Lists: Format export lists as follows:
  • If Statement: Be consistent with the 4-spaces indent rule, and the then and the else keyword should be aligned. Examples:
  • Case expressions: The alternatives in a case expression can be indented using either of the two following styles:

    Align the -> arrows when it helps readability.

  • Imports: Imports should be grouped in the following order:

    standard library imports

    related third party imports

    local application/library specific imports

    Put a blank line between each group of imports. The imports in each group should be sorted alphabetically, by module name.

    Always use explicit import lists or qualified imports for standard and third party libraries. This makes the code more robust against changes in these libraries. Exception: The Prelude.

    4. Naming

  • 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.
  • Follow standard Haskell naming conventions: For long names composed of multiple words, use “camelCase.” Similarly, type, typeclass and constructor names are written using the StudlyCaps convention.
  • Modules: Use singular when naming modules e.g. use Data.Map and Data.ByteString.Internal instead of Data.Maps and Data.ByteString.Internals.
  • Pattern Matching on Lists: When pattern matching against a list, if you call the head x, then use xs for the tail.
  • Capitalization of Abbreviations: For readability reasons, don’t capitalize all letters when using an abbreviation. For example, write HttpServer instead of HTTPServer. Exception: Two letter abbreviations, such as IO.

    In Haskell types start with capital and functions with lowercase letters, so only avoid infix identifiers. Defining symbolic infix identifiers should be left to library writers only.

    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.

    5. Comments

  • Comments: Top-level declarations (data types and functions) should be preceded by a short comment, written with Haddock syntax. Put a haddock comment on top of every exported function and data type. Make sure haddock accepts these comments.
  • End-of-Line Comments: 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.

    Separate end-of-line comments from the code using 2 spaces. Align comments for data type definitions.

  • Comment what the code does, not how: 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.

    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. Do not over-comment. Comments should be written using correct spelling and grammar in complete sentences with punctation (in English only).

    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 any 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.

    “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.

    Avoid comments that state the obvious.

    6. Functionality

  • Write simple functions: “Functions should be short and sweet, and do just one thing. They should fit on one or two screenfuls of text (the ISO/ANSI screen size is 80×24, as we all know) and do one thing and do that well.” From the Linux Kernal coding style.

    Most haskell functions should be at most a few lines, only case expression over large data types (that should be avoided, too) may need corresponding space.

    The code should be succinct (though not obfuscated), readable and easy to maintain (after unforeseeable changes). Don’t exploit exotic language features without good reason.

    It’s not fixed how deep you indent (4 or 8 chars). You can break the line after “do”, “let”, “where”, and “case .. of”. Make sure that renamings don’t destroy your layout. (If you get to far to the right, the code is unreadable anyway and needs to be decomposed.)

    Good:

    Avoid the notation with braces and semicolons since the layout rule forces you to properly align your alternatives.

  • Avoid Parenthesis around function Application: Avoid redundant parenthesis around a function application which is itself the argument of an infix operator (Function application binds tighter than any infix operator.
  • Spaces: 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.

  • Partial Functions: For partial functions document their preconditions (if not obvious) and make sure that partial functions are only called when preconditions are obviously fulfilled (i.e. by a case statement or a previous test).

    Usually a case statement (and the import of isJust and fromJust from Data.Maybe) can be avoided by using the “maybe” function:

    maybe (error “.“) id $ Map.lookup key map

    Be more explicit about failure cases. Surely a missing (or an irrefutable) pattern would precisely report the position of a runtime error, but these are not so obvious when reading the code. Missing unreachable cases can be filled in using “error” with a fixed string “.” to indicate the error position (in case the impossible should happen). Don’t invest time to “show” the offending value, only do this temporarily when debugging the code.

  • No dead code: Do not leave unused or commented out code in your file. 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.

    7. Let or where expressions

    Avoid mixing and nesting “let” and “where”. 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.

    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.

    Use auxiliary top-level functions that you do not export. Export lists also support the detection of unused functions.

    8. Pattern Matching

  • Complete Cases in Pattern Matching: Incomplete pattern matches are flagged with compiler warnings.
  • 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: Don’t do this.

    Preferred version:

  • Avoid Unnecessary Projections: Prefer pattern matching to projections with function arguments or a 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.
  • 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:

    vs.

    9. Verbosity

  • 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 and Hayoo can help you find them.
  • 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.

    10. Types

    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.

    11. Application Notation

    Many parentheses can be eliminated using the infix application operator “$” with lowest priority. Try at least to avoid unnecessary parentheses in standard infix expression.

    12. List Comprehensions

    Use these only when “short and sweet”. Prefer map, filter, and foldr.

    13. IO

    Try to strictly separate IO, Monad and pure (without do) function programming (possibly via separate modules).

    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:

    14. Dealing with laziness

    By default, use strict data types and lazy functions.

    14.1. Data types

    Constructor fields should be strict, unless there’s an explicit reason to make them lazy. This avoids many common pitfalls caused by too much laziness and reduces the number of brain cycles the programmer has to spend thinking about evaluation order.

    Additionally, unpacking simple fields often improves performance and reduces memory usage:

    As an alternative to the UNPACK pragma, you can put

    at the top of the file. Including this flag in the file inself instead of e.g. in the Cabal file is preferable as the optimization will be applied even if someone compiles the file using other means (i.e. the optimization is attached to the source code it belongs to).

    Note that -funbox-strict-fields applies to all strict fields, not just small fields (e.g. Double or Int). If you’re using GHC 7.4 or later you can use NOUNPACK to selectively opt-out for the unpacking enabled by -funbox-strict-fields.

    14.2. Functions

    Have function arguments be lazy unless you explicitly need them to be strict.

    The most common case when you need strict function arguments is in recursion with an accumulator:

    15. 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).

    16. Generalize the Code

    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.

    17. Monad Transformers

    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.

    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.

    References:

    http://www.seas.upenn.edu/~cis552/12fa/styleguide.html

    http://www.haskell.org/haskellwiki/Programming_guidelines

    https://github.com/tibbe/haskell-style-guide/blob/master/haskell-style.md

    https://stackoverflow.com/questions/1983047/good-haskell-coding-standards

  • Haskell 99 Problems – 07

    Problem 7

    (**) Flatten a nested list structure.

    Transform a list, possibly holding lists as elements into a `flat’ list by replacing each list with its elements (recursively).

    Example:

    * (my-flatten '(a (b (c d) e)))
    (A B C D E)
    

    Example in Haskell:

    We have to define a new data type, because lists in Haskell are homogeneous.

     data NestedList a = Elem a | List [NestedList a]
    *Main> flatten (Elem 5)
    [5]
    *Main> flatten (List [Elem 1, List [Elem 2, List [Elem 3, Elem 4], Elem 5]])
    [1,2,3,4,5]
    *Main> flatten (List [])
    []

    Solution: