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 string eq

  • a /.../ regex (with optional imsx flags) matches the topic

  • a [ 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 (an OR chain of equality tests, each numeric or string per element)

  • a predicate - either \&name (optionally package-qualified, \&Pkg::name) or an inline sub { ... } - 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)