W0070 - Inline Nested List Comprehension
Warning
foo(List) ->
[f(X) || X <- [g(Y) || Y <- List]].
%% ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 weak: W0070: Nested list comprehension can be inlined to avoid intermediate list allocation.
Explanation
This warning detects nested list comprehensions where the outer comprehension iterates over the result of an inner comprehension. The pattern allocates an unnecessary intermediate list that can be eliminated by inlining.
Before
foo(List) ->
[f(X) || X <- [g(Y) || Y <- List]].
After
foo(List) ->
[f(g(Y)) || Y <- List].
Why This Matters
- Readability: The inlined form is more concise and directly expresses the intent
- Performance & Memory Pressure: The inner comprehension
[g(Y) || Y <- List]creates a temporary list that is immediately consumed and discarded
When the Diagnostic Does Not Apply
The transformation is only suggested when the outer variable appears exactly once in the body expression. If the variable appears multiple times, inlining would duplicate the computation:
% NOT transformed - X appears twice, g(Y) would be evaluated twice or require a `begin ... end` block to first bind the result to a temporary variable
[{X, X + 1} || X <- [g(Y) || Y <- List]].
Notes
Strictly, this changes the order of evaluation, and hence the fix can change semantics. Comparing:
Before
foo(List) ->
[f(X) || X <- [g(Y) || Y <- List]].
After
foo(List) ->
[f(g(Y)) || Y <- List].
In the before case, all evaluations of g(Y) are completed before any evaluations of f(X). In the case after, we have f(g(Y)), so the function calls are interleaved. If g/1 and f/1 are side-effectful and dependent, this change may be problematic. However, it's keeping this ordering which makes the before case more expensive and requires keeping the intemediate list state.
Where this order is required, it is suggested to extract the inner comprehension and explicitly
bind it first, to make the intention clear, e.g.:
foo(List) ->
Gs = [g(Y) || Y <- List],
[f(X) || X <- Gs].