CONVENTIONS ----------- Conventions for Erlang functions in packages and modern Erlang programming in general: Unused variables Always have the compiler option 'warn_unused_vars' turned on. Warnings are not generated for variables whose names begin with '_'. Eliminate all warnings from old code by renaming all variables that really are unused, e.g.: 'X' -> '_X'. Doing this usually pays off, as it tracks down bugs and dead code. Return values in general - A function should *not* return wrapped values like {ok,Value}/{error,Reason} to indicate success or failure. The assumed behaviour should be success, and failures should be signalled by exceptions, as described below. - A function which is evaluated for its side effects only should always return the atom 'ok'; this is the typical Erlang "void" value. Use {value, X}/none: When a function may either return a value or signal that no value is available, always return {value, X} if there is a value, and 'none' otherwise. The exception is when there is an *obviously* better encoding, like e.g. "nonempty string/empty string" (when it is *definite* that "empty string" cannot be a legal value). Use the same convention for record fields which may or may not have a specified value. This makes it easy to write e.g.: #thingy{foo = get_foo_value(...)} where 'get_foo_value' returns 'none' or {value, ..}, and later switch on the contents of that field: frob(#thingy{foo = none}) -> ...; frob(#thingy{foo = {value, X}}) -> .... Note that error handling is a separate issue - thus *do not* add a third case {error, ...}; see below for details on error handling and exceptions. Use is_... tests in guards Use the new 'is_...' versions of guard tests, rather than the old names (cf. above). E.g., not: X when float(X) -> ... but instead X when is_float(X) -> ... (The 'erl_tidy' tool in the syntax_tools distribution can be used to check this, or even rewrite the code automatically.) The reason is that guards should not have a separate name space from the rest of the program. Using the new names can make it possible to eventually generalise the kind of expressions allowed in guards. Guard tests and failure - Never write clause guards that rely on failures for choosing the correct clause. The following kind of programming is *strongly discouraged*: if hd(X) >= 0 -> ... true -> % If X is a cons cell whose head is less % than 0 *or* if X is not a cons cell: ... end since it assumes that if X is not a cons cell, then the failure of hd(X) will simply cause the next clause to be selected. Never rely on this behaviour - it makes the program difficult to understand, and it could possibly be changed in a future version of Erlang, so that the failure simply causes a normal exception instead. Use boolean-valued functions with good names! - A function that can be made into a simple yes/no test (a predicate) should always be written in that way. For example, do not define: sex(Thing) -> male | female but instead is_female(Thing) -> true | false (unless you know that other alternatives may be added some day). Do not use names like: female(Thing) -> ... since it does not make it clear whether 'female' is a predicate or a conversion operation. Do not use negative-form names, as in: not_male(Thing) -> true | false since it makes it difficult to remember if the name of a particular predicate was "is..." or "not_...". Also, double negatives become confusing, as in: if not not_male(Thing) -> ... Exceptions and errors - Always use 'try ... catch ... end' to handle exceptions (when it finally becomes available). The 'catch ...' expression is then obsolete and should not be used in new code. - Unexpected errors (type errors, etc.) should cause an exception of type 'error'. This is raised by erl.lang:error(Reason) or erlang:fault(Reason). (This is the same kind of exception that is raised by errors in built-in functions and operators.) * Reason should be something like {badarg, X}, that is likely to be inspected by a human, but might possibly be analyzed by a program. * Errors should typically not be caught, but be allowed to crash the process. * Errors that are not caught cause the process to terminate, and propagate the Reason as a signal to all linked processes. - "Expected" possible errors in a function, such as "file not found", "key not present in dictionary", and so on, should be reported by an exception of type 'throw'. This is raised by erl.lang:throw(Value) or erlang:throw(Value). (This is also used for nonlocal returns in general.) * Value should be something like 'file_not_found' or {parse_error, Line}, that is intended to be caught by the program at some point and analysed. * Throws that are not caught by the process are turned into a corresponding error exception with Reason = {nocatch, Value}. - Explicit termination of the process is done by raising an exception of type 'exit'. This is raised by erl.lang:exit(Reason) or erlang:exit(Reason). (If Reason is the atom 'normal', and the exception is not caught, the behaviour is indistinguishable from that when the evaluation of the process' initial function call has finished.) * Exits (unless caught) cause the process to terminate, and propagate the Reason as a signal to all linked processes. * Reason should be as for a throw. (Process exits can be compared to nonlocal function returns.) * Exits should typically not be caught, but be allowed to terminate the process. * Exits should not be used for other purposes; these are covered by 'throw' and 'error'. Obsolete/deprecated language features - Don't use 'catch ...' (see Exceptions above) if possible - use 'try' instead (when it becomes available). - Don't use tuples {Module, Function} as proper functional values, as in: F = {lists, reverse} F([1,2,3]). This "feature" is nothing but trouble (for one thing, it hides important module dependencies from programs like 'xref'), and it should preferably be removed from future versions of Erlang. Either use real funs: F = fun (Xs) -> lists:reverse(Xs) end, F([1,2,3]) or match out the names manually if you have to use a dynamic callback hook: my_code({Module, Function}) -> Module:Function([1,2,3]) - Don't use apply(Module, Function, Arguments) unless the Arguments list actually *can* vary arbitrarily in length. (This is rarely the case.) Instead, use: Module:Function(Arg1, ... ArgN) Where Module and/or Function is a variable. (This is *much* more efficient than 'apply'.) If Arguments can have one of a limited number of lengths, then write an explicit switch: case Arguments of [] -> Module:Function(); [A1] -> Module:Function(A1); [A1, A2] -> Module:Function(A1,A2) end (This is still more efficient, and also has the advantage that it catches illegal-length argument lists.) Never use erlang:apply/2. (If you did not know it existed, then just forget about it again.) - Use spawn(Fun) and spawn_link(Fun) instead of spawn/spawn_link(Module, Function, Arguments). This is cleaner (cf. apply/3 above) and lets you avoid exporting functions from a module when they are merely starting points for a process. E.g.: -module(my_server). -export([start/1]) -> start(Data) -> spawn(fun () -> init(Data) end). init(Data) -> State = setup_state(Data), loop(State). loop(State) -> ... If the start function is in another module, you should still use spawn/1, as in: spawn(fun () -> my_server:init(Data) end) (Note that as soon as the tail call inside the fun is executed, the fun itself becomes garbage.) Exported functions - Separate the user interface from other exported functions that are only for internal use, or for debugging, etc. The main module should only export functions that are officially exported and documented. E.g., if a function needs to be exported for the only reason that it is a checkpoint for dynamic code updates, as is usually the case with the main loop function in a server, then always place this function in some other module than the one which exports the user interface. Passing module names in parameters - If module names are passed around as data, then *do not* hard-code such names. At the very least, use a macro definition, as in: -define(CALLBACK_MODULE, my_server_callbacks). ... start(...) -> gen_server:start(?CALLBACK_MODULE, ...) Indexing - Indexing should preferably start at 0. See for example the module 'erl.lang.list'.