Beware of the removal of when in Perl v5.28

[Although I haven’t seen an official notice besides a git commit that reverts the changes, by popular outcry these changes won’t be in v5.28. It’s not that they won’t happen but they won’t be in v5.28. People who depend on Perl should stay vigilant. My advice in the first paragraph stands—change is coming and we don’t know what it is yet.]

Perl v5.28 might do away with when—v5.27.7 already has. Don’t upgrade to v5.28 until you know you won’t be affected by this! This change doesn’t follow the normal Perl deprecation or experimental feature policy. If you are using given-when, stop doing that. If you aren’t using it, don’t start. And everyone should consider if a major change like this on such short notice is comfortable for them. It’s not a democracy but you can still let the core developers know which way you want your favorite language to go.

There are several knock-on consequences with the removal of when since so many broken features from v5.10 were entangled with each other. This is related to smart matching. I won’t go into the simplified smart matching and you can read the p5p thread for yourself. Blame Sawyer for kicking the hornets’ nest!

Remind yourself about when. You have to use it in something that topicalizes (sets the current element to $_). You can have multiple when blocks but the first one that runs its block stops that iteration because it has an implicit break at the end unless you use continue. You can have code between the when blocks. With no comparison operator in the condition it uses an implicit smart match against $_ (the topic):

use v5.10.1;

for( $ARGV[0] ) {
	when( $_ )         { say "$_ is true"; continue }
	when( not $_ % 2 ) { say "$_ is even"; continue }
	when( 137 )        { say "Number is 137"; continue }
	when( @array )     { say "In array branch" }
	}

You could rewrite this with if but notice how ugly that last if condition is. A smart match against an array smart matches the topic against items in the array. If one of those is true the smart match is true. The grep checks every element and returns a count; the smart match can stop midway. You could fix that with first from List::Util (a core module) but it’s still a bit uglier:

for( $ARGV[0] ) {
	if( $_ )         { say "$_ is true" }
	if( not $_ % 2 ) { say "$_ is even" }
	if( $_ == 137 )  { say "Always true branch" }
	my $value = $_;  # doubled $_!
	if( grep {$_ == $value} @array ) { say "In array branch"; next }
	#if( first {$_ == $value} @array ) { say "In array branch"; next }
	}

The job of when is now decomposed into two new keywords: whereso and whereis. Good luck explaining these in a way that anyone in your code review will remember. The keywords can still change and there’s no hardcore insistence on them. However, the words we normally use in speaking (“if” and “when”) are either taken or banished. I have my own suggestion later.

The whereso always tests its expression for a simple true or false. It does not impose any smart matching. Instead of an implicit break it has an implicit next (which is the same thing). break is banished with when because we never needed it (even if it was a C keyword):

use v5.27.7;
no warnings qw(experimental);

my @array = qw( 1 3 7 137 );

for( $ARGV[0] ) {
	whereso( $_ )         { say "$_ is true"; continue }
	whereso( not $_ % 2 ) { say "$_ is even"; continue }
	whereso( 137 )        { say "Always true"; continue }
	whereso( @array )     { say "In array branch" }
	}

In that last whereso the test is the number of elements in the array. It’s just an array in scalar context. If there’s at least one element its true. If I don’t want that I’m back to the ugly grep. Perl solves that ugly grep with a confusing (so far) smart match. I think many people would be satisfied with a new comparator in (as in Python) that evaluated to true or false if $_ was in the list. I don’t think we need it but it’s a request (and use) I see often.

I don’t like this keyword and I think most of the people who chimed in on the p5p thread hated it in the same way. “whereso” is an existing English word although I can’t recall if I’ve ever heard it in a modern context, I don’t think I could properly construct a sentence with it, and my spellchecker doesn’t recognize it. Perl 6 also uses so to boolify something; there’s a tenuous connection between the colloquial “so” as a logical connector and it’s use in Perl 6. I think it’s too clever for its own sake and sure to annoy the workaday programmer. I’m not sure how whereso is better than if other than the implicit next. But the keyword is not set in stone and I’m betting it will change before v5.28.

The smartmatching version is whereis. Now the array test checks if $_ is an element of the array. In the 137 case it checks that the value is exactly that:

use v5.27.7;
no warnings qw(experimental);

my @array = qw( 1 3 7 137 );

for( $ARGV[0] ) {
	whereis( $_ )         { say "$_ is true"; continue }
	whereis( not $_ % 2 ) { say "$_ is even"; continue }
	whereis( 137 )        { say "Is 137"; continue }
	whereis( @array )     { say "In array branch" }
	}

Notice that this works without explicitly enabling an experimental feature although I do get experimental warnings. I think this is an oversight because it reuses the switch feature name which is loaded automatically when you use (not require) v5.10 or above. It should get a new feature name but I’m guessing that the wide-ranging scope of the change can’t co-exist with the old ways. I’m not happy that this hasn’t gone through the formal experimental feature process.

Perl v5.27.7 has an experimental feature that unexpectedly loads. I could turn off the switch feature (no feature qw(switch)) but I have no way to get back to its old behavior. This is quite unfriendly and breaks the social contract and comfort that perlpolicy was supposed to provide to enterprise users. This trust, once broken, will be almost impossible to regain. And, it’s easy to foresee future changes to this feature because that’s more probable that it will miss something or users won’t like it simply because so many new features have had that outcome.

I quite admire Python’s Enhancement Proposals and the visibility they give to the wider community. Hat tip to Leon Timmermans for posting on blogs.perl.org about the already merged change. Perhaps part of perl5porter’s policy for experimental changes should include fully fleshed out discussions posted somewhere (manywheres) that have a wide audience beyond core developers. Making the workaday programmer pay attention to the minutiae of daily perl5porters to catch these things isn’t reasonable. However, remember what Mark Jason Dominus said about the Perl 6 RFC process: serious applicants only!

Perl (either current flavor) make us think we could have natural language programming (even if we didn’t) and now new language features try to be linguistically clever even though that’s one of the biggest complaints about my favorite language. The debacle of given and its lexical $_ (removed in v5.24) pushed that a bit too far with implied magic and inadequate testing.

As part of this change given is now just a loop that happens to run exactly one time. Even though the keyword v5.10 chose was not switch (as in C), that’s how people seem to pronounce given. The language people want to use and what Perl actually uses doesn’t match.

I’ve already written that people should use for instead even though p5p fixed the topicalizing bug that led to that advice. With this shift in the language we should get rid of given too. It’s likely at the same spots you’ll already have to modify with this change.

As long as anyone tries to be clever with the natural language words most people are not going to remember which one smart matches. Many other interesting words are already functions in modules (whereis in Unix::Whereis, where in MooseX::Types and PDL, and with in various modules). The core developers could have long conversations and eventually settle on something that they like, but all those conversations will have to be replayed for each programmer on their first encounter with the new words. We shouldn’t settle for words we like; we should use words our audience will like and understand.

I’m in the camp that thinks the keywords should mostly explain themselves. Why not call the one that smart matches smartmatch and let if with an explicit loop control do the rest? I’m sure I’ll remember which one of these does what:

for( $ARGV[0] ) {
	if( $_ )             { say "$_ is true"; next }
	smartmatch( @array ) { say "In array branch"  }
	}

Not only that, this reads better in English. It’s an imperative sentence that tells me exactly what to do. Heck, even a smartif will do. This could be a new feature with a new name that doesn’t disturb the deprecated when, break, and given that would disappear v5.32. That’s a lot more than five months notice of code breaking changes.

If your system automatically upgrades to the latest perl, I suggest that that you separately install the perl you want to use and target that (Item 110. Compile and install your own perls.). I’ve written about Choose the right perl version for you (which I might have to update to recommend not using v5.28) and I’ve noted that Apple recommends that you don’t use their distributed perl. It’s easy to compile a development version so you can test these things on your code base before major changes make it into a maintenance release. I’ve also explained how to tell the different between maintenance and development releases.

9 thoughts on “Beware of the removal of when in Perl v5.28”

  1. Can we just give in and do what every other language does? Switch and case. No ambiguity there. It’s this kind of bizzaro all of a sudden crazy behavior that pissed of Guido which led to our being booted from the top of the scripting hill. Can we ever learn? Larry, BDFL, step in here and save us from looking so stupid

  2. They tried that with given and when. But people don’t want switch and case (that would be a limited subset of nswitch from Switch::Plain). Everyone wants it to match in a different way which is how we ended up with the mess of old ‘when’ and old smartmatch where you have to read a list of 20+ rules to figure out what it will even do (https://metacpan.org/pod/perlsyn#Experimental-Details-on-given-and-when then https://metacpan.org/pod/perlop#Smartmatch-Operator).

  3. Wow. Agreed, whereis and whereso is very confusing. Vastly prefer your suggestions. Offhand, smartif seems nice.

  4. I guess this is not the right place for this discussion, but I was intrigued by your counter-suggestions.

    I definitely like that “if” should be “if” and “smartmatch” is quite clear about what it does. I have an issue with “for” in that it bears a “cognitive overload” to make topicalization in a place where someone from outside the Perl echo camber would expect to read “switch”. It reminds me of the minimalistic approach to OOP or “eval” for handling exceptions: “is there something preventing it to you?” (ITSPITY).

    The default behavior of “break”ing (actually, “next”-ing?) in “smartmatch” is somehow annoying. I can only imagine the original intent in “when” was to fix an annoyance when using C’s switch (we all got biten by forgetting a break, I guess), but for an old-timer like me it’s again new cognitive load. On top of that, I have to remember that I should use “continue” inside “smartmatch”, but use “next” inside “if”. This IMHO increases the chances of being biten in some way, much more than we have in C.

  5. While it is true that Perl is not a democracy, neither should it be the wild west.

    As Sean Connery put it so eloquently in The Hunt for Red October. “The captain seems to think you’re some sort of cowboy (Buckaroo).”

    I agree with Mathew’s appeal for Larry to step in and save us from looking so stupid.

  6. Drat, I read this on blogs.perl.org and missed the last part of it.

    Using “if” and “smartmatch” is an absolutely sensible solution to the issue. How about we:

    1) add those in 5.28
    2) deprecate given/when 5.28
    3) remove given/when 5.30.

    Wouldn’t that make more sense?

    Look, we as a community take enough abuse for using a language that is seen, rightfully or not, as line noise looking and regexp happy, among its supposed “sins”. I don’t see the need for giving any more ammunition to the five Pythonistas that sit with me at my $job. Can we stop shooting ourselves in the head (not foot) with the ‘ooh-we-are-such-clever-comp-sci-purists’ gun?

  7. Yes, this breaking change seems quite troublesome. There is more than one way to do the right thing.

    It seems to me that smartmatch works obviously for a few cases: strings, numbers, undef, and regexes.

        $_ ~~ "string" # $_ eq "string"
        $_ ~~ 137        # $_ == 137
        $_ ~~ undef    # not defined($_)
        $_ ~~ /regex/  # $_ =~ /regex/
    

    When you include arrays, hashes, functions and statements, it gets confusing.

    Why not allow smartmatching for the unambiguous subset, and create some functions for the places where it gets confusing.

        $_ ~~ within(@array)  # searches for value,
        $_ ~~ within(%hash)   # not key (use exists() for key)
    
        $_ ~~ so( expression ) # eval (or do) expression for truth
    

    So when only operates on values that smartmatch can accommodate, and you have some other operators:

        so( $_ ) { ... }              # $_ is true
        so( not $_ % 2 ) { ... } # $_ is even
        when( 137 ) { ... }       # $_ == 137
        within(@array) { ... }  # $_ is within @array
    

    Of course, so is simply an if with an implied last at the end. If you want to put continue at the end, just use if. The within branch would be syntactic sugar for so( within(@array) ) { ... } There are probably a few other functions that could be composed to create the various views of the proper smartmatch matrix for the complex cases (@ ~~ @, @ ~~ %, …).

    This is all my brain thinking out loud, so I’d definitely appreciate some thoughts and opinions here.

  8. I agree that the “old” smart match does work reasonably for the few cases that Bob K mentions.

    “whereis” sounds like it should return the index of the matching element of an array.

    “whereso” means wherever, so I would expect it to return true if the topic appears anywhere in an array.

    “so” as an alternative to “if” makes no sense to me.

  9. Of course cperl will not follow this new symptom of ongoing p5p destruction, and rather implement the proper Perl6 design of given when with it’s compile-time possibilities and a properly scoped $_. Esp. nested when, types and destructuring are the killer features in cperl and perl6 given when, esp. for multimethod matching on signatures. Good luck with that with p5p perl.

    I’m just not sure about the * used as new topic, and not _ (or @_ for list context and $_ for scalar context)

Comments are closed.