Credence.Pattern.NoListDuplicateFlatten (credence v0.7.1)

Copy Markdown

Detects Enum.concat(List.duplicate(list, n)) (or the piped list |> List.duplicate(n) |> Enum.concat()) and suggests Enum.flat_map/2 instead.

LLMs frequently produce this two-step pattern when they need to "tile" or "repeat" a list n times: first List.duplicate/2 to create a list of copies, then Enum.concat/1 to merge them. Enum.flat_map/2 does the same work in a single pass.

Why this is narrowed to Enum.concat + variable + literal count

This rule is deliberately restricted to the cases where the rewrite gives the exact same answer for every input:

  • Enum.concat/1 only, never List.flatten/1. Enum.concat merges one level; Enum.flat_map does too, so they agree. But List.flatten/1 flattens recursively, so List.flatten(List.duplicate([[1], [2]], 2)) is [1, 2, 1, 2] while Enum.flat_map(1..2, fn _ -> [[1], [2]] end) is [[1], [2], [1], [2]]. Different answer → not rewritten.

  • The repetition count must be a positive integer literal. The rewrite uses 1..n. For n == 0 the original yields [] but 1..0 is a descending range ([1, 0]) so the rewrite runs twice; for negative n, List.duplicate/2 raises but 1..n would not. Only a literal n >= 1 is provably safe, so a runtime variable count is left untouched.

  • The duplicated value must be a bare variable. List.duplicate evaluates its argument once; the fn _ -> value end closure evaluates it n times. For an arbitrary expression that changes side effects, so only a side-effect-free variable reference is rewritten.

Bad

list
|> List.duplicate(3)
|> Enum.concat()

Enum.concat(List.duplicate(list, 3))

Good

Enum.flat_map(1..3, fn _ -> list end)