Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses

This could have DWIM better

by Aristotle (Chancellor)
on Mar 01, 2006 at 16:54 UTC ( #533694=perlmeditation: print w/replies, xml ) Need Help??

The recent Chatterbox bug discussion reminded me of a huge pet peeve of mine about Perl (and hugely peevish too, I know): itís such a pain to test string length if you want to accept undefined values without emitting warnings. You have to write something like this:

$foo if defined $str and length $str;

Itís so redundant and verbose that people will often simply do

$foo if $str;

and accept that a value of 0 or "0" will produce false negatives with a shrug.

In other words, the easy and obvious way is wrong.

That could have been avoided so simply:

BEGIN { *CORE::GLOBAL::length = sub(;$) { defined $_[0] ? CORE::length( $_[0] ) : undef; }; }

The above test for string length then becomes this:

$foo if length $str;

Since undef silently evaluates to false in boolean context, same as 0, this never throws an uninitialized warning and does the right thing. This version of length is also more useful, since you can tell undefs from empty strings just by looking at the return value, without an extra defined test. Granted, now you have to check the return value for definedness if you want to use it in calculations, but the situation does not actually change, since you need a definedness test in either case Ė either on the passed scalar or on lengthís return value.

Iíve been using this in some scripts for a while, and I love it. I canít think of a single reason why anyone would want to have lengthís current behaviour of returning 0 while throwing a warning on undef scalars, as opposed to returning undef without a complaint. The latter seems more useful and/or less annoying in every case I can come up with.

Of course, itís way too late to do anything about this now. I darenít use it in any complex codebase that relies on a lot of CPAN code either. Maybe something to be dealt with using feature?

Oh well.

Makeshifts last the longest.

Replies are listed 'Best First'.
Re: This could have DWIM better
by japhy (Canon) on Mar 01, 2006 at 18:20 UTC
    Don't forget about $_ in your function.
    BEGIN { *CORE::GLOBAL::length = sub(;$) { my $arg = @_ ? $_[0] : $_; defined($arg) ? CORE::length($arg) : undef; }; }

    Jeff japhy Pinyan, P.L., P.M., P.O.D, X.S.: Perl, regex, and perl hacker
    How can we ever be the sold short or the cheated, we who for every service have long ago been overpaid? ~~ Meister Eckhart
      Yikes, that's a bug(update: shows a bug in perl itself). The call to length should explicitly be passing $_, but it's not (update: passes nothing) when it is overridden. This causes a problem with lexical $_:
      $ perl5.9.4 -w BEGIN{ *CORE::GLOBAL::length = sub (;$) { my $arg = @_ ? $_[0] : $_; defined($arg) ? CORE::length($arg) : undef; } } $_ = "abc"; my $_; $_ = "defghi"; print length; __END__ 3
      This should print 6 whether length is overridden or not, but as is prints 3 when it is overridden.

      Update: after consideration, I think this is just a natural consequence of the ;$ prototype, and should be no longer a problem with a _ prototype (when someone implements that).

        Sounds like it's passing the package/global $_ instead of the lexical $_. I must've missed the memo on "lexical $_".

        Jeff japhy Pinyan, P.L., P.M., P.O.D, X.S.: Perl, regex, and perl hacker
        How can we ever be the sold short or the cheated, we who for every service have long ago been overpaid? ~~ Meister Eckhart

      Díoh. Thanks.

      Makeshifts last the longest.

Re: This could have DWIM better
by radiantmatrix (Parson) on Mar 01, 2006 at 21:48 UTC

    If you're worried about polluting other's namespaces, why don't you just override the function locally? I started with japhy's modification and produced this:

    BEGIN { use subs 'length'; sub length(;$) { my $arg = @_ ? $_[0] : $_; defined($arg) ? CORE::length($arg) : undef; } }

    You could even modularize this pretty easily:

    package Func::Smarter; require Exporter; use vars qw'@ISA @EXPORT @EXPORT_OK'; use subs qw 'length' @ISA = 'Exporter'; @EXPORT_OK = qw'length'; sub length (;$) { my $arg = @_ ? $_[0] : $_; defined($arg) ? CORE::length($arg) : undef; } 1;

    Then you could just use Func::Smarter 'length';, yes?

    A collection of thoughts and links from the minds of geeks
    The Code that can be seen is not the true Code
    I haven't found a problem yet that can't be solved by a well-placed trebuchet
Re: This could have DWIM better
by demerphq (Chancellor) on Mar 01, 2006 at 17:40 UTC

    There is always:

    D:\>perl -we"my $x; print length($x.='')" 0

      Yeah, that works to suppress the warning. Itís still an unnecessary hoop to jump through though, and Iím not keen on unnecessary side effects, even if they are usually transparent. (F.ex., what if this is a tied scalar?)

      It also loses the distinction between undef and an empty string, should that be needed.

      Compared to the alternative length behaviour, it does remove the need for checking definedness of the return value if itís needed in calculations, but you can simply apply your approach to the return value instead then: $len += 0; And that avoids the problem with side effects, as the return value of length is never overloaded.

      I hadnít considered this particular point, but as I said, any way the matter is turned, I cannot find a reason to prefer the currently implemented behaviour.

      Makeshifts last the longest.

        Sorry, i was probably too terse. I wasnt suggestingthat this was a good replacement for your code, just that it was possible.


      I darenít use it in any complex codebase that relies on a lot of CPAN code either.

      Why? I thought that this trick would only affect the module its declared in. Its not going to have a global effect fwir.


        Quoth perlsub:

        Library modules should not in general export built-in names like open or chdir as part of their default @EXPORT list, because these may sneak into someone elseís namespace and change the semantics unexpectedly. Instead, if the module adds that name to @EXPORT_OK, then itís possible for a user to import the name explicitly, but not implicitly. That is, they could say

        use Module 'open';

        and it would import the open override. But if they said

        use Module;

        they would get the default imports without overrides.

        The foregoing mechanism for overriding built-in is restricted, quite deliberately, to the package that requests the import. There is a second method that is sometimes applicable when you wish to override a built-in everywhere, without regard to namespace boundaries. This is achieved by importing a sub into the special namespace CORE::GLOBAL::.

        Emphasis mine.

        Makeshifts last the longest.

The verdict, 20 months later (was: This could have DWIM better)
by Aristotle (Chancellor) on Jan 13, 2008 at 23:34 UTC
Re: This could have DWIM better
by Jenda (Abbot) on Mar 03, 2006 at 13:07 UTC

    Well, to tell the truth, I just

    use warnings; no warnings qw(uninitialized);
    to silence this particular warning completely. I find it annoying and pointless. But that's just me and everyone knows I'm weird.

      I find it valuable about 4 times out of 5. The one warning category I always disable is once.

      Makeshifts last the longest.

Log In?

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://533694]
Approved by Corion
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others having an uproarious good time at the Monastery: (7)
As of 2023-03-28 11:22 GMT
Find Nodes?
    Voting Booth?
    Which type of climate do you prefer to live in?

    Results (67 votes). Check out past polls.