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