http://www.perlmonks.org?node_id=734774

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

I'd rather do this:
say $shop->ShopperDueDate->andand->day_name();
or
say $shop->ShopperDueDate->?->day_name();
or even
say $shop->ShopperDueDate->&&->day_name();
vs. this:
say $shop->ShopperDueDate->day_name() if $shop->ShopperDueDate;
I haven't seen it done yet, but I am sure that you can do it with perl. I just have no idea how. Any ideas?

Edit: One of the things that none (as far as I can see) of these answers helps me with is if I need to do TWO andands. Such as: $shop->order->?->date->?->day_name(); It seems like using something like Simple::Filter is the only way to do this...

Replies are listed 'Best First'.
Re: Is there an andand for Perl like there is in Ruby?
by tilly (Archbishop) on Jan 08, 2009 at 01:32 UTC
    What I've done before is create an object which uses overload to return an object that stringifies to "", numifies as 0, is false in boolean context, but which can have any method called on it and always returns itself. Then have ShopperDueDate return that in the event of that field being missing. Then you can chain method calls as far as you like and look at the return of the last one.

    I would have released it to CPAN as Class::Null but there already is a (IMO much less useful) class out there named that. :-(

    Update: Apparently the author of that class saw the error of his ways and that class is now the useful class that I just described! :-)

Re: Is there an andand for Perl like there is in Ruby?
by jdporter (Paladin) on Jan 08, 2009 at 01:33 UTC

    In

    say $shop->ShopperDueDate->andand->day_name();
    the say statement isn't conditional, as it is in the if form. So what is say supposed to do — that is, what is its argument supposed to be — when $shop->ShoppperDueDate is false?

    One technique you could consider — using $_ as a sort of alias for the value from some other expression (this is called a "topicalizer" in Perl 6 land):

    $_ and say $_->day_name for $shop->ShopperDueDate;
    But it's not entirely unugly.

    Between the mind which plans and the hands which build, there must be a mediator... and this mediator must be the heart.
      $_ and say $_->day_name for $shop->ShopperDueDate; But it's not entirely unugly.
      Indeed. But there is another way. A while ago, tye pointed me out that || (and friends), when the whole expression is in list context, return their RHS in list context too. So you can make this:
      say $_->day_name for $shop->ShopperDueDate || ();
      which will indeed skip the loop body if the LHS evaluates to false.
Re: Is there an andand for Perl like there is in Ruby?
by Aristotle (Chancellor) on Jan 08, 2009 at 09:02 UTC

    You can almost do this in Perl, but it requires sticking a method in UNIVERSAL (awful) and loading autobox::Core (because by default not everything is an object), both of which highly intrusive changes.

    use autobox::Core; use Class::Null; sub UNIVERSAL::andand { return defined $_ ? $_ : Class::Null->new for +shift }

    However, even with autobox loaded you cannot call methods on undef. So for all the intrusion you accepted for this it will still fail. You just have to be explicit:

    for ( grep $_, $shop->ShopperDueDate ) { say $_->day_name() }

    You can try to abstract this out a little, but then you get either a different kind of verbosity:

    sub iftrue { for ( grep $_, $_[0] ) { $_[1]->() } } iftrue $shop->ShopperDueDate, sub { say $_->day_name() };

    Or if you try to fix that it reads in the wrong order:

    sub cond(&$) { for ( grep $_, $_[1] ) { $_[0]->() } } cond { say $_->day_name() } $shop->ShopperDueDate;

    And don’t even think of “fixing” this with Filter::Simple. If source filters are the answer then you’re asking the wrong question. You will have mysterious bugs if you go down that route because source filters always break. How badly depends on filter in question; yours is a case where the type of breakage is bad.

    What might (eventually) be possible is to implement something like this with Devel::Declare voodoo, which is (becoming) a macro system and therefore not prone to the problems of source filters.

    Makeshifts last the longest.

      I've just written Scalar::Andand, using a similar approach. It handles undefined cases, though that may be buggy in a lot of cases. Check your local CPAN mirror soon.
Re: Is there an andand for Perl like there is in Ruby?
by ikegami (Patriarch) on Jan 08, 2009 at 01:32 UTC

    You can use for as a topicalizer, but that's about it unless the object provides something for you.

    for ($shop->ShopperDueDate) { say $_->day_name() if $_; }

    On the other hand, if you're ok with changing ShopperDueDate, you have options.

Re: Is there an andand for Perl like there is in Ruby?
by sundialsvc4 (Abbot) on Jan 08, 2009 at 14:25 UTC

    (Shrug...)

    “And then again, there really is such a thing as too much ‘terseness,’ ” he observed.

    I guess that my biggest objection to writing things this way is that “it sounds like the programmer is stuttering.” Anyhow, the un-trained eye could easily gloss over the apparently-substantial difference between and and “this andand thing,” which, although it superficially appears to be a word (specifically, a logical-operator of some sort...), apparently isn't.

    I tend to dislike attempts to sacrifice keystrokes at the expense of clarity. I've been burned too many times by such things. Since I generally work by the task, not by the hour, such things cost me real money.

Re: Is there an andand for Perl like there is in Ruby?
by ForgotPasswordAgain (Priest) on Jan 08, 2009 at 13:13 UTC

    Another way to think about it might be: why do you have to check that ShopperDueDate is true in the first place?

    For example, you could have $shop->ShopperDueDate throw an exception if it's not true that it can('day_name'), then

    eval { say $shop->ShopperDueDate->day_name };

    Ok, I'm joking, but what about never allowing ->ShopperDueDate to be a non-DateTime object (or whatever). Like in Moose:

    package Shop; use Moose; .... has 'ShopperDueDate' => (...., isa => 'DateTime', default => sub { Dat +eTime->new });

    Now when $shop is initialized, you know that ShopperDueDate can call day_name, so you just write

    say $shop->ShopperDueDate->day_name;

Re: Is there an andand for Perl like there is in Ruby?
by JavaFan (Canon) on Jan 08, 2009 at 02:23 UTC
    print +($shop->ShopperDueDate || "") && ($shop->day_name . "\n");
Re: Is there an andand for Perl like there is in Ruby?
by moritz (Cardinal) on Jan 08, 2009 at 21:26 UTC
    In Perl 6 you can do that with the $object.?method syntax, and that works in Rakudo today:
    $ cat test.t class A { method some-method { say "called some-method"; } } my $a = undef; my $b = A.new; for $a, $b -> $obj { $obj.?some-method } $ ./perl6 test.t called some-method

    You can also use it to call a method on $_ like this:

    for $a, $b { .?some-method }
      I wish we had perl6 at work :-)
      But yeah, this would be the best case scenario as it's part of the language.
Re: Is there an andand for Perl like there is in Ruby?
by systems (Pilgrim) on Jan 08, 2009 at 14:26 UTC
    you can obviously do this
    say if ($_ = $shop-> ShopperDueDate-> day_name());
    since the assignement operator in perl returns the assigned value
Re: Is there an andand for Perl like there is in Ruby?
by runrig (Abbot) on Jan 08, 2009 at 18:07 UTC
    I'm not sure that I like this solution (I'm pretty sure I don't), but it works (and I now see that this probably is insufficient for what you want in methods that return objects in different classes):
    #!/usr/bin/perl package MyClass; use strict; use warnings; use Want; sub new { my $class = shift; return bless {last_op => ''}, $class; } sub this { my $self = shift; if (@_) { $self->{this} = $_[0]; } if ( my $op = $self->{last_op} ) { $self->$op($self->{this}); } else { $self->{curr_value} = $self->{this}; } return want(qw(SCALAR REF)) ? $self : $self->{curr_value}; } sub that { my $self = shift; if (@_) { $self->{that} = $_[0]; } if ( my $op = $self->{last_op} ) { $self->$op($self->{that}); } else { $self->{curr_value} = $self->{that}; } return want(qw(SCALAR REF)) ? $self : $self->{curr_value}; } sub andand { my $self = shift; $self->{last_op} = '_andand'; return $self; } sub _andand { my ($self, $value) = @_; $self->{last_op} = ''; $self->{curr_value} = $self->{curr_value} && $value; } package main; use strict; use warnings; my $foo = MyClass->new(); $foo->this(0); $foo->that(5); print "This: ", $foo->this(), "\n"; print "That: ", $foo->that(), "\n"; my $value = $foo->this()->andand->that(); print "V: $value\n"; $value = $foo->that()->andand->this(); print "V: $value\n"; $foo->this(3); $value = $foo->this()->andand->that(); print "V: $value\n"; $value = $foo->that()->andand->this(); print "V: $value\n";
Re: Is there an andand for Perl like there is in Ruby?
by NetWallah (Canon) on Jan 08, 2009 at 20:35 UTC
    I would want to subclass or direct-code this syntax:
    $shop->ShopperDueDate->day_name->say(COMPLAIN_IF_NULL=>0)
    Of course you could set COMPLAIN_IF_NULL=0 as the default (or parameter in "new"), and not have to set that on each call.

         ..to maintain is to slowly feel your soul, sanity and sentience ebb away as you become one with the Evil.