diff --git a/README.md b/README.md index 2ef1a36..c60c7af 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,7 @@ Table of Contents: * [Properly use logging levels](#properly-use-logging-levels) * [Prefer the https protocol when specifying dependency locations](#prefer-the-https-protocol-over-others-when-specifying-dependency-urls) * [Suggestions & Great Ideas](#suggestions--great-ideas) + * [Favor higher-order functions over manual use of recursion](#favor-higher-order-functions-over-manual-use-of-recursion) * [CamelCase over Under_Score](#camelcase-over-under_score) * [Prefer shorter (but still meaningful) variable names](#prefer-shorter-but-still-meaningful-variable-names) * [Comment levels](#comment-levels) @@ -538,6 +539,16 @@ handling. Things that should be considered when writing code, but do not cause a PR rejection, or are too vague to consistently enforce. +*** +##### Favor higher-order functions over manual use of recursion +> Occasionally recursion is the best way to implement a function, but often a fold or a list comprehension will yield safer, more comprehensible code. + +*Examples*: [alternatives to recursion](src/recursion.erl) + +*Reasoning*: Manually writing a recursive function is error-prone, and mistakes can be costly. In the wrong circumstances, a buggy recursive function can miss its base case, spiral out of control, and take down an entire node. This tends to counteract one of the main benefits of Erlang, where an error in a single process does not normally cause the entire node to crash. + +Additionally, to an experienced Erlang developer, folds and list comprehensions are much easier to understand than complex recursive functions. Such contstructs behave predictably: they always perform an action for each element in a list. A recursive function may work similarly, but it often requires careful scrutiny to verify what path the control flow will actually take through the code in practice. + *** ##### CamelCase over Under_Score > Symbol naming: Use variables in CamelCase and atoms, function and module names with underscores. diff --git a/src/recursion.erl b/src/recursion.erl new file mode 100644 index 0000000..df9a943 --- /dev/null +++ b/src/recursion.erl @@ -0,0 +1,38 @@ +-module(recursion). + +-export([recurse/1, fold/1, map/1, comprehension/1]). + +%% +%% Example: +%% Different functions to capitalize a string +%% + +%% BAD: makes unnecessary use of manual recursion +recurse(S) -> + lists:reverse(recurse(S, [])). + +recurse([], Acc) -> + Acc; +recurse([H | T], Acc) -> + NewAcc = [string:to_upper(H) | Acc], + recurse(T, NewAcc). + +%% GOOD: uses a fold instead to achieve the same result, +%% but this time more safely, and with fewer lines of code +fold(S) -> + Result = lists:foldl(fun fold_fun/2, [], S), + lists:reverse(Result). + +fold_fun(C, Acc) -> + [string:to_upper(C) | Acc]. + +%% BETTER: uses a map instead of a fold to yield a simpler +%% implementation, since in this case a fold is overkill +map(S) -> + lists:map(fun string:to_upper/1, S). + +%% BEST: in this case, a list comprehension yields the +%% simplest implementation (assuming we ignore the fact +%% that string:to_upper can also be used directly on strings) +comprehension(S) -> + [string:to_upper(C) || C <- S].