Hacker News new | past | comments | ask | show | jobs | submit login
Ifs and &&s and Plan 9's Source Code (computationallyendowed.com)
190 points by jayferd on May 24, 2013 | hide | past | web | favorite | 138 comments

This also provides an advantage when debugging. It will become immediately obvious which condition fails when stepping through the code. That isn't always the case with a long string of &&s.

^ this is a detail which boggles my mind. why are our debuggers still line based? they're clearly not in every respect since you can basically always 'step in'to an && sub-expression, but nothing displays progress as you step through such things, nothing lets you put a breakpoint at some sub-expression, nearly every feature of every tool is delineated by lines as if they're the important part of a program.

Because something you do relatively early in the compiler is throw away all of the structure of the source, including flattening nested expressions into a linear IR. Mapping back to line numbers in the debugger is a bit hacky to begin with, and mapping back in an even more fine-grained way would be more complex still.

Both line and column information are completely carried through the process, and put into debug info, these days.

It's not hacky, it's actually completely well structured. Even debugging info for C/C++ macros is there and done properly.

None of this is new. DWARF2 (or 3 or 4), the standard debugging format for all non-MS compilers, has been able to do this well since 1993.

It's hacky in the sense that it's somewhat ad-hoc the manner in which line/column information makes it through from the source text, through the intermediate representation and optimizations, into the generated code and debug information.

In Clang/LLVM, for example, nodes are annotated with debug information using LLVM's metadata system. However, it is strictly optional for any given transformation to preserve the metadata on IR nodes. So what makes it through after optimizations is kind of a "best effort" affair.

But this is not ad-hoc at all. This is how it is deliberately designed so it degrades gracefully depending on what optimizations you choose.

Not all compilers even really degrade. GCC does post-hoc variable tracking (and thus is mostly unaffected by optimization except when values are optiized out completely). For declarations and statements, the info is always on the declaration, so it won't be lost.

Both compiler guarantee that at -O0, all debug info will be kept.

True (currently, I see no reason this is a necessary step), but that suggests you could perform trivial expansion of lines like `if (a.what() && b == c && (d == f || d < 5))` into multiple lines like you see in this article, and then use the exact same hack to get those pseudo-lines into the final stages, and into your debugger. You could even explode each piece into extra variables, so you can see the results of `a.what()` without re-evaluating it.

Honestly, even if you had to hit an 'expand this statement' button in your debugger to see `if x() && y()` spread into:

  x_val = x()
  if x_val
    y_val = y()
    if y_val
it would completely remove the necessity to write strange things to get around this limitation. Why do we have compilers and a huge variety of languages if not to stop writing strange things unnecessarily?

Even more beneficial, it would give you a much better idea of what, in fact, the computer thought you meant. Seeing a complex nested structure flattened out would give you a more visual indication of what's going on, allowing you to spot misunderstandings earlier.

I don't think it's any harder to carry line-and-column annotations through the compilation process than it is to carry just line annotations. In fact many compilers do. Of course your debug information gets larger, but that's not usually a problem during development.

On the other hand, an optimizing compiler already makes it pretty hard to single-line-step through a program (what with reordering, CSE, and more sophisticated transforms). Single-expression-stepping would be an even more difficult "debugging illusion" to provide.

So let's separate out the producer and consumer.

On the consumer side, knowing where you are is actually the least hard problem in optimized debugging, compared to things like tracking variables that got split up into multiple disjoint registers, or part in register/part in memory, etc

As for where you are, the line table already will tell you that the column number changed on pc address advance, but line number did not. Thus, you know that you moved an expression, but not a line.

GDB doesn't happen to support this, and simply looks steps until line number change.

But it's not fundamentally hard from the debugger perspective.

On the producer side: When it comes to knowing where you are, you know you can't produce a 1-1 mapping, so you don't try. You can of course, properly present inlined functions as if they were function calls, and gdb will even do this. But there are times when lines or expressions were merged, and there simply is no right answer.

With visual studio and c# you can put a break point at a certain point in many expressions and while in the debugger you can have it evaluate expressions individually when hovering with the mouse.

Stack traces, however, are still line based and thus if an exception occurs (like null reference) you only get the line, not the statement.

Not all debuggers are line-based. Many of LLVM's tools give descriptive errors ("expressive diagnostics").

Here's an example of gcc versus clang (a "frontend" for LLVM):

  $ gcc-4.2 -fsyntax-only -Wformat format-strings.c
  format-strings.c:91: warning: too few arguments for format
  $ clang -fsyntax-only format-strings.c
  format-strings.c:91:13: warning: '.*' specified field precision is missing a matching 'int' argument
LLVM is does some cool stuff :). Some other nice examples are at http://clang.llvm.org/diagnostics.html.

That's not a debugger though; that's a compiler.

All of the debuggers I've seen are line based with the option of single stepping through assembly.

Hopefully the LLDB people will be able to modernize debugging to the degree that the clang guys have modernized error messages, but that still remains to be seen.

Given that LLVM is way behind on producing good debug info compared to GCC, i don't think LLDB is going to improve as quick as one would like.

To be fair, that page is severely out of date, and gcc 4.2 was released six years ago. An updated comparison is here: http://gcc.gnu.org/wiki/ClangDiagnosticsComparison

Anyway, those are compiler warnings rather than after-the-fact debugging. I don't think LLVM's debugger, lldb, does any better in this case.

Very interesting point! There is an argument that lines are a unit of human comprehension and complicated expressions should be broken onto different lines already, as a matter of readability. It makes some sense to let the granularity at which you debug reuse the granularity at which you read.

If you're debugging though, presumably there's something you don't understand, so a debugger should be more granular than what you normally deal with since reading wasn't enough. In many ways they are, since you can check other things in scope (or any parent scope) which is not always visible in source (e.g. a callback has multiple stack entries outside the code that defined it, even if inline).

Gdb for g++ may be; msvc for c# is not! Gdb usability and performance is a (THE) major shortcoming of the gnu tool chain.

I've worked with debuggers that allow you to step the short-circuit operations.

That said, a huge cascade of them is something I try to avoid. Two or three, okay. Nine or ten, break it up and make it clear and readily debuggable.

This also makes individual conditions slightly easier to comment out.

And yank/cut/copy, and re-order, and type-over, and... I have developed similar habits after using line-based editors. This is very easy code to modify. (Notice the return type on a line all its own.)

Exactly. You could easily consider this to be defensive code in a similar way as always using 'break' with the last case of a switch-case is defensive.

I hadn't considered this before... interesting!

Ouch. I'm pretty sure that the compiler I worked on 22 years ago could stop at sequence points, e.g. after the evaluation of the LHS of && and ||. (OK, actually it was a superset of those, called "gesornenplatz points" in-house, but that's another story.)

Ignoring the "no space after if/while/for" issue, I'd suspect that code to not have been the author's intent if I just came across it.

Thought the same. If I need three && in an if condition, I nest (although I add brackets, I prefer having them). It makes easier debugging, easier reading and in case of need they are a good place to put some printfs

How about using this alternative form:

  if((b != nil)
      fprint(2, "cp: %s and %s are the same file\n", an, bn);
      ret = 1;
It keeps almost the same visual look and it uses the common convetion, except for the && at the beginning of each line.

I still prefer this:

  	b != nil &&
  	b->qid.type == a->qid.type &&
  	b->qid.path == a->qid.path &&
  	b->qid.vers == a->qid.vers &&
  	b->dev == a->dev &&
  	b->type == a->type
  ) {
  	fprint(2, "cp: %s and %s are the same file\n", an, bn);
  	ret = 1;

We can go deeper...

  	b != nil &&
  	b->qid.type == a->qid.type &&
  	b->qid.path == a->qid.path &&
  	b->qid.vers == a->qid.vers &&
  	b->dev      == a->dev &&
  	b->type     == a->type
  ) {
  	fprint(2, "cp: %s and %s are the same file\n", an, bn);
  	ret = 1;
(Lined up the "a"s to make it obvious that they're all the same.)

I'd prefer some variation on:

    int samedirfile( Dir *a, Dir *b )
            if( a == b )
                    return 1;
            return  ( a && b ) &&
                    ( a->qid.type == b->qid.type ) &&
                    ( a->qid.path == b->qid.path ) &&
                    ( a->qid.vers == b->qid.vers ) &&
                    ( a->dev      == b->dev ) &&
                    ( a->type     == b->type );
    if( samedirfile( a, b ) ) {
            fprint(2, "cp: %s and %s are the same file\n", an, bn);
            ret = 1;

I think it's fascinating that we prefer styles that are almost opposites:

    int samedirfile(Dir *a, Dir *b) {
        if(a == b) {
            return 1;

        return (a && b)
            && (a->qid.type == b->qid.type)
            && (a->qid.path == b->qid.path)
            && (a->qid.vers == b->qid.vers)
            && (a->dev == b->dev)
            && (a->type == b->type);
    if(samedirfile(a, b)) {
        fprint(2, "cp: %s and %s are the same file\n", an, bn);
        ret = 1;

I'll note I actually have a slight preference for prepended continuation operators like you have, but I stick to the style used at work for the sake of my sanity in trying to write consistent code.

I'm so, so, so glad that new languages are banning braceless if/else bodies.

So how about that python? </trololololo>

I should have of course said "some" :)


I'm not sure if you're trolling or not... but isn't that the point of the function in the first place...?

Putting the &&'s at the beginning of each line makes the overall shape of the logic expression easier to percieve: you can prove they're all one big 'and' expression without having to hunt for the end of each line.

  if(b != nil){
    fprint(2, "cp: %s and %s are the same file\n", an, bn);
    ret = 1;
I will never understand the allergy to multiple braces on one line.

I think the aversion is because the way that they line up visually is the opposite of the way that they are matched by the parser. I think everyone needs a dose of LISP to get over any problems they have with any frequency or arrangement of brackets/parens/braces. Anyway, having a function that compares two structs and returns 0 or 1 with a printf out to stderr is just gross in my opinion.

Using ifs is definitely more readable than &&, words always win. However carefully alignment of && and ( can make things better. Note that the comment after "if" is necessary to keep the shape balanced.

    if( /* b is same as a */
           (b != nil)
        && (b->qid.type == a->qid.type)
        && (b->qid.path == a->qid.path)
        && (b->qid.vers == a->qid.vers)
        && (b->dev == a->dev)
        && (b->type == a->type)
        fprint(2, "cp: %s and %s are the same file\n", an, bn);
        ret = 1;

"words always win"

Add seven to three then multiply the whole thing by twelve.

(7 + 3) * 12

(mult (add seven three) twelve)

Better, obviously, but I think the regular math notation still wins. It's more concise, and equally (if not more) familiar.

only if you treat symbols as a foreign language

One downside to non-braced conditionals is that a semicolon accidentally placed after the conditional will cause the block to always run, e.g.:

  if (null != foo);
This is valid code in C and Java, and bar() will always run in this case.

Having seen people waste hours on such a semicolon, I always use braces, even in one-liners, because I never know when someone is going to break it out into multiple lines later:

  if (null != foo) { bar(); }

I was going to say that a compiler should issue a warning for this, as you'd almost never want a semicolon right after an if condition, but to my surprise Eclipse doesn't seem to flag it.

It does however indent the line after the semicolon to the same level as the if, which is at least a red flag that something is up, if you are used to how the auto-indenting normally works.

Absolutely, indentation helps catch this when writing.

When I've seen this happen, it wasn't because of a semicolon added when the code was written. It was someone accidentally adding a semicolon to a line later, without realizing it. Unless they then went to the next line and hit the "fix indentation" key, they didn't catch it.

Both clang and gcc warn on "if(1);", clang by default, gcc with -Wextra. clang also warns on "while(1);" - I think this is somewhat obnoxious, since it can be useful, and would prefer if it only warned if the semicolon was followed by an opening brace, but YMMV.

I personally have never put a semicolon after a condition and being mainly a C# developer I have written a lot of them. Is this really something that happens more than once or twice in a lifetime?

Yes, but such mistake, once made, is very hard to notice (because you never do this, you don't look for it), so it may well lead to a fatal disaster. Hence 'once in a lifetime'.

I don't think braces solve that particular problem:

    if (null != foo);

It does if you use the One True Brace Style:


since your eyes would flag:

    if (null != foo); {
as badness.

[edit: correct } to {, ta]

As someone who programs in Go a lot these days, that line (I assume you meant the first curly to be { and not }) looks less obviously wrong than it would have in the past when I did more C/C++ programming.

Because I've gotten used to Go's support for short assignment in an if, the semi doesn't look completely out of place there (though the construction here is not valid Go either).

I agree. This is we do

   if (null != foo) bar();

   if (null != foo) {

I know, let's start a language war thread: "That's why I use Python." ;)

More seriously, after more than 10 years of writing C, C++ and Java, I've never run into this problem. I still wrap mine out of habit (to prevent this dreaded occurrence), but I think good testing would obviate any need for this.

As an idiom I quite like this form. I often do similar for double iterations:

   for (int x = 0; x < width; x++)
   for (int y = 0; y < height; y++) {
      do_something(x, y);

There is a similar [common][1] idiom with `using` in C#. When you need to create and later dispose several resources, to avoid excessive nesting you write

    using (var outFile = new StreamReader(outputFile.OpenRead()))
    using (var expFile = new StreamReader(expectedFile.OpenRead())) 
[1]: http://stackoverflow.com/a/1329765/458193

I actually quite like that, although usually when I have a large conditional like that I just give it named boolean variables to make it read more like a sentence, IE:

    bool exists = b != nil;
    bool sameType = b->qid.type == a->qid.type;
    bool samePath = b->qid.path == a->qid.path;
    if(exists && sameType && samePath) {
It's also a lot easier to inspect in a debugger, as all the conditions appear in the locals list.

You could also group related conditions, so that nested if's don't bloat the locals so much:

    speedIsSlow = dict(downstreamSlow = downstreamSpeed < 200, 
                       upstreamSlow = upstreamSpeed < 100)
    if any(speedIsSlow.values()):
        connectionIsDead = getPingTime() > 100
        if connectionIsDead:
                print("It's dead, Jim")

As a side note:

is interesting. This is not how you compare strings in C. So it is either a bug or dirstat() needs to guarantee that different results pointing to the same file always share the path string.

This line is comparing integers, not strings.

          Qid is a structure containing path and vers fields:
          path is guaranteed to be unique among all path names cur-
          rently on the file server, and vers changes each time the
          file is modified.  The path is a long long (64 bits, vlong)
          and the vers is an unsigned long (32 bits, ulong).  Thus, if
          two files have the same type, dev, and qid they are the same

I use this convention often for loops:

    for (int x = 0; x < width; x++)
    for (int y = 0; y < width; y++)
    if ((x + y) % 3) {
      // ...
The semantics are kind of like using a comprehension.

I abhor deep indents, and when I have deeply nested loops that just serve to trivially enumerate things, in C I sometimes add a helper function for the iteration.

e.g. instead of:

    for(int i=0;i<N;i++){
        for(int j=0;j<K;j++){
            for(int k=0;k<L;k++){
it might become

with all_frob_indices() doing the i++; if(i>N){ i=0; j++ }...

This makes code look more similar to e.g. itertools-constructs in python where you can easily make products, zips, ... from iterables.

Doing this stops most compilers putting the index variables in registers, with knock on effects on array indexing efficiency.

Not necessarily a bad practice, but something to be aware of.

It really is a bad idea, though, for the other obvious reason. It goes from being something any programmer can figure out immediately to something that requires additional thought to understand.

The only upside is that it satisfies someone's indentaphobia. No, thank you.

Not really a bad idea, imo. The upside is that you cannot make a stupid typo in these nested loops, which is really easy to do, when you have to write the same thing several times in several places of your program. Also, it is easier to refactor, when the need will be.

The biggest problem I see in doing this is that since C++ does not have yield statements / coroutines / continuations you need to write the iterator code "inside out" and with explicit state, something that usually ends up harder to read than a regular nested-for-loop iteration.

Notice that your own post illustrates why this is a bad idea; whereas

    for(int i=0;i<N;i++){
correctly steps through `N` `i`-values, a version with your `all_frob_indices` would step through `N + 1` `i`-values (because your termination check is `i > N`). Of course, it's easy to fix this once it's spotted, but you won't spot it with the same ease that you'd spot an off-by-one error in a `for` loop, because your eyes (or at least my eyes) don't know how an `all_frob_indices` function should look as well as they do how a `for` loop should look.

I often use: if (false) { } else if (something) { } else if (something) { } To get some indentations right.

When constructing UI elements in code, I used to scope copy/pasted blocks: { Button button = xxx; // and some more }

{ Button button = xxx; // and some more but different }

In the rubydays it'd be: Button.new do |b| something end

If not for the need to get and later free the

    Dir *b;
, and print the message, this condition would get a lot simpler. I'd find it more readable to write a function

    bool samefile(Dir *a, Dir *b)
, have it consist of a single-line return statement, and then have the caller just do if (samefile(a, b)) { ... }.

I think the meaning was fairly obvious at first glance, and it read as a sloppy way of conjoining expressions, where && should be preferred, because it doesn't rely on the arbitrary block rules of if expressions.

Imagine for a moment that a stray ";" ends up at the end of one of those if statements. I don't care how, perhaps you just dropped your Warby Parkers on your keyboard or something. Not only is the code now broken, but it still compiles. With &&, it would fail to compile and the error would be caught immediately.

The moral of the story is that syntax is your friend, not your enemy. Use syntax as much as possible to catch errors. Especially with strictly-typed languages, you have an incredible tool for automated verification of certain portions of your program. Use it.

I think the meaning was fairly obvious at first glance, and it read as a fine way of conjoining expressions, where multiple if statements should be preferred, because it doesn't rely on the arbitrary subexpression evaluation ordering rules of binary operators.

Imagine for a moment that a character ends up deleted at the end of one of those && expressions. I don't care how, perhaps you just dropped your Warby Parkers on your keyboard or something. Not only is the code now broken, but it still compiles. With if(...)s, it would fail to compile and the error would be caught immediately.

The moral of the story is that syntax is your friend, not your enemy. Use syntax as much as possible to catch errors. Especially with strictly-typed languages, you have an incredible tool for automated verification of certain portions of your program. Use it.

Lazy evaluation is not arbitrary, it's a well established convention that any sane language implements (thus marking the languages that do not use it as completely insane and unworthy of our effort).

And also, your reply is technically incorrect, a typographical error or any kind is more likely to be caught in the && situation, and the style of reply makes you an insufferable geek.

Good luck using an auto formatter on a code base that uses this technique.

If I'm not mistaken, Plan9 comes with a code formatter, and this idiom is common in Plan9, so it seems likely that it is supported. http://plan9.bell-labs.com/sources/plan9/sys/src/cmd/cb/

I believe that the layout of code is done by humans for humans. An auto code formatter destroys all of that meaning.

I blogged about this more here: http://www.databasesandlife.com/do-not-use-automatic-code-re...

Yeah, I saw that. I think it's misguided. The amount of time spent on arguing about style far exceeds the time savings of the changes you propose. People can get used to different formats quite easily, but time spent arguing about and maintaining formats can never be recovered.

Also, your formatter looks pretty brain-dead. clang-format formats all of those cases (in C++) as in your green examples with Google style turned on. Even the Eclipse formatter should do a better job than what you've posted with the appropriate settings. When I advocate an auto-formatter, it doesn't make sense for you to respond with a blog post talking about a formatter seemingly designed to make everything worse.

I agree and for this reason, gofmt, an automatic formatter for Go code is possibly my favorite part of the toolchain. It is kinda like PEP8 for Python but formalized into a program (there is a third party pep8 program) or Emacs for Common Lisp. The advantage is that it is a standard tool.

So I can just run it over my code, no matter what editor I used and the code is fine. No arguing what is right, no thinking about where to put some stupid spaces or how to break a fucking line, just run gofmt, done.

"People can get used to different formats quite easily, but time spent arguing about and maintaining formats can never be recovered" This is nevertheless what this whole discussion is about :-) e.g. should one list those ifs straight like that, or use &&, etc.

"Also, your formatter looks pretty brain-dead", it is, alas, what Eclipse does, and that's pretty standard in the Java world, and it's also pretty standard to use its code formatter alas.

When working in a team, human code formatting can cost a lot of time. The rule is generally to have an auto code formatter that runs when we do a commit. The choice of formatting rules is made collectively and can be discussed. The usage of an auto formatting is generally mandated by society policies. I think it is a huge time saver (not using an auto formating tool is way to lose a lot of time).

For what it's worth, Vim handles it just fine.

What? Not for me, not with cindent on. It indents each if statement by one level, just like it "should". How would it somehow know to not indent them?

An auto-indenter could easily detect the idiom if hit backspace after the auto-indent it produced after the first 'if'.

Once you have your two ifs at the same indent, the indenter can use that info to keep them at the same indentation level. It could even assume you mean (a&&b) vs (!a&&!b) and not the three-way (a&&b), (a&&!b), (!a) when you follow it with an else block (I think that would be the best heuristic)

Implementing this will get a bit hairy, but if you are writing an auto-indenter for C, you should be used to that.

So, all you would have to do is hit one extra backspace to signal your intent.

Alternatively, one could type

  if(a) if(b) {
To signal that one wants

  if(b) {
Same number of keystrokes, and, IMO, that space is slightly easier to type than the return it replaces.

None of the existing auto formatters that I'm aware of do this.

That's all good and well, but vim does not do this and, more generally, does not support this style, which is what I was saying.

Great, now add an "else".

In ruby, the equivalent to a switch (confusingly called case) can take no initial-compare field and basically become a chained-if that lines up nicely (or more nicely than elsifs). I prefer the visual look of it, personally, but it seems that a lot of people find it too confusing.

As an example:

  when (a == 1)
  when (b == 2)
  when (c)

In Mathematica the FullForm of a && b is And[a, b]. You can use this to format long lists of conditions as

You can also rely on the fact that logical expressions are always short-circuited, so False && Print["won't be printed"], doesn't print anything.

Finally, you can put a bunch of conditions in a list and do And @@ list.

> You can also rely on the fact that logical expressions are always short-circuited, so False && Print["won't be printed"], doesn't print anything.

This is true for any language I can think of.

E.g. in perl

    open(my $fh, "<", "input.txt")
        or die "cannot open < input.txt: $!";
is idiomatic

C++ has "and"/"or"/"not" etc., so you can do

    if(not A and B)
but I'm not sure I've ever seen anyone do it. http://en.cppreference.com/w/cpp/language/operator_alternati...

To get them in C, see the note about iso646.h in the link.

I don't think it really improves readability. All the programmers are familiar with && and ||, and using something else will just confuse people.

Also, it's not really that hard to #define it yourself, instead of using includes.

I definitely wouldn't use them because they are unfamiliar. However, in my head I pronounce "&&" as "and" and "&" as "bitand", so using and/bitand over &&/& would reduce a depressingly not-uncommon typo for me. However, on balance, not worth it to me.

Note that in C++ they are keywords which is superior to macros.

How are they superior?

For one, a macro "and" would prevent you from using "and" as the name of a struct field, while a keyword will not. Error reporting will be better too. The differences don't seem all that big though.

When you use the fact that && is not the logical conjunction, you'd better use another if.

    if (b != nil)
    if (b->bla == a->bla)
is so much clearer (and language agnostic) than

    if (b != nil && b->bla == a->bla)

I've never heard about writing language agnostic code in my life, lol!

Oh god that code is fucking hideous.

If someone who worked with me wrote that I'd talk to them about it and make sure they never did anything like that ever again.

From the terrible argument names to the abuse of the single line if syntax (which should never be used anyway, always use curly braces.)

Makes me wonder what the quality of the architecture is on your projects is though. Not that I think coding style is unimportant, but it's all too easy to criticize yesteryear's code with today's style standards and totally miss the forest for the trees. Are you doing work that measures up to the ambition of Plan 9?

We all know how well that ambition turned out.

Yes, (1) commercially it went nowhere, (2) technically it's a complete working os with original ideas implemented in a masterful way. Not bad I think!

I can say the same for many other OSs, like BeOS or Native Oberon, just to name two of them, but in the end it is (1) that counts.

It is like football, it does not matter how brilliant a team plays, but which one has higher score when the referee signals the end of the game.

IMHO, (1) is the not only think that counts in the end. A 'failed' (as in (1)) concept may have a second life in future works. Also, in the case of plan9 some ideas did get traction (UTF-8 for example), some didn't so it's not all bad. Comparisons are tricky, you can compare an OS to a football team, and then yes maybe what you say is right. Or you can compare it to a work of art, then what matters is the influence the works has in shaping the future...

Yeah, maybe I was a bit too harsh.

It is just that I personally never found Plan9 that interesting, except for its successor Inferno and Limbo.

For me operating systems that explore designs done with regards to micro architectures or safe systems programming languages are much more interesting.

So for me Plan9 tends to be just another OS. And yes I have used it.

You know what never fails though? The middlebrow dismissal!

Maybe it comes from using Python as my go-to language (and that it's my favorite language) but i personally like to avoid non-essential braces and other minutia.

Yes, of course, I know the argument: a one-line bracketless "if" can set-up a future developer for failure if they need to add an item to the conditional block. And if they for some reason decide not to read the actual conditional. And if they don't test it.

And I don't hate code that uses braces even when they're not strictly needed, but I personally prefer to omit them. And my feeling is that the dogma around braceless-ifs is a little overblown.

Of course when working on a team that has adopted a no-braceless-if policy, I conform. Having consistent code is way, way more important than somebodies own favorite bracing style.

Odd, since python is all about using the best way and not variants, that you would want to have some with braces and some without braces.

Sure, being "pythonic" is about having one best way.

But you're taking it to a level for snark that really isn't warranted. I can think of at least 4 ways to iterate a list. It's about doing something the easiest/best way for the circumstance.

I find this code pleasantly readable.

How about this one? http://git.savannah.gnu.org/gitweb/?p=coreutils.git;a=blob;f...

No. Single line if syntax is good. Gratuitous curlies make the code longer and ultimately harder to read (since you can't fit as much on a page). Being able to see your whole function/algorithm at once is more important that almost any other consideration.

Thats hideous? I see worse code everyday.

Just because there's lots of worse code doesn't make that good.

There's worse songs than "Who Let the Dogs Out" but it's still a really bad song.

11k lines in one file - this is the way it should be done. ^^

You may find astyle and indent personally useful if/when you go hoarse (hackers aren't really known for their conformity).



What did you find wrong with the argument names?

It is always the worst developers who fixate on arbitrary rules because they never developed the ability to read code. Go practice reading code, that's the only way to get better at it.

It's a good thing they didn't use a goto, or this guy might have had an aneurysm.

Amazing how old C, and still obscure things always pop up here in HN. I had no idea of such idiom.

Perhaps the compiler wasn't relied upon to provide "short circuit" boolean eval? This code will compile that way no matter if it's available in the compiler or not.

Honestly for the sake of being more robust, I'd add if(a != nil) after the first test of b.

if(b != nil) if(a != nil) if ...

If the compiler can't do short-circuit evaluation, the compiler can't do C compilation.

Short circuit is guaranteed by the language standard. On top of that, plan9 comes with its own C compiler so the people who wrote this also wrote the compiler used to compile it.

I see, I just have no idea how complete the Plan 9 tools are. Working with in-house domain specific language interpreters or compilers I've twice seen unary minus support missing so I assumed anything was possible when something's not fully commercialized.

This popped up in some other thread recently. It was (still is, I suppose) a standard ANSI C89 compiler with a couple of small extensions and tweaks. Ken Thompson's short overview is here -


You can nerdmuse yourself a little by googling why it had a

    #pragma hjdicks

It's not complex at all. How can it be confusing? I was confused by the author's confusion :-/

My knee-jerk reaction to this post was that this increases the chance of another developer introducing a dangling else manyfold.


Hmm, I'm not a fan of an if without braces. It is just begging for someone to come along and stick multiple statements after it expecting them to run when it fires. Heck, maybe even me tired at the end of the day.

It looks weird without tabbing. But if the code was tabbed, then it would take too much space, since they are using huge tabs. That's why I prefer 3-space tabs. Not too small, not too big.

3 tabs? is there a language, community or editor that encourages this convention? usually the default for tab is 4 or 2, and 4 is the PEP convention for Python, while 2 is very commonly used around Ruby as far as I have seen.

btw there are no tabs specifically because it's meant to be read as a single statement, not nested conditions.

Following typical layout rules, there's a much easier way to get this conjunction without indents:

if(foo == bar

        && baz == qux

        && ! frob

        && whatever()

        && something) {

This isn't a new thing. I remember it being used in ZZT-OOP, where you didn't have boolean operators.

    #if foo #if bar #send obj:dosomething

And what's the alternative for if(A && B){ x(); }else{ y(); }? Seems very easy to shoot yourself in the foot with this approach.

Well, you could

    if (one) {
    if (two)
    if (three)
    if (soon) {
    }} else {
        else stuff
Personally, I think I won't have any trouble reading that, but I have noticed I'm somewhat more tolerant than others in this regard. Though I am more OCD than others in other ways.

That doesn't what he's talking about, though. Your else will run anytime `one` is false, not when `one && two && three && soon` is false.

But it's still not the equivalent of: if (one && two && three && soon) {stuff} else {stuff} which I assume the GP meant.

Here, the x() and y() are assignments, so it's easy:

    ret = 0; //y()
    if (A)
    if (B)
        ret = 1; //x()
If the else statement is more involved, it gets difficult.

The Gish source code does that too.


I hope everyone realizes that this code was probably written by Ken Thompson or Rob Pike.

This just looks like it was written by someone who's done some logic programming.

Have some fun unit testing that! ha

Registration is open for Startup School 2019. Classes start July 22nd.

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