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

The draft design document that all of the feedback is based on mentions C++, Rust, and Swift. In the extensive feedback document you link above I could not find mention of do-notation/for-comprehensions/monadic-let as used Haskell/Scala/OCaml. I didn't find anything like that in the first few pages of the most commented GitHub issues.

You make it out like the Go Team are programming language design wizards and people here are breezily proposing solutions that they must have considered but lets not forget that the Go team made the same blunder made by Java (static typing with no parametric polymorphism) which lies at the root of this error handling problem, to which they are throwing up their hands and not fixing.



I think Go should have shipped with generics from day one as well.

But you breezily claiming they made the same blunder as Java omits the fact that they didn't make the same blunder as Rust and Swift and end up with nightmarish compile times because of their type system.

Almost every language feature has difficult trade-offs. They considered iteration time a priority one feature and designed the language as such. It's very easy for someone looking at a language on paper to undervalue that feature but when you sit down and talk to users or watch them work, you realize that a fast feedback loop makes them more productive than almost any brilliant type system feature you can imagine.


This is a very good point, fast compilation times are a huge benefit. The slow compiler is a downside of languages like Rust, Scala, and Haskell. Especially if you have many millions of lines of code to compile like Google.

However, OCaml has a very fast compiler, comparable in speed to Go. So a more expressive type system is not necessarily leading to long compilation times.

Furthermore, Scala and Haskell incremental type checking is faster than full compilation and fast enough for interactive use. I would love to see some evidence that Golang devs are actually more productive than Scala or Haskell devs. So many variables probably influence dev productivity and controlling for them while doing a sufficiently powered experiment is very expensive.


Take a look a the kubernetes source code. It's millions of lines, and almost all of it is generated. In a language like C++ or Rust, the vast majority of it would be template or macro instantiations.

For an apples-to-apples comparison of compilation speed, you should either include the time it takes go generate to run, and the IDE to re-index all the crap it emits, or you should count the number of lines of code in the largest intermediate representation that C++ or Rust has.


> For an apples-to-apples comparison of compilation speed, you should either include the time it takes go generate to run

But that would be unfair to the very design choice of omitting metaprogramming while exposing the go/ast library to users to foster code generation.


What makes you think Rust’s compile times are related to its type system?


The way the type system interacts with the rest of the language leads you down the path to monomorphization as the compilation strategy. Monomorphizing is what gives you huge piles of instantiated code that then has to be run through the compiler back end.

Blaming it on LLVM like another comment does misses the point. Any back end is slow if you throw a truck-load of code at it.

I'm not saying monomorphization is intrinsically bad. (My current hobby language works this way.) But it's certainly a trade-off with real costs and the Go folks didn't want their users to have to pay those costs.


Monomorphization has got nothing to do with type system though. If you have a GC (as go does), you can automatically box your references and go from a `impl Trait` to a `&mut dyn Trait` with the GC taking care of value vs reference semantics. Monomorphization is orthogonal to how you define the set of valid arguments.


Except if your traits are not dyn-compatible. Which I believe a lot of Rust's traits are not. That restriction is specifically why Go does not allow methods to have extra type parameters: To make it possible for the language implementation to choose its own tradeoff between monomorphization and boxing.

So I don't think you can say that this has nothing to do with the type system. Here is a restriction in the Go type system that was specifically introduced to allow a broad range of implementation choices. To avoid being forced to choose slow compilers or slow code: https://research.swtch.com/generic

The Go type system and the way it does generics is directly designed to allow fast compile times.


> you can automatically box your references

Yes, but that is now a different runtime cost which Go also didn't want to pay.

The language goes to great pains to give you pretty good control over layout in memory and avoid the "spray of tiny objects on the heap with pointers between them" that you get in Java and most other managed languages.

I think Swift maybe does something more clever with witness tables, but I don't recally exactly how it works.

It's not an easy problem.


> I think Swift maybe does something more clever with witness tables, but I don't recally exactly how it works.

Pestov actually wrote a long explanation of what it is that Swift does there[1,2]. And I’m almost sure you’ve already seen it, but it’s been on my reading list forever and I’m hoping that maybe if I can’t get myself to read it, than somebody else will see this comment, get interested and do it.

[1] http://download.swift.org/docs/assets/generics.pdf

[2] https://github.com/apple/swift/tree/main/docs/Generics


Yeah, I've had that PDF sitting on my hard drive for years. I'm definitely interested but it's 600 pages.


You realize that having a generics and having monomorphization are two orthogonal things, right?

If you're not aiming for the highest possible performance, you can type erase your generics and avoid the monomorphization bloat. Rust couldn't because they wanted to compete with C++, but Go definitely could have.


People keep mistaking languages with implementations.

OCaml and Haskell don't suffer from similar pain points, because interpreters, and JIT like tooling is also available.

One can happily develop with one toolchain, and press the red button for an optimised compiled build, when it actually matters.

Also both Swift and Rust have made the mistake to make used of the compiler backend that everyone avoids when they care about build performance, LLVM.


This has been a lazy excuse/talking point from the Go team for a while, but in realitiy Generics aren't the reason why Rust and Swift compile slowly, as can be easily shown by running cargo check on a project using a hefty dose of generics but without procedural macros.


Last time I checked, Rust's slow compile times were due to LLVM. In fact, if you want to make Rust faster to compile, you can compile it to wasm using cranelift.


Not just LLVM in itself but the Front-end codegen: AFAIK the rust front-end emits way too much LLVM IR and then counts on LLVM to optimize and they have been slowly adding optimizations inside the front-end itself to avoid IR bloat.

And there's also the proc macro story (almost every project must compile proc_macro2 quote and syn before the actual project compilation even starts).


Thanks for the clarification.


> lets not forget that the Go team made the same blunder made by Java

To be fair, they were working on parametric polymorphism since the beginning. There are countless public proposals, and many more that never made it beyond the walls of Google.

Problem was that they struggled to find a design that didn't make the same blunder as Java. I'm sure it would have been easy to add Java-style generics early on, but... yikes. Even the Java team themselves warned the Go team to not make that mistake.


At least Java supports covariance and contravariance where Go only supports invariant generics.


Java has evolved to contain much of “ML the good parts” such as that languages like Kotlin or Scala that offer a chance to be just a bit better in the JVM look less necessary


    > Java has evolved to contain much of “ML the good parts”
Can you give some examples?


Not OP. IMO the recent Java changes, including pattern matching (especially when using along with sealed interface), virtual threads (and structured concurrency on the way), string templates, are all very solid additions to the language.

Using these new features one can write very expressive modern code while still being interoperable with the Java 8 dependency someone at their company wrote 20 years ago.


fyi: string templates were just a preview and removed in Java 23


For Java systems that I work on for my own account there is a lot of stuffing things like SQL queries into resource files so that I don't have to mess around with quotes and such.


Like a lot of other languages, Java has gotten a big dose of

https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_sy...

To defy it's reputation for verbosity, Java's lambda syntax is both terse and highly flexible. Sum and product types are possible with records and sealed classes. Pattern matching.


I even found a way to make ad-hoc union types of element types from other packages that does exhaustive switch/case checking. I quickly wrote down a PoC so I wouldn't forget[0]. It needs wrapper types and sealed interfaces in the consuming app/package but is manageable and turned out better than other attempts I'd made.

[0] https://github.com/karmakaze/AdHocUnion


    > blunder made by Java
For normies, what is wrong with Java generics? (Do the same complaints apply to C# generics?) I came from C++ to Java, and I found Java generics pretty easy to use. I'm not interested in what "PL (programming language) people" have to say about it. They dislike all generic/parametric polymorphism implementations except their pet language that no one uses. I'm interested in practical things that work and are easy for normies to learn and use well.

    > Even the Java team themselves warned the Go team to not make that mistake.
Do you have a source for that?


> I'm not interested in what "PL (programming language) people" have to say about it. They dislike all generic/parametric polymorphism implementations except their pet language that no one uses.

That's strange. I seem to recall the PL community invented the generics system for Java [0,1]. Actually, I'm pretty sure Philip Wadler had to show them how to work out contravariance correctly. And topically to this thread, Rob Pike asked for his help again designing the generics system for Go [2,3]. A number of mistakes under consideration were curtailed as a result, detailed in that LWN article.

There are countless other examples, so can you elaborate on what you're talking about? Because essentially all meaningful progress on programming languages (yes, including the ones you use) was achieved, or at least fundamentally enabled, by "PL people".

[0] https://homepages.inf.ed.ac.uk/wadler/gj/

[1] https://homepages.inf.ed.ac.uk/wadler/gj/Documents/gj-oopsla...

[2] https://arxiv.org/pdf/2005.11710

[3] https://lwn.net/Articles/824716/


Yeah, it _doesn’t_ apply to C# generics. Basically, if you’ve got List<Person> and List<Company> in C#, those are different classes. In Java, there’s only one class that’s polymorphic. This causes a surprising number of restrictions: https://docs.oracle.com/javase/tutorial/java/generics/restri...


Type erasure is what is wrong with Java generics. It causes many issues downstream.


    > It causes many issues downstream.
I don't understand this part. Can you give some concrete examples? In my experience, Google Gson and Jackson FasterXML can solve 99.9% of the Java Generic issues that I might have around de/ser.


I could, or you could use google. Neither of those tools can solve any issue caused by type erasure.

Just to give some examples, the instanceof operator does not work with generic types, it's not possible to instantiate a generic type (can't do a new T()), can't overload methods that differ only in generic parameter type (so List<String> vs List<Integer>) and so on. Some limitations can be worked around with sending around explicit type info (like also sending the Class<T> when using T), reflection etc., but it's cumbersome, and not everything can be solved that way.


Should I repost the blog posts where they acknowledge the failure to look into languages like CLU?


Even Rust and F#[1] don't have (generalized) do notation, what makes it remotely relevant to a decidedly non-ML-esque language like Go?

[1] Okay fine, you can fake it with enough SRTPs, but Don Syme will come and burn your house down.


IDK, Python was fine grabbing list comprehensions from Haskell, yield and coroutines from, say, Modula-2, the walrus operator from, say, C, large swaths from Smalltalk, etc. It does not matter if the languages are related; what matters is whether you can make a feature / approach fit the rest of the language.


Generalized do notation as GP is proposing requires HKT. I don't think it's controversial to say that Go will not be getting HKT.


The := is from Pascal, not C.


Afaik the F# supports do notation through computation expressions.


Like Rust, F# doesn't have higher-kinded types so it's not generalized like GP is proposing. Each type of computation expression is tied to a specific monad/applicative.


hahaha :D




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

Search: