[Edinburgh-pm] a Perl surprise

Aaron Crane perl at aaroncrane.co.uk
Thu Jul 19 04:06:15 PDT 2012


Nick <oinksocket at letterboxes.org> wrote:
> But I wanted it to be robust so that if the brackets were forgotten, things
> would fail safely rather than grouping in unintended ways.  (No, I'm not
> convinced my alternative is entirely better, but... this is an experiment.)
>
> Also in the name of robustness, I want to highlight potential accidental misuses
> of 'with' and 'without', such like this:
>
>   combine with qw(toast egg), without qw(gluten);  # Error: 'toast' not eaten

Yeah; as you've learned, there's no good way to make that behave in an
obviously reliable and unsurprising way.  Which is sad.

Here's an alternative API possibility: require the caller to use a
single array ref instead of a list of arguments:

combine with [qw(toast egg)], without ['gluten'];

You might well point out that, at least aesthetically, this is no
improvement over requiring parens round the arguments.  But it does
have the advantage that your with() and without() definitions can (a)
use the ($) prototype to force unary-op parsing, and (b) throw an
exception if you call them with something that isn't an array ref
(because that's the case that doesn't allow the caller to make it
clear to the callee that they understood the grouping rules).

Further, you can alleviate the punctuational burden by applying a
module like Syntax::Feature::Qwa:

use syntax 'qwa';
combine with qwa(toast egg), without qwa(gluten);

S::F::Qwa uses Devel::Declare under the hood; effectively, the qwa
behaves like a qw, but wraps an array-ref constructor round the qw
list.

If that still doesn't give you something sufficiently low-ceremony to
use as an embedded DSL, your next best option is probably to write
things like with() and without() as Devel::Declare-parsed routines
themselves.  I've never tried that sort of thing, though.

> Bad syntax should fail instead of doing something surprising. Except to my
> annoyance, this gave me something even more surprising as discussed:
>
>   my $stuff = combine with qw(toast eggs), without qw(gluten);
>
> Here, 'with' gets 'eggs', but 'toast' gets discarded on the floor, a somewhat
> obscure warning about void context gets printed, and things charge onwards to
> disappointment and/or disaster.  Not good.

I agree entirely.

Here's a sneaky approach: force your callers to have void-context
warnings upgraded to fatal exceptions.  You can do that by having your
module's import method call warnings->import(FATAL => 'void'); it will
get invoked on behalf of the `use`-ing code.  It's slightly awkward to
do that without breaking normal Exporter stuff, but here's one way;

package Sneaky;

use strict;
use warnings;

use Exporter qw<import>;
our @EXPORT_OK = ...; # fill this in as normal

# Now modify the import() you've imported from Exporter:
use Class::Method::Modifiers qw<before>;
before import => sub { warnings->import(FATAL => 'void') };

1;

That won't accomplish anything if someone uses Sneaky without letting
it import anything, but I think I'd be happy to ignore that
possibility, on the grounds that such a person probably knows what
they're doing.

-- 
Aaron Crane ** http://aaroncrane.co.uk/


More information about the Edinburgh-pm mailing list