From 4abfb96f85b0799f9a8002003ad152ff360e5829 Mon Sep 17 00:00:00 2001 From: Nick Marino Date: Fri, 13 Jan 2017 17:48:13 -0500 Subject: [PATCH 1/4] Add guidelines on judicious use of recursion --- README.md | 11 +++++++++++ src/recursion.erl | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 src/recursion.erl diff --git a/README.md b/README.md index 2ef1a36..f8c6587 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) + * [Avoid recursion when possible](#avoid-recursion-when-possible) * [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. +*** +##### Avoid recursion when possible +> 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..e6b86ec --- /dev/null +++ b/src/recursion.erl @@ -0,0 +1,33 @@ +-module(recursion). + +-export([recurse/1, fold/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]. + +%% 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]. From f5516f5d5662eb223fcb82a475d6dc7aea77f38f Mon Sep 17 00:00:00 2001 From: Nick Marino Date: Wed, 18 Jan 2017 17:34:13 -0500 Subject: [PATCH 2/4] Update title of recursion notes This should improve clarity, since technically functions like foldl and map use recursion; we want people to avoid writing their own recursive functions, not avoid using any function that does recursion under the hood. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f8c6587..f70e666 100644 --- a/README.md +++ b/README.md @@ -65,7 +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) - * [Avoid recursion when possible](#avoid-recursion-when-possible) + * [Avoid writing your own recursive functions when possible](#avoid-writing-your-own-recursive-functions-when-possible) * [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) @@ -540,7 +540,7 @@ handling. Things that should be considered when writing code, but do not cause a PR rejection, or are too vague to consistently enforce. *** -##### Avoid recursion when possible +##### Avoid writing your own recursive functions when possible > 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) From dadf0597f8d4bbeab79f22838d9077b97526ecb7 Mon Sep 17 00:00:00 2001 From: Nick Marino Date: Thu, 19 Jan 2017 11:41:24 -0500 Subject: [PATCH 3/4] Add map example to recursion.erl --- src/recursion.erl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/recursion.erl b/src/recursion.erl index e6b86ec..df9a943 100644 --- a/src/recursion.erl +++ b/src/recursion.erl @@ -1,6 +1,6 @@ -module(recursion). --export([recurse/1, fold/1, comprehension/1]). +-export([recurse/1, fold/1, map/1, comprehension/1]). %% %% Example: @@ -26,6 +26,11 @@ fold(S) -> 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) From be5c529eb3fb0f9ee03c01b1ddd3aac347142484 Mon Sep 17 00:00:00 2001 From: Nick Marino Date: Thu, 19 Jan 2017 14:00:12 -0500 Subject: [PATCH 4/4] Reword recursion guidelines again This better conveys when and why you should avoid manual recursion. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f70e666..c60c7af 100644 --- a/README.md +++ b/README.md @@ -65,7 +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) - * [Avoid writing your own recursive functions when possible](#avoid-writing-your-own-recursive-functions-when-possible) + * [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) @@ -540,7 +540,7 @@ handling. Things that should be considered when writing code, but do not cause a PR rejection, or are too vague to consistently enforce. *** -##### Avoid writing your own recursive functions when possible +##### 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)