Go 1.19 Beta 1 is released
groups.google.com
groups.google.com
I tried instructions on the site:
$ go install golang.org/dl/go1.19beta1@latest
$ go1.19beta1 download
But I don't get where the new binary is placed. It's not in my PATH.
When Java 19 lands you can have best of both worlds. Virtual Threads are equivalent to goroutines but you have access to better languages like Java, Kotlin and Clojure.
Valhalla has also produced an amount of language machinery that results in most of the same issues as Go. (E.g. "is this function receiving an interface or concrete value?" becomes "do I have a class, value class, or primitive class?") The performance ripples of requiring value objects to be immutable also remains to be seen.
I think by Java 21 they will be rock solid though which isn't really that far away in Java timescales.
Immutability is trivial to optimize away.
(That said, C# and Java are no performance slouches and I expect they’d all be roughly comparable in the end if the Go code didn’t suck so much.)
Go isn't fast, it's decent but nothing special for AOT native code. (atleast the standard gc compiler, llgo, gccgo etc could end up being a different story). It can show good overall performance in I/O bound workloads because of native M:N green threading though. I imagine even that is no longer a point in Go's favor when Java's Virtual Threads land and become widespread however.
Go's only real interesting runtime feature was their emphasis on having a very low latency GC. This was somewhat special for a few years but between improvements made to .NET and Java's new GCs (Shanandoah and ZGC) that gap has been eliminated while these runtimes still provide better throughput on top of their now low latency.
There really isn't a good performance or resource usage reason to pick Go over C#/Java anymore, all of its advantages have been eroded.
Garbage collection accounted for over 95% of the run-time. It was a perfect pathological case where it continually thrashed below/above a GC threshold, without hitting the point where it would make more room. Adding just a couple kilobytes of ballast[1] pushed it over the limit to where it reliably took about 200ms. (and then I rewrote the test to stop allocating so much junk)
Go's GC is mostly pretty good in practice, but yeah. It's definitely not something you can rely on blindly.
Also I wish they'd stop claiming that its pause times are tiny - the stop-the-world pauses are indeed consistently very short, but it hardly matters when it can pause goroutines for far, far longer[2]. And e.g. when you're allocating, your goroutine may be paused to "help" the GC, which can take a substantial amount of time (as happened in that^ test), which frequently leads to very large tail-latency. Most people don't care about stop-the-world times, they care about tail-latency, and Go is just leaning on people's misunderstanding that one is the cause of the other. It can be, but it's not the only source.
[1]: https://blog.twitch.tv/en/2019/04/10/go-memory-ballast-how-i...
[2]: http://big-elephants.com/2018-09/unexpected-gc-pauses/ among other sources of individual goroutine pauses.
I think Go's GC is solidly "good enough", and much better than many languages, so I broadly like it. Coupled with built-in tracing that shows GC activity and it's in a pretty good position. It's more that all GCs have pathological behavior somewhere, and no amount of distracting hand-waving changes that.
All three use load-barriers which result in more work done in application threads as the pressure on the GC increases.
That said these GCs are substantially better than the Go GC so you are talking about much higher allocation throughput before running into these issues in practice vs Go.
At Google's size this means nothing. The protobuf and gRPC developers don't even always align well, let alone with an entire language ecosystem.
By the same token, C#'s excellent gRPC performance is the result of MS dumping a chunk of resources into gRPC/PB optimization most recently. For Google it's cheaper to buy 2x as many nodes as it is to make Go gRPC 2x faster.
That is definitely not true if Google was using Go gRPC at any reasonable scale.
A more accurate conclusion to draw from it is MSFT is using C# gRPC at scale and Google isn't using Go gRPC at scale. Which in itself makes a lot more sense because C# is the bread and butter language at MSFT for most large network services and Go isn't at Google, it barely has any penetration compared to Java and C++ (which predictably have much much faster gRPC and PB implementations).
There are no stdlib filter/map/reduce yet, and generics are new enough that most people are still writing the same for-range loops over and over. Lambdas don’t capture loop variable values.
x, y := f() sometimes assigns and sometimes shadows an outer scope.
defer isn’t block scoped, you have to simulate the code to figure out which calls are stacked at this point. Hopefully they’re added deterministically, but you can keep deferring stuff in a loop if you want.
(This is aside from the many ways Scala and Kotlin are far more readable than Java.)
How do you easily ignore the majority of the screen? Is there an editor plugin that just doesn’t display those ifs?
Here's one example that might make it clearer: https://commandcenter.blogspot.com/2017/12/error-handling-in....
If the only thing you're doing is saying `if err != nil { return nil, err }` anyway, which is what empirically the majority of go's error handling code does, yes, you should have a better way of representing that, whether it's a "?", ASSIGN_OR_RETURN, or exceptions, which take a single character, a single line, or a no characters at all, as opposed to golang's 3 lines of visual noise!
Saying that "you can use custom error types to give finer grained error information" is great and all, but I learned that in my CS101 class, in Java, in like 2012. It's not fancy or better or good, except compared to C. Its a worse way of doing a thing that Java and Python (and...) have been doing for decades.
// Bad, most of the time.
result, err := foo(x)
if err != nil {
return err
}
But to do this: // Better, most of the time.
result, err := foo(x)
if err != nil {
return fmt.Errorf("performing foo on x = %v: %s", x, err)
}
This isn't noise. This is meaningful logic. That is what exception mechanisms seem to discourage, as they are invisible (unless you wrap every call to everything that throws into a try{}, but then it's not better than an if{} is it?), and this is what the ? operator in some languages discourages as well, because it adds no human- or machine-readable context.There must be syntax that indicates what to do when an error occurs, and that syntax must support addition of information about the context in which the error has occurred. And why not have the most basic conditional take the role of that syntax?
Also, bubbling up is the sane default, even if you are writing a fast script/trying out some code it won’t clutter anything and you will be notified to handle the problems once you want to write robust code. While in Go you will just put empty if errs and will forget about the whole thing, swallowing errors which are arguably the worst thing.
try { let res = somethingThatMightFail(); catch(err) { // log the error or something return null; }
// res is good now... but it's out of scope
Whereas in C++ you'd just write
ASSIGN_OR_RETURN(auto res, somethingThatMightFail())
And it will bubble your error status just like you wanted, only takes 1 line of code, and keeps all the error information (assuming you're using absl::Status).
I haven't really used Go, but it sounds like the worst of both worlds.
> even if you are writing a fast script/trying out some code it won’t clutter anything and you will be notified to handle the problems once you want to write robust code
How are you going to go back and find every callsite that might potentially fail at a later date? There's no easy way to even tell if somethingThatMightFail might fail if it doesn't return something like a "StatusOr" or "Option" or something. I think Java has some tooling to force exception handling. Not sure about Go.
While Java’s exceptions are not without problems (too deep inheritance trees don’t make sense on exception types, lambdas not operate too well with this feature, etc), I think it is a really good direction not utilized to the full by any mainstream language.
I've seen people that do that and it sounds nice until you end up with
Failed foo on x: failed bar on x: failed doing baz on x: failed to do quux".
You've created a worse stacktrace! I now have to search for a string and hope I can guess where the %s (or v or q) was instead of just grabbing the line number.
Or I do none of that, pass errors around directly, and just use the stacktrace.
The issue with your claim is that we only care about errors in two places, where we create them (like where the file was unable to be read) and where we handle them (when they are either swallowed bc the behavior was expected or passed to the user).
You don't care about them in all the intervening code, and exceptions handle that better.
> And why not have the most basic conditional take the role of that syntax?
Because it bloats your code and you end up with in practice more lines spent on verbose, cookie cutter error returning than actual logic!
Like, you don't need additional human readable context except in very particular cases. Go proves this in practice as almost everyone just returns the unmodified error most of the time, and it works great. Except for the annoying syntax.
Because having approx. 3/4 of your code devoted to error handling and error propagation is both hard to write and hard to read.
IMO rust's approach is close to ideal, in the common case where you just propagate up, the syntax is succinct, but still explicit, and when you handle it, the logic is close to the callsite, and is just working with a normal value. Useful stack traces are still a bit of a problem, but there is work being done on that.
And that's not to mention how in go you always return both the error and regular values, even though you usually only use one.
I agree about defer and the walrus operator though. Both can be quite confusing at times, and I think that Go with block-scoped defer and no walrus would be a more consistent language.
2) No try/catch. Inexcusable in my opinion.
3) The syntax for declaring maps/arrays looks gross. Again, being different for the sake of it.
4) No while loops. The for loop syntax is not too bad, but I think this was again a choice made out of trying to simplify things when not a single person ever complained that "Java/C has too many types of loops".
I could go on, but in general my opinion is that Go deviated from some good established norms for no good reason, and this makes it much harder to adopt. It also isn't fun when you have a code base with e.g. Go and Java or Go and C# mixed in. The mental overhead of having an entirely different syntax to reason about is frustrating. And for what benefit? At least list comprehensions in Python are nice to read. I can't think of anything that is nice to read in Go.
You won't like Rust either then. Though its just a matter of time till Rust gets try-catch too. It's slowly moving towards that step by step. Folks have already made crates https://docs.rs/try-catch/latest/try_catch/
For 2 you can see it that way. Or you are glad there are no exceptions and prefer the explicitness of error handling in Go. So in the end it's also subjective I guess.
I remember when I first looked at Go ~7-8 years ago I also didn't like the syntax. However it grew on me and now I like it. Opinions change. These days I try to not instantly judge something on my initial feelings.
The reality is that go is a very hard language to master and to avoid footgun. But it is an hard truth that few people are willing to accept.
What makes go powerful also make it weak.
In general the complexity need to live somewhere and in go it can only be the code itself as the language is too simple.
As for concurrency, that topic is hard in pretty much any mainstream language. Maybe with the exception of Rust, but Rust isn't exactly a langauge that is not “hard to master”, and even Rust doesn't protect you from all forms of concurrency issues. Merely most of them, heh.
The introduction of generics should make it easier to wrap these lower level concurrency primitives into higher level safe constructs without needing code generation or runtime type casting via interface{}.
Most devs also struggle to structure go programs in a way that makes them easily testable, but that’s true in a lot of languages.
> The introduction of generics should make it easier to wrap these lower level concurrency primitives into higher level safe constructs without needing code generation or runtime type casting via interface{}.
That process has actually already begun with Go 1.19's sync/atomic.Pointer[T]. See https://pkg.go.dev/sync/atomic@master#Pointer. Things like easier non-blocking send and receive or easier fan-in and fan-out channels should probably follow suit.
My experience especially relative to Java is the opposite - between httptest and sqlmock and the ease of setting up listeners in a separate goroutine, it's easy even for new Go developers to create new, realistic request-to-response functional (behavioral / Detroit-style / whatever they're called today) tests.
On the other hand, getting our long-time Java devs to hook up a reasonable wiremock test is like pulling teeth. They much prefer piles of redundant unit tests with a few Spring-injected "integration tests" against H2 and an in-process gRPC "endpoint" or whatever, that of course therefore aren't testing much integration at all.
Go may win out in the early days of a program for resource usage and perceived complexity but by the time your team has cooked up some frankenstien of an application framework and tried to standardize logging, metrics, etc you would have been better off in JVM land from the start. At least then you can pick battle-tested and supported frameworks.
Python, Ruby, JS/TS -> Go is a weird one though. I can understand why when people make that transition they think Go is the best thing since sliced bread. It's by far one of the easiest languages to learn, it's miles faster than what they are coming from and the tooling is way less shit than those interpreted languages.
However all of those languages represent a very very low bar. I think if the same people could be convinced to try Kotlin (or even modern Java) they would be a) surprised how easy it is these days and b) finally understand that it's ecosystem and tooling that really matters at the end of the day.
Though I have to say Go existing is by and large a good thing even if I don't like it. It helps get people off things like Typescript which are absolute garbage-tier onto something half-reasonable. When they grow out of Go they generally end up somewhere better, just takes a few years.
My personal preference for such tools is Rust but it's admittedly a harder sell.
Comment above is mostly addressing the server software use case with larger teams and 100KLOC+ (either one or many programs).
I don't think anyone was using Java much in the CLI niche. It was dominated mostly by Python and Ruby (and Perl before that) before Go came along. I do think Go represents an improvement on that status quo.
Kubernetes, Docker, Terraform and their respective ecosystems really aren't what one would call a smaller codebase.
Go offers a nice balance between performance and platform agnosticism, features like parallelism without it being too complex to write. Manual memory management would have been a useless obstacle wasting developer time for little real gain. Fighting the language to have parallel execution, easy crosscompile, etc. why?
As for HCL, I'm on the opinion that a DSL is better. It's even in the name, Infra as Code, not with code. You want your infrastructure declarations to be easily readable and supportable. Anyone who has used Terraform can read any HCL; your Python code doing the same could be terrible and unreadable by anyone not already on your team. Too much flexibility for something as critical can end badly, imho.
In any case, there's cdktf now, you can mix and match terraform with cdktf.
Disclaimer: I work at Hashicorp, opinions my own and have stayed the same since before joining
As far as I know, it's not hard to ship a JVM runner with the installer of your program, so this should be a non-issue.
You can trivially ship a self-contained JVM that is completely isolated within a single directory.
> Go offers a nice balance between performance and platform agnosticism
I don't disagree with this. It's too bad the language itself has major problems (which I won't get into here).
> I'm on the opinion that a DSL is better
The two aren't mutually exclusive. What I want is a DSL embedded in a full programming language, like chef's dsl in ruby.
> You want your infrastructure declarations to be easily readable and supportable.
Absolutely.
> Anyone who has used Terraform can read any HCL; your Python code doing the same could be terrible and unreadable by anyone not already on your team
It's possible to write terrible and unreadable HCL too. I know because I've written some.
> In any case, there's cdktf now, you can mix and match terraform with cdktf.
Unfortunately, I haven't had a chance to look too deeply into it yet, although I definitely want to.
I've seen some atrocious Ruby for Chef, so I'm not sure I'd agree here. The advantage of a DSL is that it limits how far you can go. In the case of HCL pre-v2, it had some limitations around dynamism, but now that's out of the way IMHO it strikes a decent balance.
You can write hardcore HCL, but IMHO it's still way easier to parse by a human that bad Python or JavaScript.
As for CDKTF, I'd recommend you do. You can mix and match like having a project with CDKTF in e.g. Python or Go use regular TF HCL modules, but also vice versa, defining a module in TypeScript and consuming it from regular Terraform. So you can have the extra flexibility ( with all of it's advantages and risks) on a per module basis, not all or nothing.
It's still around, there is a lot of existing J2EE codebases but it's successor - Quarkus has done away with the vast majority of the config mess. I can't speak to how well it manages that transition though as I was never a big J2EE guy.
The other old offender, Spring, developed a new convention over configuration distribution called Spring Boot which requires minimal configuration and has pretty much displaced the old way of doing things entirely. It's been around for years as the default option at this point.
The way I see Java is that it moves slowly but tends to happily assimilate the best ideas from other languages, runtimes and frameworks and over time sheds it's warts (Java 8 era sun.internal.*, old Date/Time APIs, etc).
Not everyone seems to have an up to date opinion of Java because the last time they touched it was J2EE days or worse yet in university.
A lot of things about Go work out well in practice when there's a human watching it that can just slaughter the process if it starts going off the rails, and the trivial distribution and excellent startup performance make it almost perfect for CLIs. Small code works fairly well, and the sometimes-quite-yolo stdlib gets you to "works on my machine" for 99% of your needs quickly and that's often good enough for ad-hoc tooling (and when it isn't: just delete that non-UTF-8-named file, sheesh).
For large stuff? No please. The type system is far too weak, and the runtime has too many sharp corners. About the only thing I like about it for this is that runtime reflection is very tightly restricted, which prevents some of the worst insanity you see in Java.
Excuse me but Java? This is as subjective as it can be.
All java code be lie `public static void SomeReallyLongNameDescribingEverthing42 implements SomeOtherEvenLongerName`
For me it's 10 times harder to follow than Go's `if err` blocks (which I actually do appreciate most of the time).
CamelCase makes it even worse. Short CamelCase is quite okay, but the longer it gets, the harder it on my eyes.
But even with short naming it still like 2-3 words just to name a function. wtf.
1. snake_case - best of the best. words are separated, easy to read even if the words are not in english or latin
2. ShrtCmlCase - worse than snake. Pros: quite short, does not take whole monitor width. Cons: requires additional thinking and some sort of in-team guidelines. Very opinionated.
3. JavaCamelCase - worse than ShrtCmlCase. Pros: you can put in and describe anything in a single name of a class\variable. Cons: you spend too much attention on reading rather than analysing the program.
I can understand why Java uses long names though. Its oop and enterprise nature is to "blame". This kind of naming is unavoidable when you are building a giant monolith with hundreds or thousands of classes, all kind of inheritance nuances and trying to have all names actually mean and describe something.
As I said earlier - all are very subjective topics.
There isn't a ton about Java's syntax to be super opinionated on, compared to Go, simply due to how unique Go's syntax is.
My main problem with Java code is that it's too hard to read due to endless variables\classes etc names and such. It's almost like reading some Oracle's documentation rather than reading code.
Golang date formatting on the other hand... I still mad an whoever is responsible for this.
I then rewrote the backend in Go, which gave a 90% performance increase.
It could now run all the streams on any normal pc.
Go syntax is very cognitively demanding. Maybe I am getting old or have become used to C#, but between reading well formed go and C# code, C# always wins for me.
Admittedly, a lot of it comes down to personal preference and familiarity with different technologies.
Though in regards to Go in particular, on a technical level:
- it's simple to build self-contained executables, it's up there with the likes of FreePascal in this regard
- it's also a relatively simple language that's easy to pick up and use, in contrast to how complex code can get with C# and Java
- it also has fewer footguns than C or C++ without the complexity of Rust (which some might prefer), although it doesn't try to do as much
- it has a decent community and lots of useful packages, even the included http libraries are nice to just get things done
- it doesn't have a history of challenging versioning and platform support, when compared with .NET (Windows only support with IIS for legacy projects, anyone?)
- the performance is generally good enough
All of that combined means that Go is nice for writing both quick utilities, as well as smaller and larger services alike. Just look at Nomad, a container orchestration platform, the executables of which can be shipped as a single binary: https://www.nomadproject.io/downloadsOverall, it's pretty good for just getting things done in a reasonable amount of time and a limited amount of headaches. For example, Python might lead to code that is easier to write and read, but packaging can be a nightmare. Java might be better for select enterprise systems, but configuration is generally a bit of a mess there. .NET is a good alternative, but some folks also try to steer clear of Microsoft and tooling on *nix can be limited, much like it is for Java (your best bet would be to use JetBrains commercial products, since MonoDevelop isn't all that popular anymore, Visual Studio Code plugins wouldn't give you a "full IDE" experience and there is nothing quite like Eclipse or NetBeans for .NET).
Further thoughts: the age of a language might also coincide with how loved or dreaded it is, since we simply don't see that many horrible enterprise monolith messes written in Go as of yet: https://earthly.dev/blog/brown-green-language/
Also doable in dotnet
> it's also a relatively simple language that's easy to pick up and use, in contrast to how complex code can get with C# and Java
Go is not a simple language. It has made choices of syntax that are baffling. Not to mention the mess that is exceptions and generics.
C# and Java, on the other hand, feel natural to read.
> it also has fewer footguns than C or C++ without the complexity of Rust (which some might prefer), although it doesn't try to do as much
C# is the same in this case. Far fewer footguns.
> it has a decent community and lots of useful packages, even the included http libraries are nice to just get things done
C# wins, but perhaps because it has been around for so long. But its ecosystem is very good.
> it doesn't have a history of challenging versioning and platform support, when compared with .NET (Windows only support with IIS for legacy projects, anyone?)
Things change, systems change, environments change. At present, C# and dotnet are truly cross platform and are amazing platforms.
> the performance is generally good enough
In Go, as your program size and team gets bigger, code management becomes harder. C#, along with Visual Studio is at its productive best, whether its with one developer or a 100 developers.
A trivial example of comparing the possible scope of any two languages in question, would be to glance at their lists of reserved words, for example https://go.dev/ref/spec#Keywords and https://docs.microsoft.com/en-us/dotnet/csharp/language-refe... However admittedly that's not a discussion that I have the time for, though I'm sure that lots of people here might share their own thoughts and experiences. Honestly, for web development, everything starting with .NET Core and the newer versions has been pretty cool, so it's not like you have to deal with the inherent complexity every day, unless someone gets a bit too clever.
Of course, less isn't always better, as certain other languages might demonstrate (a silly example, but still): https://en.wikipedia.org/wiki/Brainfuck
The only thing I cannot agree with here strongly is your argument about Visual Studio: I yearn for the day when Microsoft will finally port it over to *nix, but if you don't need to support it as a developer platform then you're in a pretty nice place to be! For everyone else, in the mean time, I guess there is Rider https://www.jetbrains.com/rider/ (though, of course, a paid product)
I never really understood this claim. Arguably, assembly is even less complex of a language, yet you would be hard pressed to say it is readable. Primitive primitives don’t necessarily result in readable code. For example a declarative description of what should happen with a stream of data is much more readable in many cases than 4 nested for loops with breaks and ifs.
But back to the topic, while there is a (fortunately getting less and less relevant) branch of Java which is very into needless abstractions, the language is a very small and easy to pickup one, allowing very readable and concise programs.
How often do you actually just use the core language without one of the frameworks, though? I guess it largely depends on what you do. For example, if you use Java for web app development, one of the more popular frameworks out there is Spring or maybe Spring Boot, which will force you to deal with a large amount of abstractions.
So while the languages themselves might not be that dissimilar, the ecosystems and frameworks/libraries around them, which actually shape how you'll use them most of the time (depending on the context), will most certainly lead to the differences being more pronounced.
As someone who's worked with quite a few (monolithic) enterprise projects, I couldn't disagree more!
The regular Spring framework can turn out to be a total mess of various intertwined .xml configuration files, based on whatever the project needs - access control mechanisms or integrations with external providers (say, LDAP or Kerberos), perhaps various filters for processing web requests (transformations, logging, additional flow controls), things to make any number of integrated libraries work correctly (JSR310 integration with Jackson, for example), all of which gets even worse if you need to get server side rendering in any of the older technologies working correctly, like JSF and PrimeFaces. You can get a variety of things done with Spring, true, but in practice it's going to be brittle and needlessly verbose.
The Spring Boot framework is a marked improvement, however you will still need a whole bunch of configuration in your .properties file(s), or maybe your YAML configuration if someone has opted to use that format. Of course, you shouldn't forget that you still need to figure out how to ensure that the profiles are loaded correctly and that you somehow haven't screwed up the settings in each file. But with Spring Boot, all of the sudden you need to also figure out which configuration related annotations you need and which ones could cause problems - how to better figure out passing DB connections to myBatis or Hibernate or whatever and also ensure that the session parameters are set as needed (say, default locale, so you don't get issues with Oracle).
Spring Boot simply takes a lot of the XML hell and turns it into a Java hell - better, because you're working with a proper programming language, but still not quite there yet, because a lot of the problems will only manifest at runtime (ever needed @Lazy with @Autowired?), which isn't fun when your app with thousands of source files takes minutes to start up so you can check everything out. Personally, I blame reflection and dynamic behavior for a lot of these problems, which is far harder to reason about than your IDE telling you that what you're attempting to write is weird and won't work before you launch everything.
Of course, my dislike of Spring is shaped by the utter messes of projects that I've had to work with, which is largely the inevitable outcome for any monolithic codebase that has to be kept around and is important to a business for close to a decade but also with limited resources (say, unlike the Linux kernel). That's also why previously I mentioned the "Green Vs. Brown Programming Languages" article - you tend to dislike some stacks once you've seen what long term projects in them end up looking like.
So, if you can, it's probably best to stick to smaller projects and microservices so these pain points don't become readily apparent and then most frameworks would be passable - be it Spring, Spring Boot or most others. Though I sometimes wonder whether people who wrote COBOL and FORTRAN also found starting out and having smaller projects to be enjoyable. Somehow we talk about languages and frameworks, but not how much of a difference there can be between a new project and an older one - some sort of a maintainability degradation coefficient that would express how maintainability changes over time.
I would even go as far and claim that the reason Java has this reputation of huge complexity in every monorepo is partly the effect of survivorship bias. Some other stack may have crumbled long before growing to such a monstrosity.
Nonetheless, I would also like to have more of compile-time DI, records, etc, but we shouldn’t just reinvent everything in a heartbeat.
Go is very neat, true to its core identity and very very stable. I have been developing .NET (C# and F#) for almost 20 years and I don't enjoy it at all anymore.
EDIT:
Just last week the .NET team tweeted that they are looking to introduce green threads to .NET after all. I love the green thread implementation in Go and I think it is superior to .NET's async/await model which get scheduled by OS threads, but .NET has gone down that route many years ago and it's embedded everywhere now and introducing such a new fundamental change now is just plain fucking stupid. Microsoft is so dumb for even thinking this is a good thing to do. If they don't like how C# works then invent a new language with modern fundamentals which you like but stop changing C# so much that it's become completely unrecognisable. .NET developers don't want to rewrite their code bases every 12 months. We have real work to do.