stevieb has asked for the wisdom of the Perl Monks concerning the following question:

In Python, I can do if var in list, where list in Python is the same as an array in Perl.

I got to thinking, what if I wanted to create an in operator just to play around? How would one get the LHS (in the above case var) so I can use it in whatever implementation I decide to use? Can someone recommend the appropriate documentation to someone who wants to start learning about perl on a little deeper level?

-stevieb

Replies are listed 'Best First'.
Re: Create a new operator, get LHS
by Eily (Monsignor) on Aug 25, 2015 at 16:58 UTC

    Unless you use source filters (which I do not recommend mainly because of the complexity of parsing code, and the fact that the code perl compiles is not the same as the one you wrote) or design your own version of perl, you can't create a new operator.

    You can however overload an existing one, and do something like $var ~~ @list where $var would have to be a blessed reference. $var ~~ @list may actually already do what you want if you use the experimental Smartmatch Operator (but that thing is so complex it's probably a bad idea to use it).

    Or, if you're OK with the fact that in @list, $var is close enough to $var in @list you could do something like:

    use v5.20; use List::Util qw(any none); # Allows the first arg to be an array or a ref # If the second arg is absent, $_ is used instead sub in:prototype(+_) { any { $_[1] eq $_ } @{ $_[0] } } sub notin:prototype(+_) { none { $_[1] eq $_ } @{ $_[0] } } my @list = 'A'..'Z'; my $in = 'C'; say 'IN' if in @list, $in; $_ = '1'; say 'OUT' if notin \@list; say 'NOPE' if in @list, '2'; say 'NOPE' if notin @list, 'D';
    TIMTOWTDI of course, with or without List::Util, grep or Prototypes, that's just an example to show what can be done (and any and none are more effective than grep because they stop at the first match)

    Edit: updated the code because I had written { $_[1] } instead of { $_[1] eq $_ } and added some links

    Edit: And of course, unless order is important, the more effective way to check that an element is present in a set is to use a hash instead of an array.

Re: Create a new operator, get LHS
by SuicideJunkie (Vicar) on Aug 25, 2015 at 16:51 UTC

    You mean like: if (grep {$_ eq $var} @list) where the var part can be an arbitrarily complex function?

    I don't know about the guts, but List::Utils has a bunch of stuff that does similar things you might want to look at.

      The var part can't be an arbitrarily complex expression, as it gets computed for each element of @list:

      perl -E 'say q<Nope> unless grep { $_ eq $c++ } 1..10; say $c' Nope 10

        Although it is a bit silly with side effects, that code works fine as you demonstrated.

        It isn't very complicated, but arbitrarily complex includes 'simple'. You're also free to start an OpenGL window, slam down some matrix transforms based on $_ and use that result as the grep condition.

Re: Create a new operator, get LHS
by RonW (Parson) on Aug 25, 2015 at 16:59 UTC
Re: Create a new operator, get LHS ( prototype )
by LanX (Saint) on Aug 25, 2015 at 19:13 UTC
    > so I can use it in whatever implementation I decide to use?

    Could you please be more specific and provide some use cases?

    Binary operators are difficult even with overloading, but something like

    from {List} $scalar

    could be easily done with prototypes. ( i.e. sub from (&$) {...} )

    Btw: how does Python compare? == or eq ?

    Cheers Rolf
    (addicted to the Perl Programming Language and ☆☆☆☆ :)
    Je suis Charlie!

Re: Create a new operator, get LHS
by Laurent_R (Canon) on Aug 25, 2015 at 20:50 UTC
    Hm, not sure of what you are trying to do, but assuming I understood, one possibility might be using the smart match:
    DB<38> use v5.10.0; DB<39> $var = 7; DB<40> @a = qw/ 4 6 7 9 3/; DB<41> print "yes" if $var ~~ @a; yes
    (I know it has been deprecated and I would certainly not use that for production code that is supposed to last, but there are cases where you might want to use it nonetheless because it is a short-life project, for example.)

    grep is an alternative:

    print "yes" if grep { $var == $_} @a;
    Or you could use the any function of List::Util module (if your version of that module is recent enough, version 1.33 or above, I think), with just about the same syntax as the grep line above. This should often be faster than grep because any will short-circuit as soon as it has found a matching element, and grep won't.

    If your version of List::Util is too old, then try any in List::MoreUtils, any has been around with it for longer. Or you could implement it yourself with List::Util's reduce function (the reduce documentation shows basically how to do it).

    With reduce, you could even implement an iterator-based lazy version of grep that will short-circuit for you (but will usually not be faster, except when the list is long and the item found early in the list).

    Or you might turn to Perl 6. ;-)

    But all the above might just be off-topic if I misunderstood your requirement. Please explain further.

Re: Create a new operator, get LHS
by Jenda (Abbot) on Aug 26, 2015 at 09:14 UTC

    If you wish for a if ($var in @list) then the @list ought most probably be a hash and the statement should be if (exists $list{$var}).

    Jenda
    Enoch was right!
    Enjoy the last years of Rome.

Re: Create a new operator, get LHS
by shmem (Chancellor) on Aug 26, 2015 at 10:51 UTC
    How would one get the LHS (in the above case var) so I can use it in whatever implementation I decide to use?

    If you want a real infix in operator, you will have to hack the perl parser, namely perly.y, toke.c and related files.

    You could overload an existing infix operator with a in subroutine, but in your programs you would still use the overloaded operator (not in), which then just behaves as in, and your $var has to be blessed to that end.

    There's a notation which at least looks similar to infix - method call:

    $var->in(@list)

    - but $var has to be blessed here, too, into the package into which the in method has been compiled:

    package in { sub new { bless \$_[1],$_[0] } sub in { my $t = shift; scalar grep { $$t eq $_ } @_ } }; my $var = in->new(7); print "yup\n" if $var->in( 0 .. 8 ); __END__ yup

    Not very tingly. Sorry about that.

    perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
      > There here's a notation which at least looks similar to infix - method call:

      > $var->in(@list)

      > ...

      > Not very tingly. Sorry about that.

      so why not

      $var->$in(@list) ??

      use warnings; use strict; my $in = sub { my $x=shift; return scalar grep {$x eq $_} @_ }; if ( 5 ->$in (0..9) ) { print "bin ich drin oder was?"; } if ( 'x' ->$in (0..9) ) { print "mennoooooo... :("; }

      /usr/bin/perl -w /tmp/in.pl bin ich drin oder was? Compilation finished at Wed Aug 26 15:42:03
      (diabolic laughter)

      Cheers Rolf
      (addicted to the Perl Programming Language and ☆☆☆☆ :)
      Je suis Charlie!

        (diabolic laughter)

        /me concurs. Didn't know that was even possible. Thanks, LanX. Learned something new today.

        perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
Re: Create a new operator, get LHS (Sub::Infix)
by LanX (Saint) on Aug 26, 2015 at 14:09 UTC
Re: Create a new operator, get LHS (indirect method + autoload)
by LanX (Saint) on Aug 26, 2015 at 11:45 UTC
    just for fun, only for strings and not really meant for serious use =)

    use warnings; use strict; # true x in 'v'..'z'; # false xx in 'v'..'z'; # but variables aren't barewords my $x='x'; in->$x('v'..'z'); package in; use Data::Dump qw/pp/; our $AUTOLOAD; sub AUTOLOAD { (my $LHS= $AUTOLOAD) =~ s/in:://; #print pp [$LHS, @_]; my $res = grep {$LHS eq $_ } @_; my $not =""; $not =' not' unless $res; warn "$LHS$not @_"; return $res; }

    out

    /usr/bin/perl -w /tmp/in.pl x in v w x y z at /tmp/in.pl line 35. xx not in v w x y z at /tmp/in.pl line 35. x in v w x y z at /tmp/in.pl line 35. Compilation finished at Wed Aug 26 13:51:16

    Cheers Rolf
    (addicted to the Perl Programming Language and ☆☆☆☆ :)
    Je suis Charlie!

      Nice :). Your post inspired me to try something with Lvalue subroutines, I think this works quite well:

      use v5.20; use Operator qw{in}; use Variables qw{X Y Z}; my @list = 1..10; (X, Y) = (1, 11); say X." in @list" if X in @list; say Y." not in @list" unless Y in \@list; Z = 'A'; say Z." in list" if Z in ['A'..'C'];
      package Variables; use v5.20; our %V; sub import { shift; my $package = caller; no strict "refs"; for my $var (@_) { *{"$package::$var"} = sub:lvalue { @_ ? $_[0]->($V{$var}) : $V{$va +r} }; } } 1;
      package Operator; use v5.20; use List::Util qw{any none}; use Exporter 'import'; our @EXPORT_OK = 'in'; sub in(+) { my $list = shift; return sub { any { $_ eq $_[0] } @$list }; } 1;
      1 in 1 2 3 4 5 6 7 8 9 10 12 not in 1 2 3 4 5 6 7 8 9 10 A in list

      Edit: fixed the broken link for lvalue subroutines thanks to shmem.

      Edit: $V{$var} is what I meant, not $V::{$var}

        I like the idea of realizing variables as lvalue functions which evaluate passed code refs!

        Thumbs up. :)

        Not for normal Perl code, but maybe within internal DSLs which are limited to certain code areas like blocks.

        Not sure why you use a hash where a closure var could do...

        Cheers Rolf
        (addicted to the Perl Programming Language and ☆☆☆☆ :)
        Je suis Charlie!

Re: Create a new operator, get LHS
by lonewolf28 (Beadle) on Aug 26, 2015 at 23:44 UTC

    If i'm not wrong you can do that with smart matching:

    Smartmatch Operator
    if ( $var ~~ @whatever ) { #do something }

    Update: However, when you do a string check comparison then I don't think smart matching would work. For example:

    #!/usr/bin/python some_var = 'sa' words = 'same' if some_var in words: print "yes"

    In python you can even step through a string like a list and index it. That would not be possible in Perl.

Re: Create a new operator, get LHS
by raiph (Deacon) on Sep 11, 2015 at 15:50 UTC
    »»» This post is about alpha status Perl 6, not rock solid Perl 5 «««

    In Perl 6, operators are defined by subs. Operator subs have to have an appropriate name and signature. They get automatically added to the Perl 6 grammar in the current lexical scope as soon as their name and signature have been successfully parsed.

    For example, infix operators have a name of the form 'infix:<op>' and two parameters. So the code:

    sub infix:<foo> ($lhs, $rhs) { dd $lhs }; sub infix:<in> ($lhs, @rhs) { dd @rhs }; 1 foo 2; 3 in [3,4];
    prints (via `dd`, the built in datadumper):
    $lhs = 1 @rhs = [3, 4]

    For more details start at the Defining Operators section in the official end user doc.