Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I have no idea whether Carbon will be successful, but this is the only right way of evolving a programming language when incompatible changes must be made: by providing tools that guarantee a completely automatic migration of the legacy programs.

Hopefully Carbon will succeed to achieve this goal.



I think the idea of "when incompatible changes must me made" has caused much harm and damage to various language projects. I'm specifically thinking of the damage done as part of the python 2->3 changes and to a lesser extent the damage done with the original conception of what was called perl6.

C++ is definitely in a tough spot on the evolutionary trail. But the idea that the only path ahead is through incompatible changes seems likely to produce the same harmful effects.


In 2019 Epochs was proposed for C++. That's "too late" for C++ 20, but only by convention. Epochs proposed to give C++ a way to evolve while retaining better backward compatibility, much like Rust's Editions (the 2024 Edition stabilized last year and will ship in Rust 1.85 in a week or two)

Instead C++ has added all sorts of slightly incompatible features every three years since 2011, and is expected to do so again, periodically one of these incompatible changes is especially troublesome for important people and, like a naughty toddler, the committee promises not to do that again.

Yet, despite these incompatible changes which might have accidentally larger consequences than expected, for fear of the consequences other changes which were known to be incompatible but seem "worth it" are rejected. The worst of both worlds.


In practice, epochs offer little more than -std=lang-version, because they only cover grammar changes, and a few selected semantics.

There is no plan for binary libraries across epochs, how different implementations would interact with each other, how multiple crates requiring different versions would interact with each other if their public API crosses versions with different semantics,...


I'd argue that one of the things we saw with Rust's Editions is that it significantly increases appetite for such language improvements.

There's a recent Reddit discussion which had zero pushback against changes but instead people who were disappointed that 2024 Edition won't land all the things they'd hoped for such as the improved Range types†

Without the Edition system we know from C++ that when you say "Why can't we have X?" the defenders appear to tell you that all choices except the status quo are impossible. They will do this regardless of how difficult such a change would be, and so this immediately deflates all interest in incremental improvement. It's a self-fulfilling prophecy.

But with Editions there's a lot of things we definitely can do. Once you open the door to that, it really drives enthusiasm and that enthusiasm can power some of the difficult changes you wouldn't even have considered in the C++ process.

† In Rust syntax like 1..=4 is compiled into the core type core::ops::RangeInclusive<i32> so this is a very natural way to express what you meant, and yet it's not a goofy special case it's just a normal literal like "Word" [an &'static str] or '€' [a char] or 1.2345 [a floating point defaulting to f64] -- however, we now realise these range types made some poor choices and if you made them today you'd make them implement Copy and IntoIterator, whereas the existing types implement Iterator and so in many cases cannot implement Copy. Ergonomically a Copy type would be much nicer here. Can we fix that? Well, the idea is, we make the new types (they exist in nightly Rust in the new core::range namespace but aren't stabilized) and then once we're sure they're exactly what we want we make a new Edition which changes the syntax transformation so that that literal is a core::range::RangeInclusive<i32> instead.


> I'm specifically thinking of the damage done as part of the python 2->3 changes

Though on the other hand, we can only the imagine of not doing those changes. I reckon it would be at least as bad as the 2->3 changes. At the very least I wouldn't want to go back to a world where bytes and strings were unified.


C++ already had quite a few incompatible changes, your C++98 code will not compile in a C++23 mode compiler, if lucky enough to use one of such features.


Ok, so could we agree that then doubling down on even more breaking changes is likely to create more codebases stuck at these "evolutionary bottlenecks"?

Whenever I hear someone say - "Gee, the only path ahead is a breaking change, sorry charlie!", what I really hear is - "I gave up thinking up a solution on how to do it an evolutionary way and I am lazy and just want to declare a revolution!".

There is always a pathway ahead on the evolutionary path. That's the premise at least.


The real problem with ABI breaks isn't so much the need to (automatically or manually) migrate legacy programs, but that so much of modern software is comprised of multiple pieces that don't have any way to coordinate a lock-step ABI migration.

Take something like std::unique_ptr. The basic problem is that it is absolutely capable of being written such that std::unique_ptr<T> has the same ABI as T, but it doesn't (instead acting as a T*). It's pretty damn trivial to write the necessary thunking between the ABI versions, so it's also absolutely possible to have both old-ABI and new-ABI coexist (something that was a lot more difficult for the infamous Python 2->3 transition). And other than the internal changes needed to give it a new ABI, there's not really any user-visible API that would need to be migrated. But it still can't be done because std::unique_ptr is used on the interface between library A and B, and how is A supposed to know which version of the ABI B is expecting, when neither A nor B?


From the article:

> The goal is a tool-assisted migration of idiomatic code, not a fully automated migration of all code.


That is just an acknowledgment of the fact that for something as complex as C++ a fully automated migration is unlikely to be achievable.

This does not mean that they will intentionally avoid to make possible a fully automated migration.

Normally the migration tools should be designed to attempt to do a fully automated migration, but whenever there are corner cases for which the effort to handle them completely automatically would not be worthwhile, then human intervention shall be required.


Yes.

In C++ in particular one of the most obvious ways to write unmaintainable C++ which such automation couldn't be expected to migrate is via abuse of the pre-processor.

C++ retains the entire C pre-processor, which is a weird not-quite-language-agnostic text macro language on top of C. We can abuse this to write a different programming language, with different rules, and then at compile time the pre-processor transforms it into C++ which compiles.

I'm confident a migration tool could not always usefully translate this, and I'd argue that's not a failing of the tool so much as an inevitable consequence of writing this unmaintainable code.


> but this is the only right way of evolving a programming language when incompatible changes must be made: by providing tools that guarantee [it]

Yep. I'm not a particularly big fan of Google languages, but focusing on bridging legacy code is brilliant.

Would have been interesting if Oracle had done the same with Java shortly after acquiring it. Java is so hemmed in by legacy compatibility it basically can't make any significant language or VM changes without making harsh compromises. Of course, Oracle would likely have supported legacy Java indefinitely, and made a fortune supporting/licensing it.




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

Search: