Credence.Pattern.NoListPopAtForAccess (credence v0.7.0)

Copy Markdown

Detects List.pop_at(list, 0) |> elem(n) used to extract only the head (n = 0) or only the rest-after-head (n = 1) of a list, and rewrites it to the direct List accessor.

List.pop_at/2 returns a {popped, rest} tuple; using elem/2 to pull out just one side allocates that tuple for nothing.

Why List.first / List.delete_at, not hd / tl

The obvious-looking rewrite — hd/1 for elem(0) and tl/1 for elem(1) — is not behaviour-preserving. List.pop_at/2 tolerates an empty list (List.pop_at([], 0) is {nil, []}), so:

List.pop_at([], 0) |> elem(0)  # => nil   but  hd([])  raises
List.pop_at([], 0) |> elem(1)  # => []     but  tl([])  raises

The accessors that match pop_at's answer on every list — including the empty list — are List.first/1 (defaults to nil) and List.delete_at/2 (returns [] on an empty list). Both also raise the same FunctionClauseError as List.pop_at/2 on non-list inputs, so the raise-on-non-list contract is preserved as well.

Bad

list |> List.pop_at(0) |> elem(0)
list |> List.pop_at(0) |> elem(1)
elem(List.pop_at(list, 0), 1)

Good

List.first(list)
List.delete_at(list, 0)