1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
|
NAME
Syntax::Keyword::Match - a match/case syntax for perl
SYNOPSIS
use v5.16;
use Syntax::Keyword::Match;
my $n = ...;
match($n : ==) {
case(1) { say "It's one" }
case(2) { say "It's two" }
case(3) { say "It's three" }
case(4), case(5)
{ say "It's four or five" }
case if($n < 10)
{ say "It's less than ten" }
default { say "It's something else" }
}
DESCRIPTION
This module provides a syntax plugin that implements a control-flow
block called match/case, which executes at most one of a choice of
different blocks depending on the value of its controlling expression.
This is similar to C's switch/case syntax (copied into many other
languages), or syntax provided by Switch::Plain.
This is an initial, experimental implementation. Furthermore, it is
built as a non-trivial example use-case on top of XS::Parse::Keyword,
which is also experimental. No API or compatibility guarantees are made
at this time.
Experimental Features
Some of the features of this module are currently marked as
experimental (even within the context that the module itself is
experimental). They will provoke warnings in the experimental category,
unless silenced.
use Syntax::Keyword::Match qw( match :experimental(dispatch) );
use Syntax::Keyword::Match qw( match :experimental ); # all of the above
KEYWORDS
match
match( EXPR : OP ) {
...
}
A match statement provides the controlling expression, comparison
operator, and sequence of case statements for a match operation. The
expression is evaluated to yield a scalar value, which is then
compared, using the comparison operator, against each of the case
labels in the order they are written, topmost first. If a match is
found then the body of the labelled block is executed. If no label
matches but a default block is present, that will be executed instead.
After a single inner block has been executed, no further tests are
performed and execution continues from the statement following the
match statement.
The braces following the match block must only contain case or default
statements. Arbitrary code is not supported here.
Even though a match statement is a full statement and not an
expression, it can still yield a value if it appears as the final
statment in its containing sub or do block. For example:
my $result = do {
match( $topic : == ) {
case(1) { ... }
}
};
If the controlling expression introduces a new variable, that variable
will be visible within any of the case blocks, and will go out of scope
after the match statement finishes. This may be useful for temporarily
storing the result of a more complex expression.
match( my $x = some_function_call() : == ) {
case ...
}
Comparison Operators
The comparison operator must be either eq (to compare cases as strings)
or == (to compare them as numbers), or =~ (to compare cases using
regexps).
Since version 0.11 on any Perl release, or previous versions on Perl
releases 5.32 onwards, the isa operator is also supported, allowing
dispatch based on what type of object the controlling expression gives.
match( $obj : isa ) {
case(A::Package) { ... }
case(Another::Package) { ... }
}
Remember that comparisons are made in the order they are written, from
the top downwards. Therefore, if you list a derived class as well as a
base class, make sure to put the derived class before the base class,
or instances of that type will also match the base class case block and
the derived one will never match.
class TheBase {}
class Derived :isa(TheBase) {}
match( $obj : isa ) {
case(TheBase) { ... }
case(Derived) {
# This case will never match as the one above will always happen first
}
}
Since version 0.08 the operator syntax is parsed using
XS::Parse::Infix, meaning that custom infix operators can be
recognised, even on versions of perl that do not support the full
PL_infix_plugin mechanism.
case
case(VAL) { STATEMENTS... }
case(VAL), case(VAL), ... { STATEMENTS... }
A case statement must only appear inside the braces of a match. It
provides a block of code to run if the controlling expression's value
matches the value given in the case statement, according to the
comparison operator.
Multiple case statements are permitted for a single block. A value
matching any of them will run the code inside the block.
If the value is a non-constant expression, such as a variable or
function call, it will be evaluated as part of performing the
comparison every time the match statement is executed. For best
performance it is advised to extract values that won't need computing
again into a variable or use constant that can be calculated just once
at program startup; for example:
use constant CONDITION => a_function("with", "arguments");
match( $var : eq ) {
case(CONDITION) { ... }
...
}
The :experimental(dispatch) feature selects a more efficient handling
of sequences of multiple case blocks with constant expressions. This
handling is implemented with a custom operator that will entirely
confuse modules like B::Deparse or optree inspectors like coverage
tools so is not selected by default, but can be enabled for extra
performance in critical sections.
case if
case if(EXPR) { STATEMENTS... }
case(VAL), case if(EXPR) { STATEMENTS... }
Since version 0.13.
A case statement may also be written case if with a boolean predicate
expression in parentheses. This inserts a direct boolean test into the
comparison logic, allowing for other logical tests that aren't easily
expressed as uses of the comparison operator. As case if is an
alternative to a regular case, they can be combined on a single code
block if required.
For example, when testing an inequality in a selection of numerical ==
tests, or a single regexp test among some string eq tests.
match( $num : == ) {
case(0) { ... }
case(1), case(2) { ... }
case if($num < 5) { ... }
}
match( $str : eq ) {
case("abc") { ... }
case("def") { ... }
case if($str =~ m/g/) { ... }
}
By default the match value is not assigned into a variable that is
visible to case if expressions, but if needed a new lexical can be
constructed by using a regular my assignment.
match( my $v = some_expression() : eq ) {
case if($v =~ m/pattern/) { ... }
}
default
A default statement must only appear inside the braces of a match. If
present, it must be the final choice, and there must only be one of
them. It provides a block of code to run if the controlling
expression's value did not match any of the given case labels.
COMPARISONS
As this syntax is fairly similar to a few other ideas, the following
comparisons may be useful.
Core perl's given/when syntax
Compared to core perl's given/when syntax (available with use feature
'switch'), this syntax is initially visually very similar but actually
behaves very differently. Core's given/when uses the smartmatch (~~)
operator for its comparisons, which is complex, subtle, and hard to use
correctly - doubly-so when comparisons against values stored in
variables rather than literal constants are involved. It can be
unpredictable whether string or numerical comparison are being used,
for example. By comparison, this module requires the programmer to
specify the comparison operator. The choice of string or numerical
comparison is given in the source code - there can be no ambiguity.
Additionally, the isa operator is also permitted, which has no
equivalent ability in smartmatch.
Also, the given/when syntax permits mixed code within a given block
which is run unconditionally, or at least, until the first successful
when statement is encountered. The syntax provided by this module
requires that the only code inside a match block be a sequence of case
statements. No other code is permitted.
Switch::Plain
Like this module, Switch::Plain also provides a syntax where the
programmer specifies whether the comparison is made using stringy or
numerical semantics. Switch::Plain also permits additional conditions
to be placed on case blocks, whereas this module does not.
Additionally, the isa operator is also permitted, which has no
equivalent ability in Switch::Plain.
C's switch/case
The C programming language provides a similar sort of syntax, using
keywords named switch and case. One key difference between that and the
syntax provided for Perl by this module is that in C the case labels
really are just labels. The switch part of the statement effectively
acts as a sort of computed goto. This often leads to bugs caused by
forgetting to put a break at the end of a sequence of statements before
the next case label; a situation called "fallthrough". Such a mistake
is impossible with this module, because every case is provided by a
block. Once execution has finished with the block, the entire match
statement is finished. There is no possibility of accidental
fallthrough.
C's syntax only permits compiletime constants for case labels, whereas
this module will also allow the result of any runtime expression.
Code written in C will perform identically even if any of the case
labels and associated code are moved around into a different order. The
syntax provided by this module notionally performs all of its tests in
the order they are written in, and any changes of that order might
cause a different result.
TODO
This is clearly an early experimental work. There are many features to
add, and design decisions to make. Rather than attempt to list them all
here it would be best to check the RT bug queue at
https://rt.cpan.org/Dist/Display.html?Name=Syntax-Keyword-Match
AUTHOR
Paul Evans <leonerd@leonerd.org.uk>
|