LFE Programming Rules and Conventions

6 Various LFE Specific Conventions

6.1 Use records as the principle data structure

Use records as the principle data structure. A record is a tagged tuple and was introduced in Erlang version 4.3 and thereafter (see EPK/NP 95:034). It is similar to struct in C or record in Pascal.

If the record is to be used in several modules, its definition should be placed in a header file (with suffix .lfe) that is included from the modules. If the record is only used from within one module, the definition of the record should be in the beginning of the file the module is defined in.

The record features of LFE can be used to ensure cross module consistency of data structures and should therefore be used by interface functions when passing data structures between modules.

6.2 Use selectors and constructors

Use the record macros provided by LFE for managing instances of records. Don't use matching that explicitly assumes that the record is a tuple.

Example:

(defun demo ()
  (let* ((joe (make-person name '"Joe" age 29))
         (name-2 (person-name joe)))
    ...
    ))

Don't program like this:

(defun demo ()
  (let* ((joe (make-person name '"Joe" age 29))
         ((tuple 'person name age) joe))
    ...
    ))

6.3 Use tagged return values

Use tagged return values.

Don't program like this:

(defun keysearch
  ((key (cons (tuple key value) tail))
    value)
  ((key (cons (tuple wrong-key wrong-value) tail))
    (keysearch key '()))
  ((key '())
    'false))

Then (tuple key, value) cannot contain the false value.

This is the correct solution:

(defun keysearch
  ((key (cons (tuple key value) tail))
    (tuple 'value value))
  ((key (cons (tuple wrong-key wrong-value) tail))
    (keysearch key '()))
  ((key '())
    'false))

6.4 Use catch and throw with extreme care

Do not use catch and throw unless you know exactly what you are doing! Use catch and throw as little as possible.

Catch and throw can be useful when the program handles complicated and unreliable input (from the outside world, not from your own reliable program) that may cause errors in many places deeply within the code. One example is a compiler.

6.5 Use the process dictionary with extreme care

Do not use get and put etc. unless you know exactly what you are doing! Use get and put etc., as little as possible.

A function that uses the process dictionary can be rewritten by introducing a new argument.

Don't program like this:

(defun tokenize
  (((cons head tail))
    ...)
  (('())
    (case (get-characters-from-device (get 'device))
      ('eof
        '())
      ((tuple 'value chars)
        (tokenize chars)))))

The correct solution:

(defun tokenize
  ((device (cons head tail))
    ...)
  ((device '())
    (case (get-characters-from-device device)
      ('eof
        '())
      ((tuple 'value chars)
        (tokenize device chars)))))

The use of get and put will cause a function to behave differently when called with the same input at different occasions. This makes the code hard to read since it is non-deterministic. Debugging will be more complicated since a function using get and put is a function of not only of its argument, but also of the process dictionary. Many of the run time errors in LFE (for example bad_match) include the arguments to a function, but never the process dictionary.

6.6 Use import judiciously

We strongly urge you to be caseful about the use of (import ...) in (defmodule ...). Over-using can make code in the module harder to read, since you cannot directly see in what module a function is defined.

Generally, explicit is better than implicit.

That being said, there are times when using import actually makes the code more legible.

6.7 Exporting functions

Make a distinction of why a function is exported. A function can be exported for the following reasons (for example):

  • It is a user interface to the module.
  • It is an interface function for other modules.
  • It is called from apply, spawn, etc., but only from within its module.

Use different (export ...) groupings and comment them accordingly.

Example:

(defmodule my-mod
  ;; user interface
  (export
    (help 0)
    (start 0)
    (stop 0)
    (info 1))
  ;; inter-module exports
  (export
    (make-pid 1)
    (make-pid 2)
    (process-abbrevs 0)
    (print-info 5))
  ;; exports for use within module only
  (export
    (init 1)
    (info-log-impl 1)))