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

You're probably familiar with the semi-special value '0 but true', which works like 0 in any arithmetic operation, yet still evaluates to true. You can also use '0e0' or other varieties.

But in this case, what I'd like is a true value that acts as the "nothing value" not just in arithmetic operations, but also in string operations. So if $x has this value, if($x) is true, but $x.$y is just $y.

One sneaky solution is control characters, like $x="\c@". That gives a true value, and printouts will look correct. But it still means that if($x.$y eq $y) will fail, and anyone who tries to use length() is going to end up very confused.

Is there a way to do this?

Replies are listed 'Best First'.
Re: "" but true
by ikegami (Pope) on May 13, 2021 at 19:03 UTC
    $ perl -M5.010 -e' package Crazy { use overload bool => sub { 1 }, q{""} => sub { "" }, fallback => 1; } my $x = bless({}, Crazy::); say $x ? "true" : "false"; say length($x); ' true 0

    But don't. Just don't. If you think non-printable characters are confusing, this is next level confusing. This is an XY Problem.

    (U+FEFF ZERO WIDTH NO-BREAK SPACE would be a better choice than NUL, not that you should use that solution either.)

    Seeking work! You can reach me at ikegami@adaelis.com

      Truly great and terrifying!

      That really shouldn't be allowed...

      The fact that it is, is what makes Perl so wonderful and bizarre at the same time in a sort of perfect mix!

        > That really shouldn't be allowed...

        That's not true.

        For instance overloads are great when designing internal DSLs.

        These are embedded mini-languages which are meant to operate in very special semantics.

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery

Re: "" but true
by choroba (Archbishop) on May 13, 2021 at 19:03 UTC
    No, there isn't.

    Why do you need it? If you just need to tell whether a string is defined, use defined.

    map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]

      The application at hand is pretty basic. I have a sub that wraps around a hash to keep track of some traits for some items, traits which may or may not have a specified value. Example:

      if(trait('bob','isAdult')){print trait('bob','age')}

      One obvious way would be to use defined, as you say. It's a matter of encapsulation; I think it looks nicer if the code that uses this sub doesn't have to worry about that distinction. Another obvious way would be to use two different functions, but again, more visible complexity.

      A third would be to use a simple true value like "1", and hope that there's no conflict, but if the user decides to mix values and "non-values" in the same trait, that could go very wrong.

        I have a sub that wraps around a hash to keep track of some traits for some items, traits which may or may not have a specified value. Example: if(trait('bob','isAdult')){print trait('bob','age')} One obvious way would be to use defined, as you say. It's a matter of encapsulation; I think it looks nicer if the code that uses this sub doesn't have to worry about that distinction. Another obvious way would be to use two different functions, but again, more visible complexity.

        On the one hand, encapsulation is certainly often a Good Thing. On the other, the task you appear to be describing seems to be particularly well suited to Perl's hashes, which offer the distinction between exists and defined. Plus, you seem to want to mess with Perl's notion of Truth and Falsehood.

        If I may wager a guess, perhaps you are taking concepts from a different programming language and trying to bend Perl to match them? While Perl is indeed a very pliable langauge and There Is More Than One Way To Do It, perhaps it would make sense to look at what Perl offers natively and implement what you're trying to implement in a perlish way first? For example, next to plain hashes, there are tied hashes, and objects can be overloaded to allow hash dereferencing of an object to return a reference to a hash, including a tied hash. That would allow users of your API to use the Perl hash concepts they are familiar with, while allowing you to keep control over what goes in and out of the hash.

        Or, if you want to follow through with encapsulation idea, then it'd be best if you created classes and offered methods such as $bob->traits->get('age'), $bob->traits->is_set('isAdult'), and $bob->traits->set('foo','bar') (see e.g. the API of Hash::Ordered), because at the moment, it sounds to me like you're trying to overload your API a little too much.

Re: "" but true
by syphilis (Bishop) on May 14, 2021 at 02:28 UTC
    So if $x has this value, if($x) is true, but $x.$y is just $y

    I wondered if this could be done using a dualvar value:
    use strict; use warnings; use Scalar::Util qw(dualvar); my $x = dualvar(1, ""); my $y = 'hello'; print $x . $y; # outputs hello
    The only problem is that $x evaluates as False.
    For $x to be True, its first (number) argument needs to be true in numeric context && its second (string) argument needs to be true in string context.
    Unfortunately, "one out of two" is not good enough :-(
    Update: Thanks LanX for alerting me to this mistake, and to the following mistake.

    As LanX has already suggested As ikegami has already demonstrated, this can however be achieved using overloading.
    Here's a another little demo:
    # dualvar.pl package DualVar; use strict; use warnings; use overload 'bool' => \&overload_true, '.' => \&overload_concat; sub overload_true { # Return 1 if either $obj->[0] or $obj->[1] # are true. Else return 0. my $obj = shift; if($obj->[0] || $obj->[1]) { return 1 } return 0; } sub overload_concat { my ($obj, $other, $reversed) = (shift, shift, shift); if($reversed) { return $other . $obj->[1] } return $obj->[1] . $other; } # The above procedures would normally be put into # a module, which would be loaded as needed. # The code below relies on the above procedures. # Here we see that $x is True, and also that $y # remains unchanged when $x is concatenated onto it. my $x = [1, ""]; my $y = "hello"; my ($r1, $r2); bless $x, 'DualVar'; if($x) { $r1 = $x . $y; $r2 = $y . $x; } print "# ok 1\n" if $r1 eq $y; print "# ok 2\n" if $r2 eq $y; # Outputs: # ok 1 # ok 2
    I think that satisfies the X part of your XY problem - at least for the "X" as quoted at the very beginning of this post of mine.
    I'm not sure if it helps with the "Y" part ;-)

    Cheers,
    Rob
      > For $x to be True, its first (number) argument needs to be true in numeric context && its second (string) argument needs to be true in string context.

      Do you have a reference for this, or is it a guess?

      I'm surprised since

      • I read boolean context be a sub case of numeric context
      • Perl tends to avoid such overhead like checking two slots

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery

        Do you have a reference for this, or is it a guess?

        I thought I had a reference for this ... but no, I completely misread this statement in the docs, relating to isdual():
        If $var is a scalar that has both numeric and string values, the resul +t is true
        Not only does it not pertain to dualvar(), but it doesn't say what I thought it said, anyway !!
        Additional experimentation suggests that, in fact, the dualvar will be True if the second arg is True. Is that the correct summation ?
        Thanks for catching.

        Not sure if that mistake necessarily impacts upon the little overload demo I provided.
        In any case, of course, that overload_true() subroutine can be tweaked to make it do whatever you want.

        I'll edit that post of mine.

        Cheers,
        Rob

      I'm learning so many dirty tricks!

      It's a shame the dualvar thing doesn't work in this case, but I guess if I ever have a need for "5 but false", I know what to do.