Hacker News new | ask | show | jobs
by marcosdumay 36 days ago
Applying the increment or decrement operators over the same variable more than once on the same line should be a compile-time error.

Anyway, yes, this one example has an obvious order it should be applied. But still, something like it shouldn't be allowed.

2 comments

> Applying the increment or decrement operators over the same variable more than once on the same line should be a compile-time error

That would be nice, but don't forget the more general case of pointers and aliasing:

    int a = 5;
    int *pa = &a;
    printf("%d", (a++ + ++*pa));
The compiler cannot statically catch every possible instance of a statement where a variable is updated more than once.
Well, aliased updates are undefined behavior already.
Not in C, unless at least one of the pointers were marked `restrict`.
Honestly, having increment in expressions rather than a statement feels like more of a footgun than a benefit. Expressions shouldn't mutate things.
I think the history of this is that these operations were common with assembly programmers, so when C came along, these were included in the language to allow these developers to feel they weren't leaving lots of performance behind.

Look at the addressing modes for the PDP-11 in https://en.wikipedia.org/wiki/PDP-11_architecture and you'll see you can write (R0)+ to read the contents of the location pointed to by R0, and then increment R0 afterwards (so a post increment).

Back in the day, compilers were simple and optimisations weren't that common, so folding two statements into one and working out that there were no dependencies would have been tough with single pass compilers.

You could argue that without such instructions, C wouldn't have been embraced quite so enthusiastically for systems programming, and the world would have looked rather different.

Additionally, those indirect memory instructions ended up disappearing because it complicated virtual memory implementations. It was a pain in the ass to describe the multiple places in memory an instruction could be accessing and which actually faulted to a fault handler, not to mention having to roll back all that state on more complex designs.
I worked on a more recent custom AI ISA that had that too. Pretty neat; I'm surprised it's not more common. I guess it doesn't matter so much now that memory is so much slower than ALU ops.
Python recently went the other way and added an assignment expression. I actually wish more languages would go further and add statement expressions instead of having to imitate them with IIFEs.

C just wouldn't be C without things like a[i++]

If the past few weeks of CVEs indicate anything, it's that C being C maybe isn't a good thing...
Those things are for pointer golf and writing your entire logic inside the if statement.

Both are favorite idioms of C developers. And they are ok if done correctly, clearer than the alternative. They are also unnecessary in modern languages, so those shouldn't copy it (yeah, Python specifically).

In any language where the practice of iteration isn't achieved via C-style for-loops, having an operator devoted to increment just doesn't make sense (let alone four operators, for each of pre/post-increment/decrement). This is one of those backwards things that just needs to be chucked in the bin for any language developed post-2010.
When used well it makes for compact readable code. I don't see what it has to do with for loops or operators specifically. For example you can do the same in scheme while iterating by means of tail recursion.
> I don't see what it has to do with for loops or operators specifically.

The reason that these operators pull their weight in C is because iteration over arrays is achieved by manual incrementation (usually via the leading clauses of the for-loop) followed by direct indexing. Languages with a first-class notion of iteration don't directly index in this way, which overwhelmingly eliminates not only the vast majority of array indexing operations in codebases but also the need to manually futz with the inductive loop variable. Case in point, Rust doesn't have `++` in any form, and it doesn't miss it, because Rust has first-class iteration; on the then relatively rare occasion where you want do want to increment, you can do `+=1`, which doesn't have the footguns of `++` due to assignment being a statement rather than expression, while leading to a simpler language due to leveraging the existing `+=` syntax rather than needing a whole new set of operators.

For loops are hardly the only usecase and built in iteration constructs frequently fall short. For example any mildly complex loop that involves pointer juggling can benefit.

> which doesn't have the footguns of `++` due to assignment being a statement rather than expression,

So then I implement the local equivalent of inc( v ) and ... same issue, right? Plus with rust macros is there any technical reason you can't trivially implement ++ for yourself? That's the case for most lisps that I touched on earlier.

> For example any mildly complex loop that involves pointer juggling can benefit.

I'd say that when you're writing a mildly complex loop that involves pointer juggling, one should prefer to be defensive and explicit rather than cleverly trying to compress everything into one-liners.

> So then I implement the local equivalent of inc( v ) and ... same issue, right?

This isn't done in Rust because there's no benefit. It's rare to find an occasion where it's necessary to do something tricky enough to forego using iterators, and when working with raw pointers Rust code just plain doesn't use basic addition for pointer arithmetic; instead it has a variety of pointer arithmetic methods for being explicit about the desired semantics (e.g. ptr::add, ptr::offset, ptr::wrapping_add, etc).

> Plus with rust macros is there any technical reason you can't trivially implement ++ for yourself?

There's not, but people might look at you sideways. Here, I implemented it for you: https://play.rust-lang.org/?version=stable&mode=debug&editio... . It expands to nested blocks with internal assignments, which results in a well-defined semantics following the defined order of evaluation in Rust.

In Rust you hide all kinds of error prone iterations behind the "iterator" interface. Both the "for(int x=0;..." and the "while(list[i++])" are implemented at the standard library.

People tend to use FP abstractions for the "x[i++] = f(y[j++])" though, not iteration.

I always hate C-style for-loops because even thought I learned C over 40 years ago, I can never remember whether the increment comes before the test or the test comes after the increment. Fortunately, modern IDEs let me continue to be ignorant on those occasions when they’re necessary (usually because I need the index for some reason).
int d = foo ? bar() : baz();

I think if anything people have been leaning more and more into expressions over statements, because when everything is an expression you end up being able to walk the gradient of complexity a bit more nicely than when you end up with a thing that just has to be broken down to a bunch of statements.

Expressions are nice specifically because they don't tend to mutate things. The ternary operator is not at all the same as `a++` because you have the assign the result.
Wenn wouldn't have pearls like while (dst++ = src++);