5 ms·
I'm surprised by the complexity of Go's generic constraints, given the language's focus on simplicity. Things like the difference between "implementing" and "sa
by pansa2 2y ago
I'm surprised by the complexity of Go's generic constraints, given the language's focus on simplicity. Things like the difference between "implementing" and "satisfying" a constraint [0], and exceptions around what a constraint can contain [1]:
> A union (with more than one term) cannot contain the predeclared identifier comparable or interfaces that specify methods, or embed comparable or interfaces that specify methods.
Is this level of complexity unavoidable when implementing generics (in any language)? If not, could it have been avoided if Go's design had included generics from the start?
[0] https://stackoverflow.com/questions/77445861/whats-the-difference-between-types-implements-and-types-satisfies https://stackoverflow.com/questions/77445861/whats-the-diffe...
[1] https://blog.merovius.de/posts/2024-01-05_constraining_complexity/ https://blog.merovius.de/posts/2024-01-05_constraining_compl...
- rendaw 2y agoThere are tons of random limitations not present in other languages too, like no generic methods.
- bigdubs 2y agoThat's not a random limitation, there are very specific reasons[1] you cannot easily add generic methods as struct receiver functions. [1] https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#No-parameterized-methods https://go.googlesource.com/proposal/+/refs/heads/master/des...
- abound 2y agoFor someone not well-versed in language implementation details, it may very well feel random. I've been using Go as my primary language for a decade, and the lack of generics on methods was surprising to me the first time I ran into it, and the reasoning not obvious.
- rendaw 2y agoYeah. I'm not claiming they didn't back themselves into a corner here. There's no theoretical reason not to have it, the reason is because of a random intersection of other design decisions... unless you're saying they made those choices fully expecting to have these restrictions on generics later?
- the_gipsy 2y ago> Or, we could decide that parameterized methods do not, in fact, implement interfaces, but then it's much less clear why we need methods at all. If we disregard interfaces, any parameterized method can be implemented as a parameterized function. What? Methods are not needed if not for implementing an interface? Anyway, functions could also be implementing interfaces, some languages allow that. I swear the go docs read like a cult.
- int_19h 2y agoFunctions in Go can be generic, just not methods. And unless you're also using interfaces, methods are no different from functions aside from call syntax.
- the_gipsy 2y agoBut "methods are only needed because of interfaces" is simply not true. Not true in all other OOP languages that I know of, not true in go, and not true in go's stdlib (that is, in practice). Methods bind state with a function. That an object can satisfy an interface is secondary here. In different languages, an interface could be satisfied with a combination of methods, fields, or nominality. If the statement "we could decide that parameterized methods do not, in fact, implement interfaces, but then it's much less clear why we need methods at all" was true, then there should not be a single struct in go (stdlib nor elsewhere) that does not implement some interface (and it must be used via that interface to make sense). This is obviously not the case.
- int_19h 2y agoIf the method is not dynamically dispatched, it is exactly equivalent to a function with receiver passed as the first argument. The receiver-dot notation is just a convenient form of implicit namespacing, then, nothing more. And, in Go, methods are only dynamically dispatched on the receiver in the context of interfaces. So, everything else is just syntactic sugar. And what the doc is saying is that supporting this syntactic sugar makes the spec much more complicated, so they deemed it not worthwhile, given that a global function works just as well in this context.
- foldr 2y agoRust has a similar restriction on trait objects, for similar reasons. https://doc.rust-lang.org/reference/items/traits.html#object-safety https://doc.rust-lang.org/reference/items/traits.html#object...
- burakemir 2y agoGenerics are a powerful mechanism, and there is a spectrum. The act of retrofitting generics on go without generics certainly meant that some points in the design space were not available. On the other hand, when making a language change as adding generics, one wants to be careful that it pulls its own weight: it would be be sad if generics had been added and then many useful patterns could not be typed. The design choices revolve around expressivity (what patterns can be typed) and inference (what annotations are required). Combining generics with subtyping and inference is difficult as undecidability looms. In a language with subtyping it cannot be avoided (or the resulting language would be very bland). So I think the answer is no, this part of the complexity could not have been avoided. I think they did a great job at retrofitting and leaving the basic style of the language intact - even if I'd personally prefer a language design with a different style but more expressive typing.
- jerf 2y agoIn practice, none of this impacts your program. The standard advice I give to people messing around with this stuff is, never use the pipe operator. The standard library already implements all the sensible uses of it. In particular, people tend to read it as the "sum type" operator, which it is not. I kind of wish the syntax has used & instead of |, what it is doing is closer to an "and" then an "or". By the time you know enough to know you can ignore that advice, you will. But you'll also likely find it never comes up, because, again, the standard library has already implemented all the sensible variants of this, not because the standard library is magic but because there's really only a limited number of useful cases anyhow. I haven't gone too crazy with generics, but I have used them nontrivially, even done s could tricks [1], and the pipe operator is not that generally useful. When the generic constraint is an interface with methods is the case that can actually come up, but that makes sense, if generics make sense to you at all. It probably is a good demonstration of the sort of things that come up on generic implementations, though. Despite the rhetoric people often deployed prior to Go having them, no, they are never easy, never without corner cases, never without a lot of complications and tradeoffs under the hood. Even languages designed with them from the beginning have them, just better stuffed under the rug and with less obvious conflict with other features. They're obviously not impossible, and can be worthwhile when deployed, certainly, but it's always because of a lot of work done by the language designers and implementations, it's never just "hey let's use generics, ok, that one sentence finishes the design I guess let's go implement them in a could of hours". [1]: Just about the edge of the "tricky" I'd advise: https://github.com/thejerf/mtmap https://github.com/thejerf/mtmap
- tapirl 2y ago> In particular, people tend to read it as the "sum type" operator, which it is not. I kind of wish the syntax has used & instead of |, what it is doing is closer to an "and" then an "or". I don't understand here. In my understanding, the pipe operator is indeed closer to "or" and "sum type" operator. Interpreting it as "and" is weird to me.
- Groxx 2y agoI think they're reading it as "a bitwise-and of the functionality of the types passed", which is accurate (since you're getting the lowest common denominator of all |'d types). I'm... not sure which way I lean tbh, now that I've seen that idea. Both have merit, it's more of a problem for educational material than anything. If you present it as "these types", | makes sense. If you instead use "these behaviors", & makes sense. | is slightly easier to type for me though, and & has more meanings already (address-of), so maybe I'd still favor |.
- tapirl 2y agoThe difference between types.Implements and types.Satisfies is mainly caused by a history reason. It is just a tradeoff between keeping backward compatibility and theory perfection. It is pity that Go didn't support the "comparable" interface from the beginning. If it has been supported since Go 1.0, then this tradeoff can be avoided. There are more limitations in current Go custom generics, much of them could be removed when this proposal (https://github.com/golang/go/issues/70128 https://github.com/golang/go/issues/70128) is done. I recommend people to read Go Generics 101 (https://go101.org/generics/101.html https://go101.org/generics/101.html, author here) for a thoroughly understanding the status quo of Go custom generics.
- BobbyJo 2y agoI wish type constraints had a different Golang type than actual interfaces. "Is one of" and "Implements" seem like different enough concepts to warrant divergence there.