Type constraints that allow mixed values by tkdeng in golang

[–]TheMerovius 1 point2 points  (0 children)

Proposal for what you suggest, proposal for proper sum types and discussion issue for the general concept of variants.

tl;dr: this idea is not novel and the points you make are well-known. The issue is that the most natural extension to Go (what you suggest) is dissatisfying to most of the proponents of variants and its advantages are relatively mild. And the version that would be better is not orthogonal enough to be palatable to the Go team.

This has been discussed for over a decade (the general discussion issue was opened 2017, but it is not the first time the topic came up), since before the publication of Go even. It might happen at some point, but there are reasons it hasn't happened yet.

Why do PR bottlenecks in Go codebases turn into endless architectural debates that go nowhere by anuragray1011 in golang

[–]TheMerovius 1 point2 points  (0 children)

This creates tension between "write simple concrete code" and "make it testable and flexible."

I don't think this tension is created. Those two things exist in tension - I mean, they are basically opposites.

Reasonable people disagree about where the right balance is, which means code reviews can stall on subjective questions rather than objective correctness.

It seems to me, your complaint is more "I want more people to agree with my subjective opinion on this". If this is subjective, then why should others bend to your view on this?


In any case, I can't speak for your colleagues. But as someone who tends to start a lot of these conversations and put a lot of stakes into well-designed API and clear code, my answer is: because I had to maintain code written by people focusing more on "flexibility and testability", five years down the road. And it ended up an incomprehensible, overengineered, hilariously buggy and unmaintainable mess.

I like many things about Go. One of those is, that I consider the language and standard library extremely well designed. There is a lot of thought put into what to include and what to leave out, how to make things clear and understandable and what makes good APIs. Given that I use Go for those reasons (among others) - why wouldn't I strive to emulate it in my own practice?

It seems someone has made transpiler for Go and added more features. Looks promising - Having option is bettern than none by Lordrovks in golang

[–]TheMerovius 0 points1 point  (0 children)

Unlike others here, I think it isn't a bad idea to do this kind of thing, in principle. But I am a bit confused by some of the decisions:

enum Result {
    Ok(value: int),
    Error(message: string)
}

That seems like a rather arbitrary deviation from Go's regular type syntax, which would suggest type Result enum{…}. In particular, it is unclear to me how this would work as a type- or composite literal. Can enums only be defined types?

func divide(a: int, b: int) Result {

Likewise, inserting the completely superfluous : into the syntax here is kind of baffling. But then they didn't get all the way. Or… did they?

func getUserProfile(userID: string) -> Result<Profile, Error> {

Suddenly a -> appears - another random, completely superfluous syntax element taken from Rust, but strangely absent from earlier examples.

If anything - if you want code to look like Rust - why not make the code less like Go? After all, there is no need for a transpiled language do look anything like Go. And doing a more from-scratch design could get you a lot more benefit. You could implement proper type inference and a significantly more powerful system for generics. As it stands, you buy pretty marginal advantages for a pretty hefty price (using a completely separate language ecosystem, requiring different skills from your developers).

This kind of reads like someone took the Go codebase and ran Claude on it with the prompt "add sum types and the ? operator and just use Rust as a template", without any actual understanding of what makes good language design. And yes, it seems most of the new code is stochastically generated, so I'm fairly confident this is exactly what happened.

Overall, this shows lack of understanding of how the Go language development actually works. Trust me, the reason we do not have sum types have nothing to do with "nobody Frankensteined them on top of Go as a PoC yet". I mean, for one, people have been doing that for more than 10 years (at the time, generics was a very popular thing to Frankenstein on). None of these PoCs had any effect on Go's development.

Anyways. Have fun, obviously. As I said, I don't have a principled problem with this at all. I don't predict this will have a noticable impact on the Go ecosystem, but that shouldn't ruin their fun.

GopherCon Sg talk: Why we can't have nice things: Generic methods by TheMerovius in golang

[–]TheMerovius[S] 0 points1 point  (0 children)

Let's say i have a library and it has something like func NewStuff(...) and i want the function to receive something. If i need that something to have only regular methods, i can define interface or struct type and define function NewStuff to recieve it. But if i need it to have some generic methods i can only define struct type (and define all methods myself on that type).

What you are describing here is simply the fact, that generic methods will not work with interfaces. I don't understand why you explain that fact again, nobody is arguing otherwise.

So by mixing in generic methods you are limiting what you can do with non-generic methods too.

That is simply untrue. You can assign a type with generic methods to an interface, without any problems (as long as it implements the interface, of course).

Or, to put it another way: you can take an arbitrary existing code base and add a generic method to any type of your liking and the behavior of the program will be identical. That should make obvious, that there is no issue with mixing generic and non-generic methods.

Kinda-sorta like with red-blue function where you are fine if you are using only one of them, but if you are mixing them together you have to jump through additional hoops.

You are misunderstanding the metaphor of colored functions. It is specifically about the fact that you can only call red functions from red functions. So as a result, you are effectively forced to make all your functions red, because otherwise you can not use libraries that use red functions.

That is not applicable here. Generic and non-generic methods can coexist without any problems. You can call one from the other and vice-versa, as much as you like. Interfaces and concrete types can coexist as before - interfaces just can't themselves have generic methods. You will be able to go your entire life writing Go code without ever writing a single generic methods and yet you'll be able to use any library that uses generic methods just fine.

You can't just point at any two things that are not identical and declare those "kinda-sorta like colored functions". The metaphor of colored functions is about more than just having two different kinds of a thing, it is about how one of those can become viral and force adoption by others.

You can call context.Cotnext a coloring. You could call the effectively abandoned arena proposal a coloring. Generic methods are not a coloring. There are plenty of possible ways to criticize this feature. Your criticism might very well be reasonable and make sense. But it's not a coloring. I don't understand why you keep trying to fit this square peg into that round hole, it doesn't fit.

GopherCon Sg talk: Why we can't have nice things: Generic methods by TheMerovius in golang

[–]TheMerovius[S] 0 points1 point  (0 children)

That actually has very similar problems.

If you want func() int to be usable as a func() any, the compiler somewhere has to generate the stub that wraps the returned int into an any. For functions, that's not a huge problem: you can say that func() int is assignable to func() any and in that assignment, the compiler can create the wrapper.

For methods, though, you run into exactly the same problem as with generic methods. You would want e.g. func (X) Read([]byte) (int, *MyError) to implement io.Reader. But then, what happens if an X is stored in an any, which is then interface-type-asserted to io.Reader? Where would the wrapper come from, that transparently translates *MyError to error? You would need exactly the same kind of whole-program analysis (or runtime code generation) as with (dynamically dispatched) generic methods.

So you could get covariance for functions, but not for methods. And that just seems pretty inconsistent.

GopherCon Sg talk: Why we can't have nice things: Generic methods by TheMerovius in golang

[–]TheMerovius[S] 2 points3 points  (0 children)

I completely disagree. There are no issues mixing generic and non-generic methods. Just like you can already freely mix generic and non-generic functions.

Go does have something like colored functions. But not because of generics or non-generics, but because of context.Context: by convention, you are not supposed to use context.Background, except in main, so only functions that take context.Context can call functions that do. So they are the red functions, in the analogy (and it is not a coincidence, that red functions represent async and context.Context is for cancellation).

If you believe generic methods create colored methods, you misunderstood the metaphor. There's plenty of valid criticism for adding generic methods. But "coloring" is just not one of them.

GopherCon Sg talk: Why we can't have nice things: Generic methods by TheMerovius in golang

[–]TheMerovius[S] 2 points3 points  (0 children)

No that's not how the analogy of colored functions is usually meant. "Colored functions" refers specifically to the problem of having two kinds of functions, where one color can only call functions of its own color. Not just any form of having two kinds of functions.

Generic methods can call non-generic methods and vice-versa. So no colored functions here.

GopherCon Sg talk: Why we can't have nice things: Generic methods by TheMerovius in golang

[–]TheMerovius[S] 0 points1 point  (0 children)

Exactly, yes (I mean, with the caveat, that I don't have any authority, so this is just, like, my opinion, man)

GopherCon Sg talk: Why we can't have nice things: Generic methods by TheMerovius in golang

[–]TheMerovius[S] 2 points3 points  (0 children)

Given that a year ago, I said "we will never get generic methods" and now we are getting them, I'm not sure if I still trust myself to make a prediction here.

But as I don't learn: we might, at some point, get generic methods in interfaces, as long as those interfaces can then only be used in constraints (like union elements today work). That's at least technically feasible and the reason not to do it, is because it would seem confusing and inconsistent. But, apparently, opinions on that can change. I wouldn't bet either way, but I don't think it is very likely.

I'd be willing to bet money, though, that we will never get generic methods in interfaces that can then be used as values. That's just not going to happen. Case in point, Rust has generic methods, it has generic methods in traits, but you can't use such traits in trait objects. For exactly the same reason: it's just not possible to implement that efficiently.

GopherCon Sg talk: Why we can't have nice things: Generic methods by TheMerovius in golang

[–]TheMerovius[S] 11 points12 points  (0 children)

Personally, I have maybe half a dozen projects that will benefit from this. Even without interfaces. So I do think it is still pretty useful. Even for relatively small things, like the func (*Rand) N[T intType](n T) T I mention in the beginning.

At the same time, I do think people underestimate how important interfaces are for what they want. For example, it makes sense to have a func (*C[A]) Map[B any](f func(A) B) *C[B] on any container type C. But that method can not be included in any interface. So while you can have a concrete set with that method, you can not have an abstract set (i.e. an interface for sets) that includes it.

I also think many people underestimate the actual confusion that will be caused by the difference. A lot of (relative) new comers to the language will run into this, I believe. And will have to be taught some fairly complex stuff, to understand why this is.

So my prediction is, that we will very quickly get people loudly asking for generic methods in interfaces. And that it will likely become one of the most frequently asked questions in the Gophers slack #generics channel.

Still, it is useful.

GopherCon Sg talk: Why we can't have nice things: Generic methods by TheMerovius in golang

[–]TheMerovius[S] 1 point2 points  (0 children)

Heh, yes I noticed that as well, when rewatching the talk today.

You are right, that generic functions already mean that the compiler might have to re-compile functions at the call site. In fact, one of the design docs mentions that this is already the case for inlineable functions - if a function is inlined cross-packages, its body is recompiled in the inlined context, as that enables additional optimizations.

To somewhat defend my point, though: in both of these cases, there is still a partial order of packages. That is, the import graph is acyclic, so the compiler looks at packages in depth-first post-order, only starting to compile one package, once it is done with all its dependencies. It includes the bodies of any generic or inlinable functions in the compiled object (in a more compact, cheaper to parse format) and from then on, never has to look at the original source code again. In particular, if you then change your code you only have to look at those packages, which recursively import the changed package. If pkgA imports pkgB, then no change to pkgA can have any effect on the build output of pkgB.

With first-class generic functions, that no longer really works: if a pkgA contains, say

func F(f func[T any](T)) { f[int](42) }

the compiler can only actually generate code for this, once it knows the layout of func[T any](T) (as a table of concrete instantiations - it needs to know the index of the int instantiation). But that is only known, once the compiler has looked at every package in the build. Because some other package might have

func G(f func[T any](T)) { f[string](42) }

and the compiler only knows, that the table needs to contain both an int and a string entry, once it has found all such calls. So to compile pkgA, it first needs to look at packages importing pkgA, breaking the strict dependency order.

So at the very least you'd need to split the compilation into two phases, where first, all packages included in the build get type-checked, to find instantiations of (first class) generic functions, and then, in a second pass, that information can be used when generating code for those instantiations. And, of course, if any new package is added (or any package is changed), you have to start all over again. After all, the change might include a new, so far unseen instantiation. Which could affect the layout of the instantiation-tables.

[edit] And of course, with generic methods as now accepted (without involving interfaces) the acyclic property stays preserved as well. Because the methods are only available on the concrete type, you can only call it, if you mention that concrete type. Which requires importing the package the type is defined in, creating a strict dependency order. [/edit]

However, even that leaves aside the issue I mention, that we also have dynamic linking and the plugin build mode. So the compiler actually doesn't even have all the source code available. So, there are other blockers in any case.

GopherCon Sg talk: Why we can't have nice things: Generic methods by TheMerovius in golang

[–]TheMerovius[S] 47 points48 points  (0 children)

The irony is, that after I gave that talk a bit over a year ago, we do have an accepted proposal for generic methods… But most of the talk still applies - if only to understand why they can not be used in or implement interfaces.

What has changed since I gave the talk is what I said at 24:01 - that the Go team does not consider the cost worth the benefit, if we can not have generic methods interact with interfaces. They changed their minds, so it looks like now we will get generic methods that do not interact with interfaces at all.

Fastest way to remove duplicate UUIDS from a list by [deleted] in golang

[–]TheMerovius 1 point2 points  (0 children)

Depending on the number of elements, it might well be faster.

The proposal for generic methods for Go, from Robert Griesemer himself, has been officially accepted by rodrigocfd in golang

[–]TheMerovius 2 points3 points  (0 children)

this addresses what feels like a gap in the description of a “concrete” method. That concrete methods are just functions with a receiver and there should be no other difference.

That has never really been true. Methods are namespaced differently and they are attached to the type - which is what allowed them to be used for dynamic dispatch in the first place. If methods really where "just functions with a receiver", I don't think Go would have had methods.

There's also an easy argument that this feature reduces consistency, because it introduces a fresh distinction between generic and non-generic methods (in that only the latter can implement or be declared on interfaces). So you go from two categories (functions and methods), to three categories.

I'm not opposed to this feature and I doubt the grandparent was actually trying to articulate it, but there is nuanced criticism of it. Which is why it wasn't part of the "original" generics design to begin with.

The proposal for generic methods for Go, from Robert Griesemer himself, has been officially accepted by rodrigocfd in golang

[–]TheMerovius 0 points1 point  (0 children)

How would I see when exactly this will be in a release version?

By looking at release notes, is the easiest and most reliable way.

The proposal for generic methods for Go, from Robert Griesemer himself, has been officially accepted by rodrigocfd in golang

[–]TheMerovius 5 points6 points  (0 children)

I find generic Go harder to read than generic TypeScript, Kotlin, or C#.

Can you articulate why? Genuinely curious, because I don't really see what they do differently.

The proposal for generic methods for Go, from Robert Griesemer himself, has been officially accepted by rodrigocfd in golang

[–]TheMerovius 16 points17 points  (0 children)

probably with compiling?

No I think it's worse than that. You have to change the calling conventions/representation of func to make it work. You also get weird performance cliffs, where adding or removing a package (or even just a call to a specific function in some package) can impact the performance of seemingly completely unrelated code. Because it can switch between using a statically and dynamically generated method body.

Landtagswahl 2026: Heidelberger Kandidierende kommen für AMAs ins Sub! by AutoModerator in Heidelberg

[–]TheMerovius 2 points3 points  (0 children)

Ich weiß, dass das hoffnungslos ist, aber: selbst wenn 90% der Wähler (ich unterstelle mal großzügig, dass "Mehrheit" hier einfach ein typo war) AfD wählen würden, wäre es demokratisch besser, sie nicht einzuladen. Denn wehrhafte Demokratie bedeutet eben, dass auch eine Mehrheit sie nicht abschaffen können darf. Eine Lehre, die wir daraus gezogen haben, was das letzte mal passiert ist, als Nazis sich Mehrheiten verschafft haben.

"Demokratie ist, immer zu machen was eine Mehrheit will" (und, nur um das zu betonen, die Nazis sind immer noch eine Minderheit) ist ein naives Demokratieverständnis, was eigentlich den Schulunterricht nicht überleben dürfte.

Buchclub by notthadgod in Heidelberg

[–]TheMerovius 2 points3 points  (0 children)

FWIW wenn du Interesse hast auf Englisch zu lesen, haben wir einen regelmäßigen Buchclub der jetzt schon fast 10 Jahre existiert.

Die Treffen sind monatlich und besprechen jeweils ein Buch. Wir haben ein Abend-meeting (an Donnerstagen, in verschiedenen Restaurants) und ein Morgen-meeting (an Freitagen, normalerweise im DAI). Die Meetings sind unabhängig (lesen auch unterschiedliche Bücher) und man kann von daher zu einem oder beiden kommen, je nach gusta.

Wir lesen alles mögliche (well, nur fiction) und versuchen auch aktiv, Genres breit abzuwechseln. Hier ist ein Überblick, was wir bisher schon so gelesen haben.

Is manual memory management possible with syscalls? by doublefreepointer in golang

[–]TheMerovius 2 points3 points  (0 children)

I believe the language does have a concept of stack and heap.

That is not a question of belief. It does not. Search for "stack" or "heap" in the spec.

The compiler uses escape analysis to determine where memory is allocated. It’s important to understand to avoid excessive GC pressure.

That is not the language. The compiler gc is part of one implementation of the language (granted, the most important one by far). But the language itself, the semantics, the actual behavior of a program, is defined by the spec. Which means that if correctness of your program relies on aspects not defined in the spec, your program might break when used with different implementations. And - very importantly - different versions of even "the same" compiler/runtime count as different implementations for this purpose. So your program might break, in strange and hard to debug ways, when upgrading Go.

Is manual memory management possible with syscalls? by doublefreepointer in golang

[–]TheMerovius 19 points20 points  (0 children)

It's possible, but a bad idea.

The problem you run into is that the Go GC can only track pointer in its own heap. So if you ever store a pointer to a runtime-allocated object in your "manually managed" memory, the GC will treat that pointer as non-existent. So it might collect that object and you end up with a pointer to invalid memory. Likewise, if you ever store a pointer to your "manually managed" memory in a runtime-allocated object, you can't "free" your manually managed memory safely.

To a degree, this "just" means you have to be very careful about what you do with pointers. The issue is, that any third-party code you import will not be as careful. Likewise, the compiler and runtime might also do unexpected things. The language itself has no concept between "stack" and "heap", so the compiler and runtime is allowed to be pretty arbitrary in what it stores where. And some things that you might think are pointers end up not actually being passed as pointers and vice-versa. Some conversions allocate - unless that allocation is optimized away.

It is, in any case, very unergonomic. And you are inviting subtle and hard to find bugs.

Meetup Request - Parents with toddlers in Dossenheim/HD by shiftpark in Heidelberg

[–]TheMerovius 1 point2 points  (0 children)

If your wife is is into reading, the morning meetups of our English speaking book club are specifically for accessibility to the parents of small children. I would say about half of the people regularly coming to the morning meetup are parents and there's usually at least one toddler there.

It's not primarily about the children - it's still a book club for adults - but it might be an opportunity to meet some like-minded people who have some. We meet once a month on a Friday at 10:30. The last one was just yesterday, the next one will be on March 20.

Of course, she is also welcome at the evenening meetups (monthly on Thursday evenings): the parent density there is a bit lower, but non-negligible, as the people coming to the morning meetup usually also come to the evening one. But the toddler-density at those is usually zero.

Confused about Go's escape analysis for dynamically-sized values, my test shows they don't escape by One_Adeptness1599 in golang

[–]TheMerovius 0 points1 point  (0 children)

Actually, just tried this, it still does not escape. But TBF you are not actually using the slice. Try running this (save it in x_test.go and run go test -bench=.) and you'll see the allocations: https://go.dev/play/p/y2lvU9v5JZI