Credence.Pattern.NoMapKeysForMembership
(credence v0.7.1)
Copy Markdown
Detects x in Map.keys(m) and x not in Map.keys(m) used for membership
testing, and rewrites to Map.has_key?(m, x) / not Map.has_key?(m, x).
Map.keys/1 builds an O(n) list just to check membership — Map.has_key?/2
does the same check in O(log n) without allocating a list.
Bad
Enum.filter(queue, &(&1 not in Map.keys(visited)))
if x in Map.keys(cache), do: cached, else: compute(x)Good
Enum.reject(queue, &Map.has_key?(visited, &1))
if Map.has_key?(cache, x), do: cached, else: compute(x)Safety: only side-effect-free left operands
x in Map.keys(m) evaluates the right operand (Map.keys(m)) before the
left (x), whereas the rewrite Map.has_key?(m, x) evaluates x before m.
When the left operand has observable side effects this reorders them, so the
rule fires only when the left operand is statically side-effect-free — a
bare variable or a capture argument (&1).
For those, reordering is unobservable and the rewrite is behaviour-preserving
(membership via in/Enum.member? and map-key lookup both use term equality,
and both raise BadMapError on a non-map).