tags:

views:

302

answers:

4

I'd rather do this:

say $shop->ShopperDueDate->andand->day_name();

vs. this:

say $shop->ShopperDueDate->day_name() if $shop->ShopperDueDate;

Any ideas?

(This idea is inspired by the Ruby andand extension.)

(Actually it is inspired by the Groovy language, but most people don't know that ;-)

update: I think that both maybe() and eval {} are good solutions. This isn't ruby so I can't expect to read all of the methods/functions from left to right anyway, so maybe could certainly be a help. And then of course eval really is the perl way to do it.

+9  A: 

You can use Perl's eval statement to catch exceptions, including those from trying to call methods on an undefined argument:

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

Since eval returns the last statement evaluated, or undef on failure, you can record the day name in a variable like so:

my $day_name = eval { $shop->ShopperDueDate->day_name(); };

If you actually wish to inspect the exception, you can look in the special variable $@. This will usually be a simple string for Perl's built-in exceptions, but may be a full exception object if the exception originates from autodie or other code that uses object exceptions.

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

if ($@) {
    say "The error was: $@";
}

It's also possible to string together a sequence of commands using an eval block. The following will only check to see if it's a weekend provided that we haven't had any exceptions thrown when looking up $day_name.

eval {
    my $day_name = $shop->ShopperDueDate->day_name();

    if ($day_name ~~ [ 'Saturday', 'Sunday' ] ) {
        say "I like weekends";
    }
};

You can think of eval as being the same as try from other languages; indeed, if you're using the Error module from the CPAN then you can even spell it try. It's also worth noting that the block form of eval (which I've been demonstrating above) doesn't come with performance penalties, and is compiled along with the rest of your code. The string form of eval (which I have not shown) is a different beast entirely, and should be used sparingly, if at all.

eval is technically considered to be a statement in Perl, and hence is one of the few places where you'll see a semi-colon at the end of a block. It's easy to forget these if you don't use eval regularly.

Paul

pjf
+9  A: 

I assume your second example should better be:

say $shop->ShopperDueDate ? $shop->ShopperDueDate->day_name() : undef;

instead of what it actually says.

Anyway, you can't do it with exactly that syntax without a source filter because undef isn't an object, but an unary operator, so it can't have any methods.

Instead, consider this:

package noop; 
our $maybe = bless [], 'noop';
sub AUTOLOAD { undef };

This $noop::maybe is an object; all of its methods return undef though. Elsewhere, you'll have a regular function like this:

sub maybe { $_[0] || $noop::maybe; }

Then you can write this:

say maybe($shop->ShopperDueDate)->day_name()

This works because "maybe" returns returns its argument if true, otherwise it returns our $noop::maybe object which has methods which always return undef.

EDIT: Correction! the ->andand-> syntax can be had without a source filter, by using an XS that mucks about with Perl's internals. Leon Timmermans made an implementation that takes this route. It replaces the undef() function globally, so it's likely to be a lot slower than my method.

geocar
Why do you say that would be better? It's exactly the same right?
Frew
The order to read them makes a bit more sense in his example, IMHO
Leon Timmermans
Frew: The first example is say($some_value) where the second example is roughly equivalent to if($some_value){say($some_value)} - the former example always has say() being called, whereas the latter makes calling say() conditional.
geocar
A: 

Something like (untested):

use Object::Generic::False;
sub UNIVERSAL::andand { $_[0] || Object::Generic::False->false }

UNIVERSAL is automatically a subclass of all other classes, so this provides andand for all objects. (Obviously creating methods in UNIVERSAL has the potential for conflicts or surprising action at a distance, so it shouldn't be used carelessly.) If the object upon which the andand method is called is true, return it; otherwise return a generic false object which returns itself for any method call used upon it.

ysth
The problem with this is that you can't call methods on undefined values.
Leon Timmermans
If desired, one can always use the autobox pragma to promote the undefined value to a first class object. Then you *can* call methods on it. ;)
pjf
I already did that, see my own answer to this post ;-)
Leon Timmermans
+4  A: 

I think I've just written it. I've just uploaded it to CPAN, you can find it here.

Leon Timmermans
Can you expand on "you can't call methods on undefined values"? I'm not completely sure you read all of my post.
ysth
undefined values can not be blessed references and as such you can't call methods on them. It requires quite a bit of magic to make that work.
Leon Timmermans