NAME
Switch::Back - given/when for a post-given/when Perl
VERSION
This document describes Switch::Back version 0.000005
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
whenordefaultkeyword, 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 topicalizerCan't "default" outside a topicalizer-
whenanddefaultblocks can only be executed inside agivenor aforloop. Your code is attempting to execute awhenordefaultsomewhere else. That never worked with the built-in syntax, and so it doesn't work with this module either.Move your block inside a
givenor aforloop. Can't specify postfix "when" modifier outside a "given"-
It is a limitation of this module that you can only use the
EXPR when EXPRsyntax inside agiven(not inside aforloop). And, of course, you couldn't ever use it outside of both.If your postfix
whenis inside a loop, convert it to awhenblock instead. Can't "continue" outside a "when" or "default"-
Calling a
continueto override the automatic "jump-out-of-the-surrounding-given" behaviour ofwhenanddefaultblocks only makes sense if you're actually inside awhenor adefault. However, your code is attempting to callcontinuesomewhere else.Move your
continueinside awhenor adefault. Can't "break" outside a "given"-
Calling a
breakto explicitly "jump-out-of-the-surrounding-given" only makes sense when you're inside agivenin the first place.Move your
breakinside agiven.Or, if you're trying to escape from a
whenin a loop, changebreaktonextorlast. 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
givenorwhen, 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 matchUse of uninitialized value in smartmatch-
You passed a value to
givenorwhenthat 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.