Credence.Pattern.PreferMapIntersectOverMapsetIntersection
(credence v0.8.0)
Copy Markdown
Detects MapSet-based intersection of map keys that can be replaced with
Map.intersect/3 (Elixir 1.14+).
The verbose pipeline Map.keys(a) |> MapSet.new() |> MapSet.intersection(MapSet.new(Map.keys(b))) |> MapSet.to_list()
followed by an Enum.map that fetches and merges values from both maps
can be replaced with a single Map.intersect/3 call.
Bad
common_keys =
Map.keys(freq1)
|> MapSet.new()
|> MapSet.intersection(MapSet.new(Map.keys(freq2)))
|> MapSet.to_list()
common_keys
|> Enum.map(fn element ->
count1 = Map.fetch!(freq1, element)
count2 = Map.fetch!(freq2, element)
{element, min(count1, count2)}
end)
|> Enum.sort()Good
freq1
|> Map.intersect(freq2, fn _key, count1, count2 -> min(count1, count2) end)
|> Enum.sort_by(fn {key, _value} -> key end)Scope — what makes the rewrite safe
The whole two-statement shape must be present (the rule flags and fixes the same node — never one without the other), and every gap that could change the answer is closed:
- The intersection assignment feeds exactly one
Enum.map |> Enum.sort. The assigned variable is used once; the fix deletes its binding, so a second use would strand it unbound. freq1/freq2are bare variables. They are spliced verbatim into theMap.intersect/3call.- The merge expression is a pure arithmetic combination of
count1/count2and numeric literals (min/max/+/-/*/div/rem/abs). This both rules out a merge that references theelementkey (which becomes_keyand would be unbound) and guarantees the result is independent of evaluation order —MapSet.to_listandMap.intersectenumerate keys in different orders, but for a pure merge the finalEnum.sort/Enum.sort_by(key)(keys are unique, so sorting by key equals sorting by the whole{key, value}tuple) produces an identical list.