Beefy Boxes and Bandwidth Generously Provided by pair Networks
The stupid question is the question not asked
 
PerlMonks  

Smartmatch alternatives

by cavac (Chaplain)
on Dec 17, 2013 at 12:52 UTC ( #1067462=perlquestion: print w/ replies, xml ) Need Help??
cavac has asked for the wisdom of the Perl Monks concerning the following question:

Hi!

Since 5.18, perl is throwing warnings about smartmatch ans well as given/when.

Mostly i use the ~~ smartmatch operator to test if a scalar exists within an array. Can use the exists() function either, because it's use on arrays is deprecated as well.

Is there a proper, future-proof alternative that does not require me to sprinkle foreach test loops all over the place?

What alternatives are there to given/when?

Personal remark: The perl developers want to axe smartmatch because it is 'too confusing', apparently. I agree that there are some edge cases, but it looks mostly OK to me. What i really, really find confusing, misleading, ugly, hard to follow when reading code and downright stupid is the implicit use of $_. Now THAT is a 'feature' that should have died instead - a long, horribly painful death including hollywood-like special effects would be preferred if that can be arranged.
"I know what i'm doing! Look, what could possibly go wrong? All i have to pull this lever like so, and then press this button here like ArghhhhhaaAaAAAaaagraaaAAaa!!!"

Comment on Smartmatch alternatives
Re: Smartmatch alternatives
by Athanasius (Monsignor) on Dec 17, 2013 at 13:06 UTC

    From the documentation for Scalar::In:

    This module was written because the smartmatch operator ~~ was deprecated as experimental.

    But the syntax is less elegant:

    22:58 >perl -MScalar::In -wE "my @x = qw( Fred Wilma Barney Betty ); s +ay 'Found Wilma' if string_in(@x, 'Wilma');" Found Wilma 23:00 >

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

Re: Smartmatch alternatives
by BrowserUk (Pope) on Dec 17, 2013 at 13:10 UTC
    Can['t] use the exists() function either, because it's use on arrays is deprecated as well.

    It wouldn't help any way. exists on an array only tests if the array element at a given index exists (ie. has not been deleted), and has nothing to do with what it contains.

    Is there a proper, future-proof alternative that does not require me to sprinkle foreach test loops all over the place?

    Like this:

    if( grep /^\Q$scalar$/, @array ) { ## it's in there }

    Of course, that uses the implicit $_; so perhaps you'd prefer:

    if( grep $_ eq $scalar, @array ) { ## it's in there }

    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Smartmatch alternatives
by Eily (Hermit) on Dec 17, 2013 at 13:18 UTC

    TIMTOWTDI.

    I often use the grep solution $contained = grep { $_ eq $searched } @array; or even grep /^\Q$searched$/,  @array

    If you are going to search for several values in the same @array, a hash may be a good idea (and unless order is important, or the same value can appear more than once, you could just use a hash all the way):

    my %hash = map { $_ => 1 } @array; for $search (@listOfScalars) { say '@array contains '.$_ if exists $hash{$_}; }

    Or there's also the first_index function from List::AllUtils. Which is similar to the grep solution, except it stops at the first match.

Re: Smartmatch alternatives
by dcmertens (Beadle) on Dec 17, 2013 at 13:43 UTC

    I believe the answer to your question is "no". At least, I can't think of a clean looking way using normal Perl syntax to do it, unless you are willing to use a function call. On the other hand, if you are willing to use a function call, your code becomes self-documenting. In an effort to aggravate BrowserUK, I present both a normal function and a pipelining function ref for your consideration:

    use strict; use warnings; use Test::More; my @data = ('1.0', 'hello', 0); sub is_string_in { my $number_to_find = shift; return 0 < (grep { $_ eq $number_to_find } @_); } my $is_string_in = \&is_string_in; ok('hello'->$is_string_in(@data)); ok(is_string_in('hello' => @data)); ok(not 1->$is_string_in(@data)); ok(not is_string_in(1 => @data)); ok(0->$is_string_in(@data)); ok(is_string_in(0 => @data)); use Scalar::Util qw(looks_like_number); sub is_number_in { my $number_to_find = shift; return unless looks_like_number($number_to_find); return 0 < (grep { looks_like_number($_) and $_ == $number_to_find + } @_); } my $is_number_in = \&is_number_in; ok(not 'hello'->$is_number_in(@data)); ok(not is_number_in('hello', @data)); ok(1->$is_number_in(@data)); ok(is_number_in(1, @data)); ok(0->$is_number_in(@data)); ok(is_number_in(0, @data)); done_testing;

      Or, avoiding full grep:

      my $contains = sub { $_ eq $_[1] && return 1 for @{$_[0]}; 0 }; for (qw( Lisa Bart Fred LanX )) { [qw( Fred Wilma Barney Betty )]->$contains($_) and print; }

      Though efficiency probably shouldn't enter the discussion here. Performing a key search on array, where you could have used a hash—bad decision. Another reason to dislike the smartmatch: it promotes bad design.

        It makes sense to use hash keys if all the items being searched through are strings. If some of them are regexps, coderefs, objects with overloading, etc., then a hash is not an option.

        use Moops; class Cow :rw { has name => (default => 'Ermintrude') }; say Cow->new->name
Re: Smartmatch alternatives
by tobyink (Abbot) on Dec 17, 2013 at 14:16 UTC

    I needed just such a replacement when I went through my own distributions replacing ~~, so I wrote match::simple. It's a less confusing smart match operator. Syntax:

    use match::simple; foobar() if $needle |M| $haystack;

    The |M| bit is a pseudo-operator (a trick accomplished using somewhat crazy overloading of the bitwise-or operator). If you'd prefer to use it without all the crazy, match::simple also exports a match function:

    use match::simple 'match'; foobar() if match $needle, $haystack;

    match::simple has simpler, easier to remember rules than ~~, but it also comes bundled with match::smart which uses the same rules as ~~.

    For given and when you could try Switcheroo or Switch::Plain, or dispatch tables, or long lists of elsif blocks.

    use Moops; class Cow :rw { has name => (default => 'Ermintrude') }; say Cow->new->name
      Looks nice.

      Does the overloaded operator have any side effects on normal bitwise operations?
      "I know what i'm doing! Look, what could possibly go wrong? All i have to pull this lever like so, and then press this button here like ArghhhhhaaAaAAAaaagraaaAAaa!!!"

        It shouldn't do, unless your smart match operands also happen to be objects that override bitwise-or. (For example, Math::BigInt does.)

        The match($a, $b) function really is saner though. And faster for that matter. (Though the real smartmatch operator beats each hands down!)

        use v5.18; use match::simple -all; use Benchmark qw( cmpthese ); $::A = 3; $::B = [1 .. 5]; cmpthese(-1, { 'match' => q[ match($::A, $::B) ], 'M' => q[ $::A |M| $::B ], '~~' => q[ no warnings 'experimental::smartmatch'; $::A ~~ $::B + ], }); __END__ Rate M match ~~ M 11487/s -- -69% -99% match 36540/s 218% -- -97% ~~ 1420284/s 12265% 3787% --

        Anybody willing to provide a patch for an XS implementation of match::simple::match() would be likely to have it accepted. ;-)

        use Moops; class Cow :rw { has name => (default => 'Ermintrude') }; say Cow->new->name
Re: Smartmatch alternatives
by taint (Chaplain) on Dec 17, 2013 at 14:48 UTC
    I dunno. I always thought the Smartmatch scheme was rather elegant. Confusing? No.

    Hard as I try, I can't always be the brightest crayon in the box. But wouldn't it be possible to use Smartmatch to "nuke" the depreciated warnings?

    --Chris

    UPDATE I should probably add, that I realize that that would incur a bit of overhead.
    Yes. What say about me, is true.
    
Re: Smartmatch alternatives (updated)
by LanX (Canon) on Dec 17, 2013 at 15:25 UTC
    Before smartmatch was introduced, the answer was to use grep in boolean context, cause it in scalar context it returns the grep-count.

    And if speed matters take any from List::MoreUtils . Same interface like grep but immediately returns 1 after first match.

    DB<107> @a=(a..f)x10 DB<108> use List::MoreUtils qw/any/ DB<109> grep { "a" eq $_ } @a => ("a", "a", "a", "a", "a", "a", "a", "a", "a", "a") DB<110> 0+ grep { "a" eq $_ } @a => 10 DB<111> 0+ any { "a" eq $_ } @a => 1

    update

    > want to axe smartmatch because it is too confusing,

    The first documentation of smartmatch came with a ridiculous long table which was very hard to remember and IIRC not commutative (i.e A ~~ B wasn't B ~~ A).

    ( see also http://stackoverflow.com/questions/5279917/why-doesnt-this-smart-match-work )

    My (a?) catch phrase was "I'm not smart enough for smart match".

    Attempts to fix this destroyed the faith in it's reliability.

    It's rather paradox if you "like smartmatch" with all implicit mechanisms but at the same time you state "downright stupid is the implicit use of $_" ???

    We certainly need a better way to introduce and test no features...

    well actually we have do have use feature!!!

    I wouldn't object if say was made standard ...

    Cheers Rolf

    ( addicted to the Perl Programming Language)

Re: Smartmatch alternatives
by davido (Archbishop) on Dec 17, 2013 at 15:26 UTC

    My problem with smartmatch is that it tries to replace several more explicit constructs. I prefer to just use the more explicit one, leaving less to heuristics. grep would be fine except for one problem; it does an exhaustive search. There's no (sane) way to tell it to stop searching after the first element meets the search criteria. I don't know if smartmatch stops after the first satisfying condition.

    List::Util is a core Perl module. We should consider the functions it offers to be almost as much "first class citizens" as grep. ...except that for documentation we have to look at the module's POD rather than perldoc -f. One of its functions is any. This function stops searching immediately, and returns a true value as soon as one item meets the search criteria. And its name conveys exactly what it does; it's not searching for a whole bunch of things (like grep), it's just going to tell us whether any of the elements meet the criteria.

    my $found = any { $_ eq 'something' } @array_of_strings; my $found = any { /\bsomething\b/ } @array_of_strings; my $found = any { $_ == 42 } @array_of_numbers; my $found = any { $_->isa('obj_type') } @array; my $found = any { $_->can('tupitar') } @array;

    It's not as succinct as $something ~~ @array, but I can look at it and immediately know the semantics under which $something is being tested, whereas with smartmatch I have to open up perlop and check that my intuition isn't playing tricks on me.


    Dave

      Agreed, but any is in List::MoreUtils (which isn't core. =)

      And XS and it's alternative pure Perl implementation was buggy last time I looked into it.

      update

      oh did you mean first ? :)

      use List::Util qw(first max maxstr min minstr reduce shuffl +e sum); first BLOCK LIST ... "first" returns the first element where the result from BLOCK is a true value.

      update

      grr @%$ !!!...

      DB<121> @a=0..10 => (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) DB<122> first { "0" eq $_ } @a => 0

      Cheers Rolf

      ( addicted to the Perl Programming Language)

        No, I meant List::Util::any.

        $ perl -MList::Util -E 'say "Yes" if List::Util::any { $_ == 42 } @{[q +w/1 3 42 5/]}' Yes

        It shows up in both modules. There may be subtle, undocumented differences.


        Dave

        Well, L'U'first returns undef "[li]f BLOCK never returns true or LIST was empty" (as of version 1.32), so need to do check further if value is defined.

      I agree with:

      "My problem with smartmatch is that it tries to replace several more explicit constructs. I prefer to just use the more explicit one, leaving less to heuristics."

      ... in most cases. Don't use ~~ when ==, eq or =~ would do a better job.

      However, there is one place I really miss smartmatch. Let's take for example RDF::RDB2RDF; you don't need to know all the details of what it does; suffice to say that it's got user-configurable objects which process a database. One of the options that can be configured is which tables (if any) should be skipped. I liked being able to say "set the ignore_tables option to anything that can be used as the right hand side of a smart match".

      So if they wanted to skip one particular table, they could do ignore_tables => "foo", if they had a list of tables to skip, then ignore_tables => \@private, or they could use a regexp ignore_tables => qr{^priv}, or if they had more complex requirements, ignore_tables => sub {...}.

      Smartmatch made implementing that really easy. And it's that aspect of smart match that made me want to write a stable replacement for it.

      use Moops; class Cow :rw { has name => (default => 'Ermintrude') }; say Cow->new->name

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1067462]
Approved by marto
Front-paged by boftx
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others scrutinizing the Monastery: (12)
As of 2014-07-23 06:53 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    My favorite superfluous repetitious redundant duplicative phrase is:









    Results (135 votes), past polls