Reduce Anti-Patterns
I mentioned in Reduce is Not the Answer how:
reduce
is fairly low-levelreduce
doesn’t capture “intent”
Very loosely, I believe that “loop constructs” fall somewhere on this abstraction scale:
Exact placement is subjective; this is a starting point. As for ??
and beyond,
it might correspond to well-named functions with custom looping logic. For example, mean
and stdev
would
fit the bill. However, these function quickly become less generic.
If I missed something to the right, let’s talk 😄
With this post, I want to sketch typical reduce
patterns and identify their higher-level alternatives.
Disclaimers
- Elixir is used; that should not stop anyone from understanding what’s going on
- I’m aiming for useful, not “revelation”
- I put anchors ( 🔗 ) next to the section headers, copy-paste them as needed
- your code might be an exception…
I initially added a comment to almost all sections:
This is usually combined with other logic, to “save the extra loop”.
Yes, that’s usually how it goes.
Enum.map 🔗
Usual shape:
Giveaways:
- initial empty list
- item mapping or transformation
- prepend-reverse, or append
Alternative:
Comments:
Not often directly found in the wild.
Enum.filter / Enum.reject 🔗
Usual shape:
Giveaways:
- initial empty list
- some conditional
- prepend-reverse, or append
Alternative:
Comments:
Probably combined with a map
.
Enum.find 🔗
Usual shape:
Giveaways:
- initial
nil
or sentinel value - conditional; rest of loop doesn’t matter…
Alternative:
Comments:
It might be driven by an edge case or custom logic.
Enum.all? / Enum.any? 🔗
Usual shape:
Giveaways:
- initial flag
- conditional toggles a flag; rest of loop doesn’t matter…
Alternative:
Comments:
In some languages (e.g. JavaScript), this was be a later addition. It
also goes under various names (some
, every
…) and it might not be easy to
understand what it does and how/when to use it.
Enum.count 🔗
Usual shape:
Giveaways:
- initial zero
- conditional math
Alternative:
Comments:
This isn’t always available; in that case, I would go for:
Enum.sum 🔗
Usual shape:
Giveaways:
- initial zero
- conditional math?
- value extraction/generation
Alternative:
Enum.group_by / Enum.frequencies / Enum.split_with 🔗
Usual shape:
Giveaways:
- initial empty map
- conditional update of map
- management of “not yet in map” edge case
Alternative:
Comments:
This is all the same logic: divide things into groups.
- for
frequencies
, you only care about the size of the groups - for
split_with
(often namedpartition
), there are only 2 groups
Enum.min / Enum.max 🔗
Usual shape:
Giveaways:
- initial sentinel value?
- conditional update
Alternative:
Discussion
Eventually, more languages and compilers will be able to grab a combination of map-filter-etc and compile it down to one loop.
In the meantime, I’m afraid we will have to live with “optimized” one-loop reduce
.
I’m always happy to discuss specific benchmarks regarding why your reduce
can’t
be a combination of map
, filter
and other Enum
functions.