Hacker News new | past | comments | ask | show | jobs | submit login
No ifs...alternatives to statement branching in JavaScript (javascriptweblog.wordpress.com)
57 points by carloG on Aug 6, 2010 | hide | past | favorite | 44 comments



Dislike of if confuses me. People claim to find conditions wordy or confusing. I don't find conditions particularly difficult to follow, but I have a question from the other side: when you use && and || as logic gates, don't you (in your mind) translate them back into conditions anyhow? Here's his first example without if:

    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.


The first line is perfectly fine to me, I read it "event is either given event or event from the window".

The second part is convoluted because of poor responsibility handling: ideally this function would never be called without some event present.


Simply, no. I prefer to think in boolean expressions rather than sequences of statements. Personally, I'd further reduce it to (assuming there's no JavaScript subtleties I'm missing here; in C/C++ this would have the same semantics):

  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.


In the OP's example, evt will retain it's value if passed in as "truthy" or take on the value of window.event otherwise. The return then returns evt and evt.target or evt.srcElement which may from the original passed in evt or really from window.event, which was assigned to evt in the first line.

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.


But that crashes on null in all three languages, no? (i.e. if evt is null and window.event is non-null.) I suppose you could do this:

  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.


In C/C++, evt could never be null because it would have to be a stack allocated object or a reference. (We're not dereferencing a pointer.)


Gotcha. It's been a while!


Look at it like this. I am a developer who prefers the terse code compared to the longer if() variety. I've been doing this long enough that its is easier, clearer, and faster for me to understand the "evt = evt || window.event" than it is to trace through all of those if() statements.

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!


I agree that in some cases, guards, defaults and ternaries flow more naturally. However, I feel that there's a contradiction. You say that it is easier to understand but have learned to write them in plain old "if" first because it's better. It's easier to get it wrong if you write it shorthand. If it's better to start writing them in "if", doesn't that mean it's easier to understand? If it is easier to use guards, defaults, and ternaries, you shouldn't have to resort to plain "if" to get the logic right. And I agree, DOCUMENT!


We sometimes use if-else statements to write out some straight-up logic because we don't understand it at first. So we build it from the ground up. If, after writing it all out with primitives, we realize "Oh, it's just this anded with this and ored with that" then we actually understand the problem.

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 got to agree here. The short form is great for those ubiquitous "set if null" pieces of code, but you wouldn't want to use one for a complex conditional. The advantage comes in when you recognize the intent from the way it's formulated, and so don't have to trace the syntax. If your clause is something like "y%4==0 && (y%100!=0 || y%400==0)", you may want the long form, even for null y. I feel the example return statement is pushing it.

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.


  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.


And for god's sake, DOCUMENT!

What clarity would documentation add to this example?


// I don't like the keyword "if" so I'm hiding it with && and || because I haven't thought deep enough about a better way of structuring my code

// TODO think harder


I'm guessing you must be joking?


I'm trying to elicit a wry smile from those who understand but those comments describe the OP.

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;


Did you read the entire article?


Yes, it's the sort of thing every programmer goes through on the road to enlightenment


You said "I don't like the keyword "if" so I'm hiding it with && and || because I haven't thought deep enough about a better way of structuring my code"

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.


Also not cool is to be a patronizing prick. If I may sprinkle a bit of anti-troll powder: I liked your article and admire anyone who approaches his craft with that degree of thoughtfulness. There's relatively little flamewarism in this community overall, and you'd make a pretty good participant, so please feel welcome.


"That's very clever."

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.


Hi - I 'm the author of this post. A few comments:

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 :-) )


From the title, I was expecting something interesting like "SubText", or at least some kind of talk about dispatch :) Using &&, || etc gets you terse code, but the branching that you mentally work out is exactly the same.

.. 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.


Write readable code for your source files and then compile with Google's closure compiler.


I'm a very young coder and loved the article, especially how it brings functional features to imperative languages.

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.


What if you did? Would it be a better framework? Would anybody care at all?

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).


It would not be for cleverness, much less for optimization (is it even faster?)

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.


Maybe it's a coding style that suits you then. That's cool.

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.


This isn't a functional technique! Functional languages remove assignments and mutable variables. Not branches.


Branches too. The only conditionals I've seen in functional programming are equivalent to the ternary operator.

Unless I'm absurdly wrong, which can happen.


The program still branches (decides which code path to follow) - it just does it with a different mechanism, for instance pattern matching.


Yes, pattern matching fits the bill, thanks.


I think codesearch can answer you: http://www.google.com/codesearch?q=if+lang%3Ahaskell


"if"s in Haskell work exactly like the ternary operators, just as I said. I was using the article's differentiation of "branching" and "microbranching" and stating that I haven't seen the former in functional languages.

The link provided doesn't contradict any of this, actually it does quite the opposite.


Doesn't this depend on strict lazy evaluation? I could imagine if the implementation underneath changed (possibly to take advantage of parallel execution), if any segment had side effects, it would be horrible.


I think you're confusing lazy evaluation with operator short-circuiting. (http://en.wikipedia.org/wiki/Short-circuit_evaluation)


I can't think of a case where lazy evaluation would be required, only that the semantics of the && and || operators not change.

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.


Thanks


One of the things I love about Google's Closure Compiler is that it takes care of these optimizations for you. If you want, you can write in an explicit readable style and still get any performance benefits.


But these are not performance optimisations.

Angus is making the case that functional techniques can be more concise and readable than procedural style control flow.


Are short-circuiting boolean operators as conditional-plus-result-rolled-together really a functional technique? Certainly in strongly typed functional languages like Haskell they're impossible, because his example isn't even well-typed:

  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.


> evt && (evt.target || evt.srcElement)

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.


A collection of ifless techniques for those of you lucky enough to read portuguese:

http://alquerubim.blogspot.com/search/label/ifless


Eventually you will learn to stop doing this.




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: