develooper Front page | perl.perl5.porters | Postings from August 2012

Why smartmatch

Thread Next
David Golden
August 24, 2012 06:54
Why smartmatch
Message ID:
Just to step back, I thought I'd mention why I think smartmatch is a useful
feature given all the ambiguity we're discussing.

(1) It's a generic "test" operator

It's effectively $rhs->test($lhs). It makes it easier to work with a
collection of test "objects" without having to interrogate each one to know
how to resolve the test.

For some built-ins, the nature of 'test' is clear. For strings/numbers,
it's not, and the argument is really (a) do we pick one interpretation, (b)
do we try to guess based on $lhs or (c) do we refuse  to smartmatch
(possibly with undef as a signal).

It works particularly nicely with junctions, where we can express things
like this:

    if ( $lhs ~~ all( @list_of_tests ) ) { ... }

Instead of the much more verbose:

  my $pass;
  for my $t ( @list_of_tests ) {
    if (ref $ eq 'CODE') {
        $pass++ if $t->($lhs);
    elsif (ref $t eq 'Regexp') {
       $pass++ if $lhs =~ $t;
    ... # for all other possibly types of tests we can handle
  if ( $pass == @list_of_tests ) {

In that long-hand form, if ref($t) is q{}, then we are forced to explicitly
choose whether to do == or eq or try to guess based on looks_like_number or
something else.

Just because smartmatch can't handle that case isn't too damning to me --
it still lets me avoid specifying all the things that *aren't* ambiguous.

[Frankly, I think we shot ourselves in the foot by calling it *smart*match
because we set expectations we can't live up to.  If we'd called it the
"generic match" operator or something, we'd have been on safer ground.]

So, if we're not careful to avoid ambiguous scalars in our list of tests,
to be safe we need to clarify the desired fallback in the junction case:

    if ( $lhs ~~ all( map { any($_, "$_") } @list_of_tests )  ) { ... }

That's still *much* easier than the longhand construct I wrote above.  Is
it perfectly elegant? No, but this is the price we occasionally must pay
for dynamic typing.

(2) It allows less verbose "switch" constructs with "when"

Instead of a long chain like this:

    if ( $lhs == 0 ) { ... }
    elsif ( $lhs == 1 ) { ... }
    elsif ( $lhs == 2 ) { ... }
    else { ... }

We get to avoid writing "$lhs ==" in every clause by using the generic
match instead:

    when (0) { ... }
    when (1) { ... }
    when (2) { ... }
    default { ... }

That's less verbose, so easier to read.

The "problem" with "when" is that it inherits the generic match problem of

    when ($rhs) { ... } # what if $rhs is a plain scalar?

But we've proposed allowing an explicit test to serve the same
block-with-break function:

    when {$lhs == $rhs} { ... }

That gives me an idea – maybe C<< when($rhs) { ... } >> should be
discouraged somehow.

The most severe way to do that would be to disallow *any* scalar variable
and only allow literals (including qr//) or "\&foo" references.

The gentlest way to do that would be to warn if a scalar $rhs in C<<
when($rhs) {...} >> is not a reference.

That would be orthogonal to the question of whether C<< $lhs ~~ $rhs >>
should warn if $rhs is ambiguous -- this would be specific to discouraging
when($rhs) as a construct so as to encourage explicit C<< when { $lhs ==
$rhs } { ... } >> or more generically C<< when ( any( $rhs, 0+$rhs ) ) {
... } >> instead.


*David Golden* <>
*Take back your inbox!* →
Twitter/IRC: @xdg

Thread Next Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at | Group listing | About