NAME
Switch::Declare - compile-time, lexically-scoped switch/case
SYNOPSIS
use Switch::Declare;
# statement form
switch ($value) {
case 200 { handle_ok() } # numeric -> ==
case "GET" { handle_get() } # string -> eq
case /^\d+$/ { all_digits() } # regex -> =~
case [400 .. 499] { client_error() } # range -> >= && <=
case ["a","b","c"] { in_set() } # list -> membership
case \&is_weekend { weekend() } # predicate-> $code->($topic)
default { fallback() }
}
# expression form - yields the matched arm's value
my $label = switch ($status) {
case 200 { "ok" }
case 404 { "missing" }
default { "other" }
};
DESCRIPTION
Switch::Declare installs a switch keyword that parses an entire switch (EXPR) { ... } construct at compile time and lowers it to an ordinary Perl optree. All of the parsing work happens once, at compile time - nothing of the parser remains at runtime.
The construct is a real lexical pragma: the switch keyword is recognised only within the lexical scope of a use Switch::Declare (and can be switched off again with no Switch::Declare). Outside such a scope switch is an ordinary identifier, so the keyword never collides with unrelated code.
The scrutinee is evaluated exactly once. The first matching case wins; there is no implicit fallthrough. A trailing default matches when no case did. Used as an expression the construct yields the value of the executed block (undef if nothing matched and there is no default); used as a statement it simply runs the matched block.
Pattern kinds
Each case pattern is recognised at compile time and lowered to the cheapest matching op:
a number literal compiles to numeric
==a string literal (
'...'or"...") compiles to stringeqa /.../ regex (with optional
imsxflags) matches the topica
[ LO .. HI ]range compiles to an inclusive bounds test (numeric or string depending on the bound literals)a
[ a, b, c ]list compiles to a membership test (anORchain of equality tests, each numeric or string per element)a predicate - either
\&name(optionally package-qualified,\&Pkg::name) or an inlinesub { ... }- is called with the topic as its argument; a true return matches. An inline sub closes over the enclosing lexicals:my $limit = 100; switch ($n) { case sub { $_[0] > $limit } { "over" } default { "ok" } }
Range and list comparisons follow Perl's own ==/eq rules per element, so a [1, 2, 3] list (numeric elements) compared against a non-numeric topic warns under use warnings exactly as the equivalent hand-written $x == 1 || $x == 2 would. Keep list/range element types consistent with the topic to avoid this.
Patterns are deliberately a small, predictable grammar of literals rather than arbitrary expressions, so classification is unambiguous and the emitted code is as tight as a hand-written conditional.
PERFORMANCE
The keyword plugin emits the op tree directly in place of the keyword, so there is no wrapper subroutine call per evaluation. The chain of case tests lowers to a native conditional expression - the very same ==/eq/=~/bounds ops you would write by hand.
For the common case - the scrutinee is a plain variable or constant and each arm is a single-expression block - the construct compiles to exactly a hand-written if/elsif (ternary) chain: no temporary, no enclosing scope, no extra ops. In the bundled benchmark (xt/bench.pl) switch and the equivalent hand-rolled chain run within measurement noise of each other (0-2%).
Dispatch mode
When a switch is effectively a lookup table - every case maps a string literal to a constant value, with at least a handful of arms - it is lowered to a single O(1) hash lookup against a hash built once at compile time, instead of an O(n) chain of eq tests:
# compiles to a single hash lookup, not 6 string comparisons
my $name = switch ($code) {
case "GET" { "read" }
case "PUT" { "update" }
case "POST" { "create" }
case "DELETE" { "remove" }
case "PATCH" { "modify" }
case "HEAD" { "peek" }
default { "?" }
};
In the bundled benchmark a 20-arm string switch in dispatch mode runs about 2.5x faster than the equivalent hand-written if/elsif chain. The table is constructed once at compile time (not per call), so there is no per-evaluation build cost. Dispatch mode is chosen automatically; it never changes behaviour (numeric switches keep == semantics and stay as a chain; any non-constant arm or duplicate key falls back to the chain), so you never opt in or out.
AUTHOR
LNATION <email@lnation.org>
LICENSE AND COPYRIGHT
This software is Copyright (c) 2026 by LNATION <email@lnation.org>.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)