Credence.Pattern.NoMapKeysOrValuesForIteration (credence v0.8.0)

Copy Markdown

Performance rule: Detects Map.values(map) or Map.keys(map) passed directly into an Enum function, which creates an unnecessary intermediate list.

All Enum functions accept maps directly and iterate over {key, value} pairs without allocating an intermediate list.

Scope — only order-INDEPENDENT terminals are rewritten

Map.keys/1 and Map.values/1 iterate in a different order than a direct Enum-over-map traversal once the map has more than 32 keys (small-map array vs. hash-tree iterator). So the rule rewrites ONLY operations whose result is independent of iteration order — all?, any?, count, empty?, frequencies, frequencies_by. Order-dependent ops (map, filter, find, at, take, join, reduce, sort, …) would reorder the result and are left untouched: neither flagged nor rewritten (check and fix share one scope gate, fixable?/2). Enum.sum/product/max/min are already idiomatic and also not flagged.

Automatic fixing

# Callback wrapping binds the user's variable to the right slot:
Enum.all?(Map.values(degrees), fn v -> v == 0 end)
 Enum.all?(degrees, fn {_k, v} -> v == 0 end)

Map.values(m) |> Enum.count()
 Enum.count(m)

Enum.frequencies(Map.keys(m))
 Enum.frequencies_by(m, fn {k, _} -> k end)

Bad

Enum.all?(Map.values(degrees), fn v -> v == 0 end)
Map.values(map) |> Enum.count()

Good

Enum.all?(degrees, fn {_k, v} -> v == 0 end)
Enum.count(map)
Map.values(map) |> Enum.sum()       # already idiomatic
Map.values(m) |> Enum.filter(...)   # order-dependent — left alone

Glossary (terms used throughout this module)

  • map_fn : the Map function being eliminated, either :keys or
            `:values`. Drives every `{k, v}` slot decision.
  • enum_fn : the Enum function the user is calling on the result of
            `Map.keys/values`.
  • map_expr: the AST of the actual map being iterated (the argument
            to `Map.keys/values`).
  • enum_args / enum_args: the remaining args of the Enum call
            (everything after `Map.keys/values(map)`).
  • "kv pattern" : the destructuring tuple pattern ({k, _v} or
            `{_k, v}`) we substitute for the user's single-arg
            callback parameter.
  • "extractor": the closing Enum.map(..., fn {k, _} -> k end) we
            append when the inner Enum function returns a list of
            `{k, v}` pairs but the caller only wanted keys/values.