Credence.Pattern.NoZipThenMap (credence v0.7.1)

Copy Markdown

Detects Enum.zip/2 followed by Enum.map/2 that destructures the resulting 2-tuples, which can be replaced by Enum.zip_with/3.

Why this matters

Enum.zip(a, b) |> Enum.map(fn {x, y} -> expr end) allocates an intermediate list of 2-tuples only to immediately destructure them. Enum.zip_with/3 (available since Elixir 1.14) combines both steps into a single pass without the intermediate allocation.

Bad

Enum.zip(names, scores)
|> Enum.map(fn {name, score} -> {name, score * 2} end)

Enum.map(Enum.zip(names, scores), fn {name, score} ->
  {name, score * 2}
end)

names
|> Enum.zip(scores)
|> Enum.map(fn {name, score} -> {name, score * 2} end)

Good

Enum.zip_with(names, scores, fn name, score ->
  {name, score * 2}
end)

Scope

Flags when ALL of these hold:

  • Enum.zip/2 is called with two arguments.
  • Its result is passed to Enum.map/2 (via pipe or nesting).
  • The Enum.map function destructures a 2-tuple into two variables.

Does NOT flag:

  • Enum.zip/2 alone (no following Enum.map).
  • Enum.map whose function does not destructure a 2-tuple.
  • Enum.zip/1 (a list of enumerables) at the head of a pipe — this is the real Enum.zip/1, not a pipe-elided zip/2, and is not convertible.
  • Guarded fns (fn {x, y} when ... -> ... end) — dropping or rebuilding the guard is out of scope, so these are left untouched.
  • Already-idiomatic Enum.zip_with/3.

Auto-fix

Replaces the Enum.zip |> Enum.map pair with Enum.zip_with/3, transforming the fn's tuple pattern into separate parameters.