boutell: (Default)
[personal profile] boutell

Since 1973, or perhaps earlier, the "C" language has offered "?", known as the ternary operator. This allows programmers to include an "if / then / else" statement in the value of an assignment. That allows us to write this:
awesomeness = (iAmInvisible()) ? 100 : 1;

Which is equivalent to this:
if (iAmInvisible()) {
  awesomeness = 100;
} else {
  awesomeness = 1;

This is handy. But in this situation:
if (getCat()) {
  cat = getCat();
} else {
  cat = emergencyBackupCat;

The shorthand version is still a bit wordy:
cat = getCat() ? getCat() : emergencyBackupCat;

And calling getCat() twice might be a slow or otherwise expensive operation. (Just ask mama cat.)

This problem comes up in Symfony PHP programming when we want to fetch something the user has submitted or default to something else. We wind up with something like:
$catName = $this->getRequestParameter('catName');
if ($catName === false) {
  $catName = 'fluffy';

Which isn't too terrible. But if we'd like to just pass this to another function right off the bat, it would be nice to avoid the verbosity with the ternary operator. Unfortunately this requires us to call a method twice for no really good reason:
$cat->christen($this->getRequestParameter('catName') ?
  $this->getRequestParameter('catName') : 'fluffy');


It turns out that Symfony provides a workaround for this. It's not discussed much, but getRequestParameter and similar "getters" in Symfony often accept a second argument, which is returned as the default value:
$cat->christen($this->getRequestParameter('catName', 'fluffy'));

This is much better. But it only helps us if the method we're getting the value from offers such a convenient syntax. And it still isn't ideal if the default value is something less trivial, something we'd prefer not to compute unless it is actually necessary to do so:
$cat->christen($this->getRequestParameter('catName', CatPeer::computeUltimateCatName()));

This is where a bit of surprising good news comes in.

This morning I came across this thread in which a PHP user— perhaps one lacking old fashioned expectations that a C-like language can't possibly deviate from the classic syntax of C in any meaningful way— asks why PHP doesn't have a '?:' operator, which would return the value of the test if it is considered to be true, and return an alternative value otherwise. In other words, this:
$cat->christen($this->getRequestParameter('catName') ?: CatPeer::computeUltimateCatName());

The response to his suggestion was not what I expected: as it turns out, this exact feature is already slated for inclusion in PHP 5.3. And that's very cool, because it makes abundant sense. So what if C should have had it 30 years ago?

Another issue unique to PHP and other interpreted languages is the problem of testing whether a variable has actually been set before accessing its value. Good PHP programmers (and Perl programmers, and all Python programmers by default) set things up so that accessing a variable whose value has not yet been set is reported as an error, or at least a warning. But this can lead to tedious syntax in some cases:
if (isset($catName)) {
} else {

You may feel that this is just the result of bad design— that you ought to have initialized the variable to false in the first place— and I'm sympathetic to that view. But sometimes you're coding in an environment created by others and you just can't assume that.

The new ?: operator doesn't help here because it checks whether a value is false... which is not the same thing as checking whether a variable has been set at all.

There is an RFC to add an ifsetor operator to PHP. Unfortunately it has been rejected, mainly because it is possible to implement it directly in PHP. But that implementation always evaluates the second, default argument, which is potentially slow and just not very cool.

But I'm being greedy here. ?: is pretty cool already.

Edit: [ profile] iamo points out that Perl (perhaps among other dynamic languages) already has this feature. In Perl, the logical OR operator ( || ) returns the last value evaluated, not a strict "true" or "false." That is, if you write this in Perl:
$bar = 5 || 3;

Then $bar will be assigned the value 5, because || stops evaluating as soon as it turns into a true value. The right-hand side never gets evaluated in that case, which is also what we want.

So in Perl, this problem was solved a long time ago. Credit where credit is due.

However, in PHP, it is still a live issue because PHP does it essentially the same way C does it: the || operator returns a definitive "true" or "false" in PHP, and exactly 1 or 0 in C. So in order to avoid breaking compatibility with existing code that tests explicitly for true, PHP does need to introduce a new ?: operator. And that goes double for C, since there is undoubtedly optimized Linux kernel code somewhere that capitalizes on the fact that a true result from || is exactly 1. Changing that would be completely unacceptable in a mature language, while adding the PHP-style ?: operator to C would be a backwards compatible change. Oh, it'll probably never happen, but I'm pretty sure ?: could be added to the gcc C compiler tomorrow morning without breaking a single line of existing code.

Date: 2008-07-28 01:54 pm (UTC)
From: [identity profile]
There is an RFC to add an ifsetor operator to PHP. Unfortunately it has been rejected, mainly because it is possible to implement it directly in PHP.

but isn't ?: essentially:

(x = getCat()) ? x : emergencyBackupCat

? (or am i crossing languages)

Date: 2008-07-28 02:04 pm (UTC)
From: [identity profile]
Yes, and that's a workaround I neglected to mention, thanks. But ?: is still a lot less annoying to write. Also, x won't be local to that expression, so that technique can introduce a lot of potentially conflicting throwaway variable names hidden on the right-hand side of assignments etc.

I was originally going to acknowledge that my second objection is specific to PHP, which doesn't have variables local to blocks (they are local to functions as a whole). But an expression like that wouldn't be a block in C either.
Edited Date: 2008-07-28 02:05 pm (UTC)

Date: 2008-07-28 02:41 pm (UTC)
From: [identity profile]
Also, x won't be local to that expression, so that technique can introduce a lot of potentially conflicting throwaway variable names hidden on the right-hand side of assignments etc.

Well, if you feel like crapping all over any notion of the code ever being thread-safe/re-entrant (and this is php, right, so we might as well just take that dump and move on), you can just use one temp variable as long as you don't use nested ternary expressions (which is something that you should probably be shot for.)

I'm actually against the use of ternary expressions in code that has to be maintained anyway and imho anything that people want to use a ternary for that repeats like this is probably something that needs to get pulled out to a function.

I'm aware that php has the magic nilfalse, so that everyone and their monkey will be doing:

y = x ?: ydefault

but as you note it's actually:

y = isset(x) ? x ?: ydefault : ydefault;

which comes with a free barrel of wtf.

ranty today apparently.


Date: 2008-07-28 02:19 pm (UTC)
From: [identity profile]
I hate ternary operators. Hate hate hate.

It's like jargon in any other closed group. It's not obvious to people who aren't "in the know" and obscures the readability of the program. It's incredibly annoying in learning new languages.

While shorthand is always desirable in these situations and clearly if/then/else is a construct used so often that it's a very likely candidate for a ternary operator it bothers me that they exist. PHP is not Scheme.

This is, of course, my opinion and those who want to save thousands of keystrokes in programming will disagree with me (and I understand that desire), I feel ternary operators are archaic throwbacks.


Date: 2008-07-28 02:22 pm (UTC)
From: [identity profile]
In the general case I tend to agree with you, but the ? : / ?: operators solve a problem that every programmer worth his or her salt eventually gets well and truly sick of.


Date: 2008-07-28 02:44 pm (UTC)
From: [identity profile]
I agree! But fix it in a way that is readable!!

Date: 2008-07-28 03:03 pm (UTC)
From: [identity profile]
Other dynamic languages achieve this by making the && and || operators return the last truthy-value evaluated on success instead of a simply binary true/false. So your example in, say, ruby (and I think perl as well) can be:
cat = getCat() || emergencyBackupCat

Date: 2008-07-28 03:05 pm (UTC)
From: [identity profile]
I'd like to point out that I like this better than this invented ?: operator. Especially seeing as it's common in texts to call the trinary operator the ?: operator and this strange overloading just confuses things.

Date: 2008-07-28 03:11 pm (UTC)
From: [identity profile]
Yeah. I think it's possible that even PHP does this, though I haven't checked. Perl certainly does.

I'm annoyed by the fact that my preferred editor for PHP doesn't understand the ternary operator for proper syntax highlighting, so any time I use it, the colours below that point get fucked up. Not really PHP's fault, but.

Date: 2008-07-28 03:30 pm (UTC)
From: [identity profile]
Hey, you're right. And I used to know that, back when I used Perl more often.

But PHP explicitly does not do this ( (the || operator returns strictly TRUE or FALSE in PHP), and that is the only reason the feature is necessary.

I wondered if I'd somehow missed this feature in C for a bazillion years, so I checked up on that too. As it turns out C behaves as PHP behaves. So ?: would still be an interesting addition to C (in the very unlikely event that any additions to the core of C were ever accepted again), since altering the behavior of || is not acceptable in a mature language (writing code that depends on the fact that || returns 1, not simply a nonzero value, is wacky but somebody's undoubtedly using it as an optimization somewhere in the Linux kernel).

Date: 2008-07-28 03:38 pm (UTC)
From: [identity profile]
Bah. In my day, C's || operator returned zero for false and nonzero for true, and that was all you could count on. Kids these days.

Date: 2008-07-28 03:41 pm (UTC)
From: [identity profile]
Funny. I did write some K&R C in the very beginning, perhaps that's why I never got too cozy with the idea of == returning 1 (nor did I start trusting that it would return anything else in particular).

Date: 2008-07-28 03:30 pm (UTC)
ext_86356: (Default)
From: [identity profile]
This is one of my favorite Perl idioms, to the degree that when I go back and write C I find myself forgetting that the operator doesn't work that way there. What a loss.

Another favorite is using "||=" as an initialization operator:

$foo ||= "DEFAULT";

is equivalent to

$foo || $foo = "DEFAULT";

which essentially gives $foo a default value if it starts out undefined (or otherwise set to some false value).

Date: 2008-07-28 03:40 pm (UTC)
From: [identity profile]
I've edited the original post to give proper props to Perl, although I still dig the fact that PHP's solution would actually work as a backwards-compatible addition to C.

|| and ||= aren't good for checking for undefined stuff if you're doing use strict and -w, as you should be, but the same problem exists in PHP. Thus the separate proposal for ifsetor.

Date: 2008-07-29 04:08 am (UTC)
ext_86356: (Default)
From: [identity profile]
|| and ||= aren't good for checking for undefined stuff if you're doing use strict and -w, as you should be

Eh? I believe you are mistaken. :-) Certainly you have to declare $foo before you use it, but that's a different kettle of llama entirely -- once declared, those constructions work just peachy keen.

I understand that this could not be implemented in a backwards compatible way in PHP, but as far as I'm concerned that's just yet another reason to avoid PHP like the plague.

Date: 2008-07-28 04:18 pm (UTC)
ext_181967: (Default)
From: [identity profile]
One software development team I worked with banned the use of the ternary operator in production code. I fully support this move, because all the ternary operator is is syntactic sugar. Any non-trivial compiler is going to render it internally as the equivalent of an if-then-else construct, and it's not going to get optimised any better. In the mean time, you've sacrificed readability AND opened yourself up to potential precedence issues if the tests or assignments are non-trivial. In the end, it's a clever, space-saving shorthand that can wind up causing you far more trouble than it's worth, and let's face it, space isn't exactly a rare commodity these days.

As to Perl's ||= behaviour: it works really great until you use it in a situation where zero is a valid value for whatever you're assigning, i.e. my $x = shift; $x ||= 1; will assign 1 to x even if the shifted value is a perfectly useful zero. In this case you wind up using $x = 1 unless defined( $x ); After going back and fixing that bug a few times, you get very wary about using the ||= construct.

Just sayin'.

Date: 2008-07-28 04:33 pm (UTC)
From: [identity profile]
In ruby, 0 is truthy.

Date: 2008-07-28 04:50 pm (UTC)
ext_181967: (Default)
From: [identity profile]
Seems to me that that'd cause more problems than it solves?

Date: 2008-07-28 05:01 pm (UTC)
From: [identity profile]
It would if it were a language with a legacy, but imo it's Right. 0 being false causes a lot of ugly code, and is just a legacy of the type-sparse C language. That other languages copy it when even C and C++ have boolean types now is actually really annoying to me.

Date: 2008-07-28 05:03 pm (UTC)
From: [identity profile]
So I was following along for a bit, but my brain kept dragging me back to BASIC so I couldn't help but wonder why you were trying to PRINT everything.

I need more vacation time.

Date: 2008-07-29 07:21 am (UTC)
From: [identity profile]
Your example is pretty weak given the alternative, which I think is much more readable:

cat = getCat();
if (!cat) {
cat = emergencyBackupCat;

September 2014

2122232425 2627

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Oct. 17th, 2017 10:18 pm
Powered by Dreamwidth Studios