NAME
Switch::Back - given
/when
for a post-given
/when
Perl
VERSION
This document describes Switch::Back version 0.000002
SYNOPSIS
use v5.42; # given/when were removed in this version of Perl
use Switch::Back; # But this module brings them back
given ($some_value) {
when (1) { say 1; }
when ('a') { say 'a'; continue; }
break when ['b'..'e'];
default { say "other: $_" }
}
DESCRIPTION
The given
/when
construct was added to Perl in v5.10, deprecated in Perl v5.38, and removed from the language in Perl v5.42.
Code that uses the construct must therefore be rewritten if it is to be used with any Perl after v5.40.
Or you can leave the given
and when
code in place and use this module to (partially) resurrect the former feature in more recent Perls.
The module exports three keywords: given
, when
, and default
, and two subroutines: break
and continue
, which collectively provide most of the previous behaviour provided by use feature 'switch'
.
The module doesn't resurrect the smartmatch operator (~~
), but does export a smartmatch()
subroutine that implements a nearly complete subset of the operator's former behaviour.
INTERFACE
The module has no options or configuration. You simply load the module:
use Switch::Back;
...and its exported keywords will then rewrite the rest of your source code (safely, using the extensible keyword mechanism, not a source filter) to translate any subsequent given
, when
, and default
blocks into the equivalent post-Perl-v5.42 code. See "LIMITATIONS" for an overview of just how backwards compatible (or not) this approach is.
Loading the module also unconditionally exports two subroutines, break()
and continue()
, which provide the same explicit flow control as the former built-in break()
and continue()
functions.
The module also unconditionally exports a multiply dispatched subroutine named smartmatch()
, which takes two arguments and smartmatches them using the same logic as the former ~~
operator. See "Smartmatching differences" for a summary of the differences between smartmatch()
and the former built-in ~~
operator. See "Overloading smartmatch()
" for an explanation of how to add new matching behaviours to smartmatch()
(now that ~~
is no longer overloadable).
Smartmatching differences
The smartmatch()
subroutine provided by this module implements almost all of the behaviours of the former ~~
operator, with the exceptions listed below.
Note, however, that despite these limitations on smartmatching, the given
and when
keywords implement almost the complete range of smartmatching behaviours of the former given
and when
constructs. Specifically these two keywords will still auto-enreference arrays, hashes, and slices that are passed to them, and when
also implements the so-called "smartsmartmatching" behaviours on boolean expressions.
1. No auto-enreferencing of arguments
The smartmatch()
subroutine is a regular Perl subroutine so, unlike the ~~
operator, it cannot auto-enreference an array or hash or slice that is passed to it. That is:
%hash ~~ @array # Works (autoconverted to; \%hash ~~ \@array)
smartmatch( %hash, @array) # Error (hash and array are flattened within arglist)
smartmatch(\%hash, \@array) # Works (smartmatch() always expects two args)
2. No overloading of ~~
Because overloading of the ~~
operator was removed in Perl 5.42, the smartmatch()
subroutine always dies if its right argument is an object. The former ~~
would first attempt to call the object's overloaded ~~
, only dying if no suitable overload was found. See "Overloading smartmatch()
" for details on how to extend the behavior of smartmatch()
so it can accept objects.
Overloading smartmatch()
Because the smartmatch()
subroutine provided by this module is actually a multisub, implemented via the Multi::Dispatch module, it can easily be extended to match between additional types of arguments.
For example, if you want to be able to smartmatch against an ID::Validator
object (by calling its validate()
method), you would just write:
use Switch::Back;
use Multi::Dispatch;
# Define new smartmatching behaviour on ID::Validator objects...
multi smartmatch ($value, ID::Validator $obj) {
return $obj->validate( $value );
}
# and thereafter...
state $VALID_ID = ID::Validator->new();
given ($id) {
when ($VALID_ID) { say 'valid ID' } # Same as: if ($VALID_ID->validate($_)) {...}
default { die 'invalid ID' }
}
More generally, if you wanted to allow any object to be passed as the right-hand argument to smartmatch()
, provided the object has a stringification or numerification overloading, you could write:
use Multi::Dispatch;
use Types::Standard ':all';
# Allow smartmatch() to accept RHS objects that can convert to numbers...
multi smartmatch (Num $left, Overload['0+'] $right) {
return next::variant($left, 0+$right);
}
# Allow smartmatch() to accept RHS objects that can convert to strings...
multi smartmatch (Str $left, Overload[q{""}] $right) {
return next::variant($left, "$right");
}
You can also change the existing behaviour of smartmatch()
by providing a variant for specific cases that the multisub already handles:
use Multi::Dispatch;
# Change how smartmatch() compares a hash and an array
# (The standard behaviour is to match if ANY hash key is present in the array;
# but here we change it so that ALL hash keys must be present)...
multi smartmatch (HASH $href, ARRAY $aref) {
for my $key (keys %{$href}) {
return false if !smartmatch($key, $aref);
}
return true;
}
For further details on the numerous features and capabilities of the multi
keyword, see the Multi::Dispatch module.
LIMITATIONS
The re-implementation of given
/when
provided by this module aims to be fully backwards compatible with the former built-in given
/when
construct, but currently fails to meet that goal in several ways:
Limitation 1. You can't always use a given
inside a do
block
The former built-in switch feature allowed you to place a given
inside a do
block and then use a series of when
blocks to select the result of the do
. Like so:
my $result = do {
given ($some_value) {
when (undef) { 'undef' }
when (any => [0..9]) { 'digit' }
break when /skip/;
when ('quit') { 'exit' }
default { 'huh?' }
}
};
The module currently only supports a limited subset of that capability. For example, the above code will still compile and execute, but the value assigned to $result
will always be undefined, regardless of the contents of $some_value
.
This is because it seems to be impossible to fully emulate the implicit flow control at the end of a when
block (i.e. automatically jumping out of the surrounding given
after the last statement in a when
) by using other standard Perl constructs. Likewise, to emulate the explicit control flow provided by continue
and break
, the code has to be translated by adding at least one extra statement after the block of each given
and when
.
So it does not seem possible to rewrite an arbitrary given
/when
such that the last statement in a when
is also the last executed statement in its given
, and hence is the last executed statement in the surrounding do
block.
However, the module is able to correctly rewrite at least some (perhaps most) given
/when
combinations so that they work correctly within a do
block. Specifically, as long as a given
's block does not contain an explicit continue
or break
or goto
, or a postfix when
statement modifier, then the module optimizes its rewriting of the entire given
, converting it into a form that can be placed inside a do
block and still successfully produce values. Hence, although the previous example did not work, if the break when...
statement it contains were removed:
my $result = do {
given ($some_value) {
when (undef) { 'undef' }
when (any => [0..9]) { 'digit' }
# <-- POSTFIX when REMOVED
when ('quit') { 'exit' }
default { 'huh?' }
}
};
...or even if the postfix when
were converted to the equivalent when
block:
my $result = do {
given ($some_value) {
when (undef) { 'undef' }
when (any => [0..9]) { 'digit' }
when (/skip/) { undef } # <-- CONVERTED FROM POSTFIX
when ('quit') { 'exit' }
default { 'huh?' }
}
};
...then the code would work as expected and $result
would receive an appropriate value.
In general, if you have written a do
block with a nested given
/when
that is not going to work under this module, you will usually receive a series of compile-time "Useless use of a constant in void context" warnings, one for each when
block.
See the file t/given_when.t for examples of this construction that do work, and the file t/given_when_noncompatible.t for examples the will not (currently, or probably ever) work.
2. Use of when
modifiers outside a given
The former built-in mechanism allowed a postfix when
modifier to be used within a for
loop, like so:
for (@data) {
say when @target_value;
}
This module does not allow when
to be used as a statement modifier anywhere except inside a given
block. The above code would therefore have to be rewritten to either:
for (@data) {
given ($) {
say when @target_value;
}
}
...or to:
for (@data) {
when (@target_value) { say }
}
3. Scoping anomalies of when
modifiers
The behaviour of obscure usages such as:
my $x = 0;
given (my $x = 1) {
my $x = 2, continue when 1;
say $x;
}
...differs between the built-in given
/when
and this module's reimplementation. Under the built-in feature, $x
contains undef
at the say $x
line; under the module, $x
contains 2.
As neither result seems to make much sense, or be particularly useful, it is unlikely that this backwards incompatibility will ever be rectified.
DIAGNOSTICS
Incomprehensible "when"
Incomprehensible "default"
-
You specified a
when
ordefault
keyword, but the code following it did not conform to the correct syntax for those blocks. The error message will attempt to indicate where the problem was, but that indication may not be accurate.Check the syntax of your block.
Can't "when" outside a topicalizer
Can't "default" outside a topicalizer
-
when
anddefault
blocks can only be executed inside agiven
or afor
loop. Your code is attempting to execute awhen
ordefault
somewhere else. That never worked with the built-in syntax, and so it doesn't work with this module either.Move your block inside a
given
or afor
loop. Can't specify postfix "when" modifier outside a "given"
-
It is a limitation of this module that you can only use the
EXPR when EXPR
syntax inside agiven
(not inside afor
loop). And, of course, you couldn't ever use it outside of both.If your postfix
when
is inside a loop, convert it to awhen
block instead. Can't "continue" outside a "when" or "default"
-
Calling a
continue
to override the automatic "jump-out-of-the-surrounding-given
" behaviour ofwhen
anddefault
blocks only makes sense if you're actually inside awhen
or adefault
. However, your code is attempting to callcontinue
somewhere else.Move your
continue
inside awhen
or adefault
. Can't "break" outside a "given"
-
Calling a
break
to explicitly "jump-out-of-the-surrounding-given
" only makes sense when you're inside agiven
in the first place.Move your
break
inside agiven
.Or, if you're trying to escape from a
when
in a loop, changebreak
tonext
orlast
. Smart matching an object breaks encapsulation
-
This module does not support the smartmatching of objects (because from Perl v5.42 onwards there is no way to overload
~~
for an object).If you want to use an object in a
given
orwhen
, you will need to provide a variant ofsmartmatch()
that handles that kind of object. See "Overloadingsmartmatch()
" for details of how to do that. Use of uninitialized value in pattern match
Use of uninitialized value in smartmatch
-
You passed a value to
given
orwhen
that included anundef
, which was subsequently matched against a regex or against some other defined value. The former~~
operator warned about this in some cases, so thesmartmatch()
subroutine does too.To silence this warning, add:
no warnings 'uninitialized';
before the attempted smartmatch.
CONFIGURATION AND ENVIRONMENT
Switch::Back requires no configuration files or environment variables.
DEPENDENCIES
This module requires the Multi::Dispatch, PPR, Keyword::Simple, and Type::Tiny modules.
INCOMPATIBILITIES
This module uses the Perl keyword mechanism to (re)extend the Perl syntax to include given
/when
/default
blocks. Hence it is likely to be incompatible with other modules that add other keywords to the language.
BUGS
No bugs have been reported.
Please report any bugs or feature requests to bug-switch-back@rt.cpan.org
, or through the web interface at http://rt.cpan.org.
SEE ALSO
The Switch::Right module provides a kinder gentler approach to replacing the now defunct switch and smartmatch features.
AUTHOR
Damian Conway <DCONWAY@CPAN.org>
LICENCE AND COPYRIGHT
Copyright (c) 2024, Damian Conway <DCONWAY@CPAN.org>
. All rights reserved.
This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See perlartistic.
DISCLAIMER OF WARRANTY
BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.