I feel like a broken record
Are you like me, and often find yourself wanting to include an element in a list conditionally (often depending on whether it is defined)?
F.ex., I may want to construct a URI which has a number of moving parts.
- In the simplest case, it may look like http://example.net/app/doc, where doc is a controller in the web app.
- To link a specific item, the URI might be http://example.net/app/doc/42
- Some controllers are broken down further, giving things like http://example.net/app/doc/42/notes
- The app supports multiple sites, so I might also have something like http://example.net/app/joebob/doc
- To get the editing interface, you need to prefix the whole shebang accordingly, ie. for the joebob subsite: http://example.net/app/admin/joebob/doc
There are two dead obvious approaches to write this in Perl:
-
The ugly push-based version:
my @part = ( 'http://example.net/app' );
push @part, 'admin' if $is_admin_link;
push @part, $subsite if defined $subsite;
push @part, $mode;
push @part, $id if defined $id;
push @part, $submode if defined $submode;
my $uri = join '/', @parts;
-
The nicer approach with a ternary:
# [updated: originally had `'admin' ? $is_admin_link : ()`]
my @part = (
'http://example.net/app',
( $is_admin_link ? 'admin' : () ),
( defined $subsite ? $subsite : () ),
$mode,
( defined $id ? $id : () ),
( defined $submode ? $submode : () ),
);
my $uri = join '/', @part;
But both approaches require you to repeat yourself unncessarily. In the push case, you have to repeat the push @part bit, and in the ternary case, you have to supply : () as an else clause for every case.
Enter x!!
Obviously, this is a composite operator, consisting of x and two ! negations.
The double negation is there to forcibly convert the right side to a boolean value with the same truthness as the original value:
$a = undef;
print $a ? 'true' : 'false'; # prints 'false'
print $a; # prints '' and warns
$a = 0;
print $a ? 'true' : 'false'; # prints 'false'
print $a; # prints '0'
$a = 'abc';
print $a ? 'true' : 'false'; # prints 'true'
print $a; # prints 'abc'
# whereas
$a = undef;
print !!$a ? 'true' : 'false'; # prints 'false'
print !!$a; # prints '' without warning
$a = 0;
print !!$a ? 'true' : 'false'; # prints 'false'
print !!$a; # prints ''
$a = 'abc';
print !!$a ? 'true' : 'false'; # prints 'true'
print !!$a; # prints 1
So the !! will force anything that shows up on the right side to be either 1 or !1.
The x is, of course, Perl’s repetition operator:
@_ = ( 'a' ) x 4;
print join ':', @_; # prints 'a:a:a:a'
print scalar @_; # prints '4'
Of interest to us are the cases where the number of repetitions is 1 or 0:
@_ = ( 'a' ) x 1;
print join ':', @_; # prints 'a'
print scalar @_; # prints '1'
@_ = ( 'a' ) x 0;
print join ':', @_; # prints ''
print scalar @_; # prints '0'
Since boolean values in Perl are either 1 or !1, that means ( $something ) x $boolean will do exactly what we want. And we can force everything to be a boolean using the !! double negative. That is how we get x!!.
Update: caveat codor – as ikegami emphasises, the parens are required! ( $something ) x $boolean does a very different thing from $something x $boolean. I knew this, but forgot to harp on it. (Yes, this difference is too subtle. In Perl 6, there will therefore be two different operators, x for strings and xx for lists.)
Bottom line: the example I gave can be written like this:
my @part = (
'http://example.net/app',
( 'admin' ) x!! $is_admin_link,
( $subsite ) x!! defined $subsite,
$mode,
( $id ) x!! defined $id,
( $submode ) x!! defined $submode,
);
my $uri = join '/', @parts;
Neato! Not only is it nicer, but I also find that x!! has a beautifully evocative quality. :-)
Update: but note that ( $x ) x!! $cond differs from $cond ? $x : () in shortcircuiting behaviour: the ternary will avoid evaluating $x if $cond is false, but x!! will always evaluate it.
Conventional notes
You’ll notice that defined always returns a boolean anyway, so the !! isn’t actually necessary in three of our cases, and might not be necessary in the first one either.
However, I find that one should use x!! anyway, for two reasons:
- It documents intent. Using x!! clearly signals that this is not just a repetition.
- It protects you from errors. If the value of $is_admin_link is 2 for some reason, it is still true according to Perl, but in that case ( 'admin' ) x $is_admin_link (without !!) will yield a very different result from ( 'admin' ) x!! $is_admin_link (with !!).
Conclusion
So there you have it. If you want to conditionally include a sub-list within a larger list, you can use the composite x!! operator express exactly that.
Makeshifts last the longest.
Re: Secret Perl Operators: the boolean list squash operator, x!!
by ikegami (Patriarch) on Jul 31, 2006 at 17:58 UTC
|
>perl -le "@a = 'test' x 0; print scalar @a"
1
>perl -le "@a = ('test') x 0; print scalar @a"
0
| [reply] [d/l] [select] |
Re: Secret Perl Operators: the boolean list squash operator, x!!
by xdg (Monsignor) on Jul 31, 2006 at 18:18 UTC
|
Totally ++. This is both brilliant and scary.
However, it's so unusual that I think it would be hard to follow -- more so than many other idioms. I think I'd put a space into it rather than treat it as an "operator":
( 'admin' ) x !!$is_admin_link,
I think that will be more intelligible to those who aren't familiar with the idiom.
Also, because of the way this works for arrays, you might want to call this the "()x!!" operator -- though a pithy visual metaphor escapes me. Consider:
use strict;
use warnings;
my $cond = 0;
my @foo = 'foo';
my @bar = ( 'bar', @foo x !!$cond, 'baz' );
print join(q{,},@bar), "\n";
@bar = ( 'bar', (@foo) x !!$cond, 'baz' );
print join(q{,},@bar), "\n";
Result:
bar,,baz
bar,baz
Teaching people to always use the () may save them from expected results.
-xdg
Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.
| [reply] [d/l] [select] |
|
Also, because of the way this works for arrays, you might want to call this the "()x!!" operator -- though a pithy visual metaphor escapes me.
How about the "Enterprise" operator, for it's resemblence to NC1701?
()x!!
Oh, well, it looks better in italics anyway...
-QM
--
Quantum Mechanics: The dreams stuff is made of
| [reply] |
|
There it is: proof by its very nature that Perl is an Enterprise-level language!
CountZero "If you have four groups working on a compiler, you'll get a 4-pass compiler." - Conway's Law
| [reply] |
|
I think that will be more intelligible to those who aren’t familiar with the idiom.
I don’t know. Shouldn’t that mean it should be written with one more space, x ! !? I think the hard-to-grasp part is actually why/how () x $boolean works; the addition of !! doesn’t make it appreciably more difficult to understand, in my opinion.
Makeshifts last the longest.
| [reply] |
|
Because !! is a (moderately known) idiom as well (the "boolification" operator), I don't think that needs to be split up. My snap reaction to seeing ('foo') x!! $bar is a "huh?" until my brain parses x!! into two separate things. My snap reaction to ('foo') x !!$bar is immediately "list repeated boolean times".
Separately, I was thinking of parallels to "boolification" for a name, but it's not true or false -- it's a "there or not there" operator. That made me think first of the Cheshire Cat fading in and out, and the cat idea reminded me of Schrodinger's Cat and other quantum mechanical metaphors. Maybe there's a good name in there somewhere.
-xdg
Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.
| [reply] [d/l] [select] |
Re: Secret Perl Operators: the boolean list squash operator, x!!
by Tanktalus (Canon) on Jul 31, 2006 at 20:37 UTC
|
I've said this once before. Using boolean algebra in numerical context relies on obscure implementation details while using the ternary operator relies on well-defined (and documented), designed-that-way implementation details. For that reason alone, I would not want to see this construct (not operator) in code I'd have to maintain later.
By declaring this an operator, you're making an analogy that it has some sort of precedence that can be used to avoid parenthesis - in fact, this looks more like a function in reverse (the parentheses come before the "name" of the function, which is "x!!"). So that makes it the first awkward operator we've got, even given all the extras (e.g., goatse operator, etc.).
I would instead suggest that a clearer, more maintainable version might be ($string) x ($cond ? 1 : 0). Definitely longer than your operator, but I'm not only not claiming this to be an operator (more of an idiom or pattern), but it'll be way more obvious to anyone reading it what I'm trying to do. Sure, there's still some duplication (the ?1:0 part), but not a lot more than the duplication your operator has (the x!! part).
That said, I don't find either of your original "problem" examples to be particularly onerous, and thus I may not be really appreciative of the elegance gained with some slightly obfuscated code.
| [reply] [d/l] |
|
obscure implementation details
Disagree. The shape of booleans in Perl 5 is specified behaviour, and is only going to become more explicit in Perl 6.
I would instead suggest that a clearer, more maintainable version might be ($string) x ($cond ? 1 : 0)
If you’re using a ternary anyway, why would you want to throw in an x op? All that does is lose the shortcircuiting, make the code longer, and force some extraneous parens. Just do all the work using the ternary: $cond ? $string : ()
I don’t find either of your original “problem” examples to be particularly onerous
I’d never use the push version. The one with ternaries doesn’t look onerous because there are so many conditional bits that you have no hope of putting it all on a single line anyway, and the lists are simpler than the conditionals, and it’s highly regular, and all. I was trying to come up with an example that can’t be simplified using grep.
I can’t recall the exact code that led me to post the thread on perl6-users which led Larry to nudge me in this direction, but it was a case of injecting a single list into the arguments of a one-liner join, where precedence forced me to parenthesise things, so the version using the ternary absolutely was more ornery than it should have been. With such a short list, it’s more annoying than the example I gave in the root node, because nothing really helps – putting it on a single line makes it unwieldy, breaking it into multiple lines makes it breathlessly verbose, using an extra temporary looks hackish. Sticking an x!! wouldn’t have been ideal, but would certainly read better than all the alternatives.
Makeshifts last the longest.
| [reply] |
|
That !0 == 1 is not "well-defined" that I've noticed - perhaps someone could point out precisely where this is stated in a way that is not only definitive, but unambivalent (e.g., I don't want to see docs saying "It works this way, but don't trust it.").
I have found referenced in perlsyn that ! $true_value returns something special. But it says nothing about ! $false_value.
As for perl6, that's not part of the current conversation. As in, if they explicitly document that this is how the not operator works, great, it's explicit. Perl 5 doesn't document it, though, as tye pointed out in Re: One out of three ain't bad (order), perl 5 can't change it now.
Even then, what really is a boolean in numeric context? What is a list, multiplied by "true" times? Or by "false" times? These are conceptually wrong. Just as adding "true + true" to try and get "2" (you don't - you get "true"). You may as well be trying to multiply $string x 'banana' - it makes as much sense.
Except that computer programmers seem to think that 0 == false and 1 == true. Which is a horrible habit to be in (especially when someone gets the brilliant idea to compare if ($var == true)). Let's not expand on that disservice by promoting its further abuse. Instead, let's promote using booleans as booleans, numbers as numbers, and strings as strings. Now, before someone chews my head off ("That's not the perl way! We luvs our automatic type-changes!"), I do think that Larry got it right in Perl6. These automatic changes aren't actually so automatic. They use deep amounts of context to figure it out. And, if we continue with the warnings of perl5 (where things are allowed, but you're probably not doing it right - turn off warnings if you are), some automatic changes will continue to get warnings, even though they work. I hope that booleans in numeric context will be one of them. Unless, of course, you manually set something to say you want a specific boolean (or booleans in general) to have certain values in numeric (or even string) context. For example, setting:
Bool.context(:numeric(true => 1, false => -1));
Bool.context(:string(true => Locale.gettext("true"), false => Locale
+.gettext("false"));
Or something like that (my perl6-fu is weak - that was intended to be something approaching a global setting). After all, what is the stringification of a boolean value? Shouldn't that be language-specific? Perhaps one variable should be "true" and "false" - but another variable should be "enabled" and "disabled". Of course, then another could use "male"/"female" (unless you need to keep track of 'other'), or "cowabunga dude!"/"to infinity and beyond!" or ... whatever you want. Of course, that would be more like $bool_var.context(...) or something
Anyway, my point is that booleans != numbers. Anymore than strings are numbers. Using computers, we just have to store them as numbers, because we don't have anything else to store them with. We have boolean operators, numeric operators, and string operators. Use the right operators for the type of data you're using. If you want numbers, convert from boolean to numeric. Make it explicit. The next guy to take care fo the code will thank you. | [reply] [d/l] [select] |
|
|
|
The x!! "operator" is a composite operator as the (babycart | supermarket trolley | pram) "operator" @{[]}, which isn't an operator either.
It's called an operator here just for fun.
Next, for cleanliness, it should be written as x !! EXPR. Ok, you could write this as x (!(!(EXPR))) if you don't trust precedence not verbosely stated by parens. Oh wait... list context? ;-)
Then, LIST x NUMBER is a well-defined construct (see perlop). And !! as a double negation isn't that hard to grasp, either.
Obscure implementation details? I can't see them here more than in any other perl construct. Perl is a langage with many obscure implementation details, many if not most of them are about context.
One strength of perl (which I love) is the possibility to use idiomatic constructs which rely on well defined features of the language, which, of course, must be understood to use and -- to understand them. Once you have grokked double negation and list repitition via x, the construct () x !! EXPR is cheesecake to grasp at a glance.
That said, I find the LIST x DOUBLE_NEGATION EXPR much more readable than the ternary operator in the context given by the OP, because it comprises less noise, that is, less chars necessary to grok to form a picture of what is meant in my mind. Another major benefit of it is that the construct () x !! EXPR keeps you aware of what x does to a list, and what unary ! is about (and that, if as a maintainer you don't grok them, you look up the docs).
update: read Re^5: One out of three ain't bad (order). While you are correct in that addition, subtraction, multiplication, division make no sense in boolean algebra, perl doesn't care, because the scope of bolean algebra just ends after evaluating !!. Would you prefer a typecast instead?
--shmem
_($_=" "x(1<<5)."?\n".q·/)Oo. G°\ /
/\_¯/(q /
---------------------------- \__(m.====·.(_("always off the crowd"))."·
");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
| [reply] [d/l] [select] |
Re: Secret Perl Operators: the boolean list squash operator, x!!
by CountZero (Bishop) on Jul 31, 2006 at 16:42 UTC
|
| [reply] |
|
Since binary "x" works like stated in perlop
Binary "x" is the repetition operator. In scalar context or if the left operand is not enclosed in parentheses, it returns a string consisting of the left operand repeated the number of times specified by the right operand. In list context, if the left operand is enclosed in parentheses or is a list formed by "qw/STRING/", it repeats the list. If the right operand is zero or negative, it returns an empty string or an empty list, depending on the context.
this operator resolves basically to !!.
I'd call that one:
The Highlander Operator.
It resolves any gazillion operations on the rhs to just 1 (One).
()x!! then is the list asserter. Of course, a list cannot be asserted better than through the Highlander Himself - so it's full name is "The Highlander List Asserter" :-)
--shmem
_($_=" "x(1<<5)."?\n".q·/)Oo. G°\ /
/\_¯/(q /
---------------------------- \__(m.====·.(_("always off the crowd"))."·
");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
| [reply] |
|
"Boolean Cast Operator" would be a less cryptic name than "Highlander Operator" for !!.
It's not even correct to say it returns 1. For example, print "[", !!1, "]" prints []. For a true argument, !! returns the dualvar 1/''.
Update: I got it backwards. True = dualvar (PVNV) 1/'1', False = dualvar (PVNV) 0/''.
| [reply] [d/l] [select] |
|
|
Hehe, thanks, but I’m afraid I can’t claim credit for this one. I wouldn’t have gotten there without a nudge from TheLarry.
Makeshifts last the longest.
| [reply] |
Re: Secret Perl Operators: the boolean list squash operator, x!!
by eyepopslikeamosquito (Archbishop) on Jul 31, 2006 at 21:40 UTC
|
I remember this trick being employed in early golf games, though it was never given a name to the best of my knowledge.
Searching
the secret Terje/mtv pdf book of Perl Golf
reveals five uses of this secret operator:
08-jun-2002: Piers Cawley, section 4.5, TPR(0,4) Interlinear Counts
-ap056 sub b{$_=lc;y/aeiouy//."/".y/a-z0-
9//}$c=$b=':^I';map{$c.=pack$l=A.(($l=y///c)>3?++$l:4),$_;$b.=pack$l,b
+}@F;$c
=~s/(.{59})...+\S.*/$1.../&&$b=~s/(.{58}\S*).*/$1/;$_=(@F."$c
".b."$b
")x!!@F;s/ *$//mg
19-jul-2002: Ton Hospel, section 3.11, Terje's Cryptoanalysis
-n0 for$n(A..Z){$;[pos=-split/$n/i].="$n $#_\n"x!!$#_}print@
17-jan-2003: Ton Hospel, section 5.20, LED Display
y/01/ #/,@F=/.../g,print"@F[@_]
"for unpack'(b30)*','\xE7\xDF\xFF?%\xD9\x84-
\xE5\xFF\x9F?eH\x96%\xE7\xCF\x9F?'x!!split//,pop
23-may-2003: Michal Jankowski, section 8.13, DEKODER
-nl sub J{map$_[$s-$_].=pop,1..$s;@_}$s=8;sub
S{/./g;$&^t||!($s/=2)?($&x$s)x$s:(J(&S,&S),J&S,&S)x!!($s*=2)}print for
+ S
16-sep-2003: Mtv Europe, section 6.9, Huffman Codes
-pa ($~,$;,@F)=sort{$a-$b}@F,($c=$~+$;)x!!s/\b$~\b|\b($;)\b/$c,$#-
/gwhile$;;s/$c|,//g
Update: It was also used by Ton Hospel and
Mtv Europe in May 2003 to produce the shortest known Perl solution to 99 bottles of beer:
sub
b{[@b=(abs||No,bottle."s"x!!++$_,of,beer),on,the,wall]}print
"@{+b},\n@b,\nTake one down, pass it around,\n@{+b}.\n"
for-pop||-99..-1
Notice that in golf, and different from Aristotle's example, the x!! secret operator
is most commonly used in scalar context -- in the 99 bottles of beer example,
it's used to generate either "s" or "", thus forming either "bottles" or "bottle".
Sometimes golfers rely on the ! operator returning
"" (and not 0) for false.
AFAICT, this behaviour is not guaranteed anywhere in the official Perl docs,
though it's mentioned in the Camel, in the "Ideographic Unary Operators" section,
page 92:
Unary ! performs logical negation, that is, "not". See not for
a lower precedence version of logical negation. The value of a negated
operand is true (1) if the operand is false (numeric 0, string "0",
the null string, or undefined) and false ("") if the operand is true.
| [reply] [d/l] [select] |
Re: Secret Perl Operators: the boolean list squash operator, x!!
by rinceWind (Monsignor) on Jul 31, 2006 at 17:07 UTC
|
This is cool!
Sadly, in most cases where I am doing conditionals, I am using optional hash keys with exists. Unfortunately, these would autovivify under x!!, which is an undesirable side effect. Ifs or ternaries work, but I can't see a way of stopping the autovivification with the "boolean list squash" operator. Can anybody suggest how to solve my variant?
--
Oh Lord, won’t you burn me a Knoppix CD ?
My friends all rate Windows, I must disagree.
Your powers of persuasion will set them all free,
So oh Lord, won’t you burn me a Knoppix CD ? (Missquoting Janis Joplin)
| [reply] [d/l] [select] |
|
Yeah, multi-level data structures and autovivification mean headaches. Check out Data::Diver, it will make your life much easier.
Makeshifts last the longest.
| [reply] |
|
Wow! That's a module I wasn't aware of, by our own tye, too!
Also spot on for understanding and explaining my autovivi problem. Aristotle++
--
Oh Lord, won’t you burn me a Knoppix CD ?
My friends all rate Windows, I must disagree.
Your powers of persuasion will set them all free,
So oh Lord, won’t you burn me a Knoppix CD ? (Missquoting Janis Joplin)
| [reply] |
|
@a = (
1,
2 x!! exists($h{ahoj}),
3
);
but that does not autovivify anything in %h (perl 5.8.7). Could you post and example?
| [reply] [d/l] |
|
exists $hash{ foo } && exists $hash{ foo }{ bar } ? $hash{ foo }{ bar
+} : ()
In this case, switching to x!! will cause $hash{ foo }{ bar } to always be evaluated, which will cause $hash{ foo } to be autovivified even if the condition is false.
Makeshifts last the longest. | [reply] [d/l] |
|
|
Re: Secret Perl Operators: the boolean list squash operator, x!! (grep)
by tye (Sage) on Jul 31, 2006 at 20:59 UTC
|
( list ) x!! cond
( grep cond, list )
cond ? ( list ) : ()
I think I'd rather "reuse" the well-known grep for this case than adopt the more cryptic idiom you've invented. For your example case, I much prefer something that uses:
join '/', grep defined $_, ...
Rather than repeating most of the terms twice (and repeating x!! and defined).
| [reply] [d/l] [select] |
|
How does that work out if $is_admin_link is either 0 or 1, but never undefined?
Makeshifts last the longest.
| [reply] |
|
my $uri = join '/', grep defined $_,
'http://example.net/app',
( grep $is_admin_link, 'admin' ),
$subsite, $mode, $id, $submode;
| [reply] [d/l] |
|
OK, so my example only used single-element lists, and most conditions were similar, so it was an insufficiently ornery problem to show that only the ternary and push are generically appropriate. Here’s an example to disqualify grep:
my $name = join '-', (
@prefix,
( $do_use_path ? split /:/, $path : () ),
$name,
$suffix,
);
The only way I can think of to write that with grep is thus:
my $name = join '-', (
@prefix,
( map @$_, grep { $do_use_path } [ split /:/, $path ] ),
$name,
$suffix,
);
D’oh, I see it now. Hmm…
That looks a lot worse than the ternary to me. grep is fine for problems with enough regularity, but when regularity is absent (here trivially achieved by having only a single conditional sublist, so there’s nothing to abstract), ternary/push/x!! is necessary – and of these, only x!! requires no unnecessary verbiage:
my $name = join '-', (
@prefix,
( split /:/, $path ) x!! $do_use_path,
$name,
$suffix,
);
I wish modifiers could be applied to expressions, then this would be crystal clear to write:
my $name = join '-', (
@prefix,
( split /:/, $path ) if $do_use_path,
$name,
$suffix,
);
Makeshifts last the longest. | [reply] [d/l] [select] |
|
Update: You appear to be updating your reply significantly without noting the updates so this reply does not yet reflect any of these updates. I may choose to update or reply again after your node content appears to have settled down. I've now quoted your entire original response as seen when I started replying to it:
OK, so my example only used single-element lists, so it was an insufficiently ornery problem to show that only the ternary and push are generically appropriate. Here’s an example to disqualify grep:
my $name = join '-', @prefix, ( defined $path and length $path ) ? ( s
+plit /:/, $path ) : (), $suffix;
Not at all. Where did you get the idea that grep can't handle more than one item in a list?
To repeat myself, the following three constructs are interchangeable:
( LIST ) x!! COND
( grep COND, LIST )
COND ? ( LIST ) : ()
except that only the middle one doesn't require an extra set of parens in some rare cases and only the last one short-circuits evaluation of LIST if COND is false (and perhaps some other more subtle differences, I suspect, though none occur to me at the moment).
(Update: And the second may evaluate COND more than once.)
So, I don't see much recommending the first choice over the second (two keystrokes is hardly a worthy deciding factor) and find the less-than-typical use of grep to be less confusing than the invention of an "x!!" "operator". So I'd use grep rather than x!! if I had reason to not use the third option or even the clearer conditional use of push or some other solution (such as filtering out undefined values as already demonstrated).
So your new example can be rewritten as:
my $name = join '-',
@prefix,
( grep { defined $path and length $path } split /:/, $path ),
$suffix;
to demonstrate the use of the second option on a list that might contain more than one item.
Trying to rewrite that using the first option will likely cause you new problems because of your use of "and" (which I wouldn't use in a such a simple Boolean expression, since "&&" is better suited for that task). Perhaps your new idiom needs to be renamed "( ()x!!() )" to not make it error-prone? (:
And note that $path being undefined would result in a warning (if enabled) in both my grep construct and your x!! construct. So I doubt I'd use either in that particular case.
Since split on an empty string returns an empty list, for this particular case I'd likely instead use:
( split /:/, $path || '' ),
but replacing "||" with "//" (or a suitable replacement) if $path might be "0".
Update: And I find it telling that when your update added an actual use of "x!!", you went and removed your use of "and" to avoid demonstrating this weakness in your idiom. :)
| [reply] [d/l] [select] |
|
!0 is not necessarily 1 (was Re: Secret Perl Operators: the boolean list squash operator, x!!)
by merlyn (Sage) on Aug 01, 2006 at 13:53 UTC
|
Until "!0" is documented to return specifically "1" and not just "a true value", your coding is dangerous.
Sure, !0 has been 1 in every implementation of Perl (so far), but there's no doc that supports that. It's merely an artifact of implementation. "!0" could return "4" and still perfectly satisfy all external Perl docs. I see nothing in perldata or perlop to validate that !0 is precisely "1". Just "true".
| [reply] |
|
Quoting elsewhere in this very thread:
it's mentioned in the Camel, in the "Ideographic Unary Operators" section, page 92:
Unary ! performs logical negation, that is, "not". See not for a lower precedence version of logical negation. The value of a negated operand is true (1) if the operand is false (numeric 0, string "0", the null string, or undefined) and false ("") if the operand is true.
So I guess that boat has sailed not just practically (which it did a long, long time ago) but also as far as being officially documented. Larry did have some editorial control over "the Camel", no?
| [reply] |
|
| [reply] |
A reply falls below the community's threshold of quality. You may see it by logging in.
|
|
Sure, !0 has been 1 in every implementation of Perl (so far), but there's no doc that supports that. It's merely an artifact of implementation.
That artifact of implementation possibly resides in handy.h
#ifdef TRUE
#undef TRUE
#endif
#ifdef FALSE
#undef FALSE
#endif
#define TRUE (1)
#define FALSE (0)
so check these lines in the perl source, released when Euler's anniversary falls on Fool's Day, because then, one line will inevitably read
#define TRUE (2.718281525)
--shmem
_($_=" "x(1<<5)."?\n".q·/)Oo. G°\ /
/\_¯/(q /
---------------------------- \__(m.====·.(_("always off the crowd"))."·
");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
| [reply] [d/l] [select] |
|
At this point, it's used as much as documentated features. Larry and the developers wouldn't change the value of true without taking that into consideration. They'd need an aweful good reason to change it. In addition, isn't there a mantra to the effect of "Perl is what perl does"?
| [reply] [d/l] |
|
In addition, isn't there a mantra to the effect of "Perl is what perl does"?
If so, it's a bad mantra. People can't ever legitimately claim to know how to program in Perl if the definition of Perl changes with each daily build.
If Larry replaces perl.c with hello.c, does "Hello, World" become the new Perl? ;-)
| [reply] |
|
| [reply] |
|
I gotta ask this. If v5.12 arrived with !0 returning 4, and the OP code broke, would the fix be to the OP code, or to v5.12?
Also, "dangerous" is usually reserved for things that threaten life or limb. Under any circumstance where code could possibly be dangerous in that sense, any such fundemental change to the implementation, documented or not, would surely be detected during the obligatory, extensive system and regression testing that any software in such a life critical application would have to undergo?
Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.
| [reply] [d/l] [select] |
Re: Secret Perl Operators: the boolean list squash operator, x!!
by cog (Parson) on Jul 31, 2006 at 17:13 UTC
|
Aristotle, not only is this very interesting, the explanation you give is really well written :-) | [reply] |
|
I was thinking of you actually when I chose that title. I knew you would bite instantly, too. :-)
Makeshifts last the longest.
| [reply] |
Re: Secret Perl Operators: the boolean list squash operator, x!!
by ioannis (Abbot) on Aug 01, 2006 at 07:39 UTC
|
Instead of using the x operator, we could have used the
the && operator. The advantage here is that it codes simpler,
and the disadvantage is it has no movable parts (since it is not an array).
no strict; no warnings;
my $part = 'http://example.net/app'
. ($admin && $is_admin_link)
. (defined $subsite && $subsite)
. $mode
. (defined $id && $id)
. (defined $submode && $submode)
;
print $part;
| [reply] [d/l] |
|
Where did the slashes go? That would assemble URIs such as http://example.net/appadminjoebobdoc rather than http://example.net/app/admin/joebob/doc.
You also broke down on my ornery $is_admin_link requirements. First of all the clause is the wrong way around, it would have to be ( $is_admin_link && $admin ) – but since $is_admin_link is always 0 or 1, that would leave spurious zeroes in the output, so you need to boolify: ( !! $is_admin_link and $admin ).
All things considered, we get this:
my $part = (
'http://example.net/app'
. ( !! $is_admin_link && '/' . $admin )
. ( defined $subsite && '/' . $subsite )
. '/' . $mode
. ( defined $id && '/' . $id )
. ( defined $submode && '/' . $submode )
);
I definitely feel like a broken record now.
Not to mention that all of this misses my point because it’s specific to join, and I’ve needed to pass a variable list to some function or method often enough where really needed the sub-list to be empty depending on the condition – winging it by combining && and the false evaluates to the empty string factoid wouldn’t have done me any good.
Makeshifts last the longest. | [reply] [d/l] |
|
Indeed, the substring related to $is_admin_link should have been
written to reflect both boolean states, like this:
(($is_admin_link||'') && $admin)
Whether this form is preferable than using the trenary operator, the
issue props our individual taste.
( The missing slashes were already prepended on my
variables -- you had no way of knowing this; which in turn reminds
us of the better clarity of join(), another subject
and one more issue to consider.)
| [reply] |
Re: Secret Perl Operators: the boolean list squash operator, x!!
by zby (Vicar) on Aug 01, 2006 at 07:39 UTC
|
That's interesting, but I would rather use the first, push version. When I see code like the push version I don't need to think about what is being done here - it's automatic. I also rather not use the ternary operator - it's too rare to become automatic in my brain. | [reply] |
Re: Secret Perl Operators: the boolean list squash operator, x!!
by radiantmatrix (Parson) on Aug 02, 2006 at 14:01 UTC
|
Your x!! approach is nifty, but it's a little hard to read: the double-negation is confusing unless explained, and the x operator seems to be out of place in this context. I have always found one of these easier to read:
my $uri;
foreach (
'http://example.net/app',
$is_admin_link && 'admin',
$subsite,
$mode,
$id,
$submode,
) {
$uri.="$_/" if ( defined $_ && length($_) ) #omit undef and empties
}
If you need to preserve the parts seperately:
my @part = (
'http://example.net/app',
$is_admin_link && 'admin',
$subsite,
$mode,
$id,
$submode,
);
my $uri;
foreach (@parts) {
$uri.="$_/" if ( defined $_ && length($_) ) #omit undef and empties
}
I prefer these because:
- The logic for inclusion of parts is clear
- It makes it easy to apply a common rule to all components (e.g. URL-ify components before concatenation)
- It avoids repetition of code
| [reply] [d/l] [select] |
|
This is basically ioannis’ response, with most repetition removed. All of the points I made in the last paragraph of my response to him apply equally to your suggestion.
Makeshifts last the longest.
| [reply] |
|
Actually, most of your objections don't seem to apply.
- I already include the slashes through use of $uri.="$_/"
- The $is_admin_link && 'admin' is in the right order already
As for the 0/1 problem with $is_admin_link, that's a fault of my assumption that $is_admin_link would either be true or undefined. I agree, that's a bad assumption, but it's a simple fix. I would probably use a single ternary:
$is_admin_link ? 'admin' : undef
| [reply] [d/l] [select] |
|
Re: Secret Perl Operators: the boolean list squash operator, x!!
by Anonymous Monk on Jul 31, 2006 at 20:36 UTC
|
| [reply] |
|
Let’s see an example of how clean and self-describing such a thing looks in the language of your choice. Go on, astound me. :-)
Makeshifts last the longest.
| [reply] |
|
Let’s see an example of how clean and self-describing such a thing looks in the language of your choice. Go on, astound me. :-)
I'm not likely to astound anyone. I write boring code, deliberately. It's easier to find bugs in boring code; code that's full of clever, subtle tricks causes all the problems, in my experience.
A simple series of append statements describes exactly what you want to do, but you seem to want to make life complicated, because you seem overly-concerned about the repetition of the variable name and the .= operator. I think that's only a real concern if you have to write such repeated expressions a lot in a given body of code, but if you really have to be concise (which is part of what I think you mean by "clean"), I still think there are simpler ways to write them.
Here's an even more concise way to write the main expression, using two helper functions. No wierd operator tricks are required, and this code could be written in just about any language, let alone a "language of choice". In some languages, using macros as opposed to helper functions might give a performance boost, but the principle remains the same...
# If the constant string "admin" changes, you can change it
# in just one place...
sub admin_link {
my ($flag) = @_;
if ($flag ) {
return('admin');
}
return("");
}
# this can obviously be optimized as necessary...
sub join_if_defined {
my ( $join_string,@list) = @_;
my @defined_elements;
@defined_elements = grep( defined($_),@list);
return join($join_string, @defined_elements );
}
@sections = ( 'http://example.net/app',
admin_link($is_admin),
$subsite,
$node,
$id,
$subnode
);
$uri = join_if_defined("/", @sections);
I think that's reasonablely concise; it's certainly easier to read than a list containing phrases like "a list of one element consisting of <something> repeated not not <some expression> times". | [reply] [d/l] |
|
|
A reply falls below the community's threshold of quality. You may see it by logging in.
|
Re: Secret Perl Operators: the boolean list squash operator, x!!
by blazar (Canon) on Jul 17, 2008 at 22:32 UTC
|
The nicer approach with a ternary:
my @part = (
'http://example.net/app',
( 'admin' ? $is_admin_link : () ),
( defined $subsite ? $subsite : () ),
$mode,
( defined $id ? $id : () ),
( defined $submode ? $submode : () ),
);
my $uri = join '/', @part;
Said all this, probably I wouldn't have thought of the boolean list squash operator. I would have tried to "encapsulate" the component data and map it through some suitable code. Of course the problem is: which code? Of course, the answer is that there are tons of ways that work, but I also want something which is visually clear at a glance, and unobtrosive. I considered some possibilities involving nested ternaries which would e.g. distinguish refs from non-refs, and then arrayrefs with two elements from ones with one. Then I considered as an alternative chained map()s achieving the same effect for clarity at the expense of multiple looping where a single one would suffice. But then I wouldn't want two or three "header" lines of code only to suck up say other six. I really want at most one, and simple enough. Eventually, the best alternative I could think of is:
my @part = map defined $_->[-1] ? $_->[0] : (),
['http://example.net/app' => 1],
['admin' => $is_admin_link],
[$subsite], [$mode => 1], [$id], [$submode];
Here:
- the additional visual "encapsulation" given by square parens allowed me to put several params on one line with no risk of confusing them;
- I forced a => 1 on elements that you were originally unconditionally inserting: since these were supposedly true, it's completely useless but possibly as a visual reminder that they are, and in turn this is silly for the constant item but may be sensible for $mode.
As a sort of counter-example, if I wanted the unconditionally inserted elements to be included in the list without the burden of the square parens, then it's obvious how to do it:
Here, the first line is getting long and confusing, and the absence of square parens around $mode also degrades readability.
| [reply] [d/l] [select] |
|
@part = map defined $_->[0] ? @{$_}[(@$_>1?1:0)..$#{$_}] : (),
[1 => 'http://example.net/app', 'extra'],
[$is_admin_link => 'admin'],
[$subsite], [1 => $mode], [$id], [$submode];
To my eyes, [ $condition => $value ] reads better.
Also allows you to do: [ $condition => $list, $of, $values ] | [reply] [d/l] [select] |
|
To my eyes, [ $condition => $value ] reads better.
I personally believe that it's not that important: I gave a... wild shot, if you know what I mean. Of course one may want some sort of full fledged solution taking care of all possible cases. For the present situation, your expression is fine enough, but just that little much too complex wrt "mine." I may have used something like that too, of course.
However, as a sort of justification, I was thinking much of the postponed condition in analogy with an if statement modifier, that can be thought of as an optional parameter and be there or not...
Also allows you to do: [ $condition => $list, $of, $values ]
And then again, one may want to do [ $value => @conditions ] instead: again It depends on the actual situation and there's not a clear-cut, all-encompassing answer. (There may be, if we wanted to make it into a real module, with a well thought API, and so on, but that's not the case...)
| [reply] [d/l] [select] |
|
I find that a lot harder to read. It decouples the logic from the individual parts of the list so it only works at all for lists where nearly every part is conditional, like the example I gave. If you want to conditionally include only one thing in a much larger unconditional list it really breaks down. Contrast:
join '/', 'foo', 'bar', $baz, 'quux', ( @qux ) x!! $somecond, $wibble,
+ $wobble;
# vs
join '/', map { defined $_->[-1] ? $_->[0] : () } (
[ 'foo' ], [ 'bar' ], [ $baz ], [ 'quux' ],
[ @qux, $somecond ], [ $wibble ], [ $wobble ],
);
Maybe you find the second one easier to read than the first, but I don’t. It gets a little clearer with each element on its own line (in particular, the $somecond gets completely lost in the noise if you put several elements on each line), but then you’ve gone from a single line to 10.
Your proposed solution is worth considering, but is useful only in limited circumstances. I was trying to find an option that would work in a wide range of cases.
I have to note that my proposed composite operator ()x!! is not that option, either: it doesn’t shortcircuit (nor do any of the clever alternatives given, yours included), and that has killed it in every case I might have used it since writing this node. I might find the syntactical requirements of the ternary onerous, but another option – not even a better one, but any option at all – has yet to show up. Sigh.
Makeshifts last the longest. | [reply] [d/l] |
|
I find that a lot harder to read. ... If you want to conditionally include only one thing in a much larger unconditional list it really breaks down.
I personally believe that ease of reading is in the eye of the beholder, but as far as the second remark is concerned, of course it's fully seconded++. Long story made short - IMHO: no clear-cut, all encompassing solution. Just many different situations. (And incidentally, nothing which will take our sleeping away, I hope! ;)
| [reply] [d/l] |
|
|