No ifs...alternatives to statement branching in JavaScript
javascriptweblog.wordpress.com
javascriptweblog.wordpress.com
function getEventTarget(evt) {
evt = evt || window.event;
return evt && (evt.target || evt.srcElement);
}
Sure, it's very short in this written form. But for me to understand it, I need to think, roughly: evt retains its value, if it's a truthy value; otherwise give evt the value of window.event; if evt is a true value (now), return it and the value of evt.target - if that's truthy, or, if not, add in the value of evt.srcElement; oh, and by the way, if evt was not truthy initially in the return statement, bail out early (and return no value) since && only continues on if the initial value is true.I'm not trying to be difficult, but I don't see that as miraculously more clear.
I used to be the opposite, I couldn't understand the short form or how it worked. Ternaries confused the hell out of me for a while. Why?
Because when I look at "evt = evt || window.event", my mind has learned about the conditionals that each of these represent. Its analogous to phrases that mean different things in different contexts. "That guy has balls", for example.
So I look at it and I know that "if( evt )" is the same as "evt ? true:false" as "if( typeof evt !== 'undefined' ). I know that in the context of that statement, evt || window.event is saying "if evt exists, use that. if not, use window.event".
So the reason its confusing to people is they haven't learned all of the different things the shorter code means.
For me, after having spent many, many hours writing javascript I find the short form easier to understand and less tiring. Not only that, in raw numbers it just saves space.
Now, the other lesson I've learned over the years is that many times it is much better to start out writing in plain old "if" statements. Get the logic right, then reduce it down. There's nothing more annoying than trying to debug someone else's poorly written code.
And for god's sake, DOCUMENT!
Additionally, it only works if you can trust that what is written is what is intended - which can be a problem with the gotchas in the truthiness system. If you aren't sure what type the input will have, you'll want the explicit checks, and if you don't have a helper function, those can drag on long enough to make it unreasonable to expect the reader to see the guard pattern.
All in all, I consider the short form a form of self-documenting code. If your readers don't find it easy to get used to, it becomes obfuscation. YMMV.
Put another way: if you write a boolean expression as a sequence of statements, my assumption is you don't really understand what you're doing.
I've been doing this long enough that its is easier,
clearer, and faster for me to understand
However, are you also the only one reading the code? If not: is it also easier, clearer and faster to understand for the other ones? On the one hand, they may learn fast when reading code that uses these constructions. On the other hand: perhaps it only sinks in over time and they won't learn fast enough to properly maintain such code.What clarity would documentation add to this example?
// TODO think harder
It's a cute trick, useful in shell scripts but not serious code where readability should trump cute & terse. When I clicked the title I too was expecting some kind of method dispatch. Lets get some ternaries in there too
a && b || c ? (d || e) && f : f && d || h;In addition to talking about ternaries, guards and defaults, the article discusses myriad techniques aimed at replacing conditional logic with entirely non-conditional approaches (both functional and oop).These include function dispatching, function delegation, use of high order functions, functions as data and polymorphism.
These techniques aren't going to work for everyone and it would be very cool if you chimed in with your own experiences. What's not cool is misrepresenting the article.
The second part is convoluted because of poor responsibility handling: ideally this function would never be called without some event present.
function getEventTarget(evt) {
return (evt || window.event) && (evt.target || evt.srcElement);
}
A boolean expression is straight-up math, and I've been trained to reason about that. With a sequence of statements, I have to first figure out what you're doing, then extrapolate the implications. The implications stare me in the face when I look at a boolean expression.With your example, evt.target and evt.srcElement are only coming from the passed in evt which may have been passed in as "falsey" and would not take on the target or srcElement of window.event as intended.
I could be mistaken, it's late and I'm rusty. Anyone is free to correct me.
function getEventTarget(evt) {
return (evt || (evt = window.event)) && (evt.target || evt.srcElement);
}
But that's probably more trouble than it's worth. Anyway, I agree with you in preferring expressions to statements on the whole. It's hard to imagine why anyone would prefer the more than 4x as long version in the OP; to me it reads like a parody.The above quote comes from the smartest developer I've ever known, talking about a piece of code I had written that I was just as proud of as this guy is of his ifless branching. It took a minute to sink in that "clever" is not a term you want people using to describe your code.
As a developer, the first time you're going to encounter any piece of code is when FireBug drops you into it with an exception. Personally, I'd prefer to look at a single line that does a single thing.
For any non-trivial implementation of the author's chained implicit conditional logic, a null reference exception will leave you looking at a single line with a half dozen candidates for what might actually be throwing.
Please please please don't make a habit of coding like this for anything but the most trivial cases.
1) It wasn't my intention to create a holy war. There are good arguments on both sides. I use ifs and fors in my own code and will continue to do so.
2) Over the years I have developed a distaste for the overuse of statement branching - I find it distracting and I feel it works against readability.
3) I wanted to catalog a bunch of (mostly well known) alternatives to present as a coding strategy
4) I realize that not everyone likes such terseness of style and what is clear syntax to one person can be undecipherable to the next.
5) Use what ever works best for you and your team
(Notice how procedural this comment was :-) )
.. unless you use the && and || so much that you end up chunking out specific patterns of usage without having to explicitly think about the branching.
.. which you can do with if as well.
But what if I write the entire company framework without a single conditional (including loops)? Are this alternatives to be used sparingly or as much as possible? Would you frown upon when you see it?
On a side note, I would love to never see a conditional again Even before learning functional programming I felt bad at every "if" and especially every "for" loop, but now I know why.
I doubt it.
This a code style and if you like it, then maybe you use the parts you want. But "micro branching" is still branching and it makes little difference how you do it (except the nod to performance concerns towards the end).
Thinking about code style and readability is useful and good, but this is by no means a common way to code. Beware of excess cleverness. It would be far better to worry about the correctness of the code rather than optimizing for a meaningless benchmark (no ifs).
For some reason every time I see a condition I cringe mentally. There's something in a "for" loop that makes me uncomfortable and god, using "map", "filter" and micro branching would help a lot.
I'm just thinking of what I would like to see and checking it against the opinion of others.
I just caution against putting it in such simple terms. I hate it when I read code by somebody who latched on to the latest trend and then ruthlessly bent their style to match the new way, whether it made sense or not.
As for performance, it would have to be tested. In some languages with first-class closures, a lot of code like the examples might seriously strain the garbage collector (if present). There might be a ton of young objects to clean up, all the time. That was my only thought on it.
Unless I'm absurdly wrong, which can happen.
The link provided doesn't contradict any of this, actually it does quite the opposite.
It's defined as part of the language that && will not evaluate the right hand side unless the left hand side is truthy. A lot more than using this stuff as branching would break if some implementation arbitrarily decided to not follow that anymore.
Angus is making the case that functional techniques can be more concise and readable than procedural style control flow.
evt && (evt.target || evt.srcElement)
You can't apply a boolean operator to an event! And even if you could, the result would always be a boolean. I associate this most commonly with languages I wouldn't really call functional, like Perl and Ruby (I believe Perl popularized the technique).Even in Lisp, where you can write like that, it's much more idiomatic to use the if or cond forms, which is more morally equivalent to C's ternary conditional than to short-circuiting booleans.
It's certainly true that this isn't well typed (not to mention not valid Haskell, since the . is used for namespacing, not access to member variables), but there's no reason that one couldn't do something conceptually similar:
class Boolable a where
toBool :: a -> Bool
(&&) :: a -> a -> a
(||) :: a -> a -> a
a && b = if (toBool a) then b else a
a || b = if (toBool a) then a else b
instance Boolable Int where
toBool 0 = False
toBool _ = True
I don't know how idiomatic this is, but it works. One can see that it really is short-circuiting: > :l Boolable
[1 of 1] Compiling Main ( Boolable.hs, interpreted )
Ok, modules loaded: Main.
> 1 Main.|| undefined :: Int
1
> 1 Main.&& undefined :: Int
*** Exception: Prelude.undefined
I don't know Haskell well enough to know why the `:: Int` on the end is needed, but I'm sure that that, too, can be worked around.