Credence.Pattern.PreferFunctionClausesForListPatterns
(credence v0.8.0)
Copy Markdown
Detects a redundant case inside a guarded function clause that dispatches
on the same list parameter checked by is_list/1 in the guard, and promotes
the case clauses to pattern-matching function heads.
Bad
def my_fun([], _k), do: 0
def my_fun(list, k) when is_list(list) and is_integer(k) and k >= 0 do
case list do
[] -> 0
[_single] -> 0
[h | t] ->
# ... complex body
end
endGood
def my_fun([], _k), do: 0
def my_fun([_single], k) when is_integer(k) and k >= 0, do: 0
def my_fun([h | t], k) when is_integer(k) and k >= 0 do
# ... complex body (without wrapping case)
endScope — what it flags
This rule is the guarded counterpart of no_case_on_param_dispatch (which
only handles an unguarded, single bare-variable head). It fires only when
ALL of these hold:
- The clause is
def/defpwith awhenguard containingis_list(var). - The function body is exactly
case var do … end— no pre/post statements, norescue/catch/after. - The
casehas at least 2 clauses, all matching list patterns, none using a^pin. - The
caseis total over lists: its guardless, fully-irrefutable list clauses cover every possible length (e.g.[]+[h | t], or[]+[_]+[a, b | _]).
Why these limits (safety)
Each limit closes a way the rewrite could change the answer:
is_listguard + case on that variable.is_listguarantees a list, so thecaseonly discriminates list shape; promoting the patterns to heads is the same dispatch.- Totality over lists. A non-total
caseraisesCaseClauseErroron an unmatched list, but the equivalent function heads raiseFunctionClauseError(or fall through to a sibling clause) — a different answer. We only fire when the list patterns are exhaustive, so neither construct ever fails to match a list. - Body is exactly the case / no
rescue/catch/after. Any extra statement would be silently dropped. - No
^pin. A pinned^vmatches the in-scope subject; as a function head pattern the variable would be unbound.
Clause guards and the non-is_list part of the head guard are preserved
verbatim (guard semantics are identical in a case clause and a function
head). The original parameter name is rebound with pattern = var whenever
the body or a guard still refers to it, so nothing is left unbound.
Sibling clauses
Promoted heads are emitted in place. A promoted head is dropped only when
an earlier, guardless sibling clause of the same function has a pattern that
already subsumes it — in that case both the promoted head and the original
case branch were already dead, so removing it changes nothing. Guarded
siblings never trigger a drop (their guard may fail, leaving the branch live).