Missing Smart Match

I know that smart match is considered experimental at best and is likely going away at worst, but I hate that! In what world is:

if( grep $_ eq $scalar, @array ) { }

Better than this?

if( $scalar ~~ @array ) { }

The former doesn’t even shortcut. Some will say that I should use any like this:

use List::MoreUtils qw/any/;
if( {$scalar eq $_} @array ) { }

But any damn fool can see that it’s 2 lines of code instead of 2 characters. It’s not more readable. It’s just foolish. Some parts of smart match suck and I get that. So throw out those cases, not smart match itself.

[From my blog.]

24 Comments

I think I mentioned the drawbacks to your case in Rethinking smart matching. You don't know what's going to happen in that smartmatch until you actually get there and know what sort of values are in scalar and array. It might never do a string comparison.

Here's an example of a failure using the type of check you showed.
my $scalar = "1welp";
my @array = (1, "foo");
print "smartmatch is broken\n" if ($scalar ~~ @array);
I don't think anyone's happy that smart match couldn't be fixed properly, but if you remove all of the cases that "suck", there's basically nothing left.
sub contains { my $str = shift; $str eq $_ and return 1 for @_; !1 }

And then:

if ( contains $scalar, @array ) { ... }

Now your code is about as readable as it is with “smart” match. But now it’s predictable as well, something that cannot be said of a 27-way recursive runtime dispatch by operand type.

I'm with you JT. Graham's example just looks like a bug to me.

[cibola:~] cat >t.pl
use 5.14.0;
use Scalar::Util 'looks_like_number';
say looks_like_number "1welp" ? '==' : 'eq';
[cibola:~] perl t.pl
eq

Let's just fix the bugs. It's not like we don't have the technology.

Numeric comparison is done not because "1welp" looks like a number (it doesn't), but because 1 *is* a number, and therefore this rule is followed in the smartmatch dispatch table:

Left Right Description and pseudocode
===============================================================
Any Num numeric equality
like: Any == Num

The overarching point here, that this example demonstrates, is that ~~ does *not* do what you intuitively think it ought to do.

The problem is "just fix the bugs" will break things that rely on the currently documented behaviour.

Also, making it pluggable had allllll sorts of fun scoping issues, and nobody was able to find a sufficiently sane approach to dealing with that.

So, basically, other than "kill it, wait a couple years, bring it back", there's not really a good option here.

-- mst

> Numeric comparison is done not because "1welp" looks like a number (it doesn't), but because 1 *is* a number, ...

So change that. Both sides have to look like a number in order for == to apply.

But I feel like we're getting caught up too much in nitpicky details. I believe that the anti-smartmatch lobby's point is that smartmatch simply can't be fixed. (As opposed to, say, it's too much trouble to fix, or we could fix it but that would break backward compatibility too much.) I was just pointing out that Graham's example doesn't demonstrate anything that can't be fixed.

But perhaps I've misunderstood the point, in which case I apologize for my density. :-)

> The problem is "just fix the bugs" will break things that rely on the currently documented behaviour.

Well, that is an entirely different point, granted. But I have two thoughts on that.

  • This is why it's marked "experimental," right? So that we can change the behavior, and if people complain, we just go "well, we told you it was experimental!"

  • Fixing the bugs would fix things that rely on the documented behavior, absolutely. But removing the feature altogether would break all the things that use it. :-D So getting rid of smartmatch seems self-defeating by that argument.

Obviously I'm a fan of smartmatch, so I'm biased. But I think my points still have some value.

But mainly I just wanted to let JT know that he wasn't alone in his viewpoint. ;->

> Fixing the bugs would fix things ...

Would break things, I meant. I'm dyslexic today. :-)

If somebody can figure out a way to do pluggable ~~ that isn't even more confusing than what we currently have, the future is filled with possibilities.

So far, nobody has, so the status quo remains.

-- mst

if( $scalar ~~ @array ) { }

You're missing at least one line:

use experimental qw(smartmatch);

at which point you might as well use something more predictable, such as the contains() example above, or elem() from Data::Munge:


use Data::Munge qw(elem);
if(elem $scalar, \@array) { }

(this would also have the advantage that you don't need to add a comment explaining which of the many smartmatch behaviours was actually intended...)

Smartmatch behaviour has already changed at least once: 5.10.1 broke existing code, including the common case of "is $scalar in @array". Changing it again is unlikely to be successful without consensus as to the expected functionality - do you just want a "string in array" check?

If you can put together a proposal for how a smartmatching operator should work, that'd likely be a worthy discussion for the perl5.porters list. It'd need to be a bit more concrete than "fix the bugs" though ;)

Let’s just fix the bugs.

They aren’t bugs, they are the feature. If you consider them bugs and “fix” them, the feature is gone. The feature is simply unimplementable in Perl 5 due to the language’s set of semantics.

Namely, the whole point of smartmatch was that you give it some operands and it figures out how to compare them at the time of comparison.

This is impossible in Perl 5 because scalars are untyped, therefore operator must specify what operation you expect (string vs numeric). There is no way around this and no way of changing it (without making Perl 5 into a different language… such as Perl 6). The same reason is behind the recent split of the bitwise ops into two sets for explicitly stringy vs explicitly numeric operations. But trying to apply that solution to smartmatch merely produces barely-smart and variously hard-to-write/hard-to-read designs that lose all the attractive qualities of the smartmatch fantasy. You can’t fix the smartmatch reality without destroying it.

It’s not like we don't have the technology.

That’s like saying it’s not like Apple doesn’t have the technology to create a secure golden key. It’s ignorant of the underlying calculus and the way that that calculus renders the request self-contradictory.

Trying to use looks_like_number to "fix" my example means that "01" ~~ "1" would return true. That is currently not the case. There are other similar cases that are more subtle. It just can't be done properly with perl's untyped variables.

I agree with all the criticisms of smartmatch, but I still believe that "is-an-element-of" is a very frequent operation and would be useful as an operator on its own, without the multiplicity of possibilities in smartmatch.

At the moment, with List::MoreUtils you can do "any { $x eq $_ } @y", and there's some notion that in the future junctions would let you do things like "any(@y) eq $x" (and, incidentally, "any(@a) eq any(@b)" which would be cool). But while these are more flexible features, I think they're not that easy to understand at first glance

The "contains" sub is what I use at the moment (although I call mine "in"). But I think an operator would be more comprehensible. Something like "$a in @b" (stringwise comparison) or "$a ~> @b" (numeric comparison) would be easier to understand and much simpler to use for this common case.

I still believe that "is-an-element-of" is a very frequent operation and would be useful as an operator on its own

Probably. (Not sure, but I’m not disagreeing.) That doesn’t necessarily have anything to do with smartmatch though…

"any damn fool can see that it’s 2 lines of code instead of 2 characters."

That's, for me, the worst possible reason to make a syntax change. Typing is cheap, and while I prefer a good, perlish expression over a verbose, redundant one. I'd prefer typing two lines if that makes the code definite for both the maintainer and the machine.

"It’s not more readable."

yes it is. the use of List::MoreUtils points to a specific module API, and the use of eq means string comparison. Match that (no pun intended) with a maintainer needing to parse through all the smartmatch cases to figure out what is going on, or what may be happening in that line.

Perl6::Junction seems to address the "basic use cases" pretty well and readably, for a modest increase in typing effort.

It allows you to mix junctions, although you have to be careful of what you want. It also seems to be aware that equality isn't commutative any more when you mix all and any (as in all items here have to be equal to at least one item there, that is different from any item here has to be equal to all items there).

If you're worried by the Perl6:: in the name, Stevan Little's has got you covered: This is a solid module which you can use right now in your Perl 5 code..

Actually, there's already Syntax::Keyword::Junction which is a cleaned up fork that's explicitly extant to avoid the Perl6:: thing (and is maintained by the usual suspects).

-- mst

Also note that the 'any', 'all', 'notall', and 'none' interfaces are now in List::Util, which is shipped with core perl. (You should depend on version 1.33 to ensure you get an upgraded version on earlier perls that shipped an older version.)

The construction any(@a) eq $x is just not as comprehensible as $x in @a, even if it does the same thing.

I don't find
$x in @a
very comprehensible because it isn't obvious how it will be comparing elements. Will it use string equality or numeric equality? What if you need to pick one or the other?

Didn't know about Syntax::Keyword::Junction, that's what you get when you stop to the first module that fits the requirements :). It would be good to have some hint in the Perl6::Junctions docs, anyway, because it seems "more popular" with two reviews and a few more ++'s. Maybe even a new comment would do the trick?

The new subs in List::Util are OK but they (understandably) follow the syntax of the rest, which is less sugary. This seemed to be an important point in the OP. How much sugar you like is a matter of taste, of course.

I agree with Graham Knop and I consider any(@a) eq $x better because I can also say any(@a) == $x and any(@a) =~ $x, which is needed in Perl5 to avoid all smart match hoop jumping. Sometimes DWIM just expands to Detail What It Means.

$x in @m and $y ~> @n are analogous to and just as comprehensible as $x eq $m and $y == $n.

Leave a comment

About JT Smith

user-pic My little part in the greater Perl world.