4 ms·
I think this is mistaken. Go already has a way to represent 'open' union types (interfaces), so all of these runtime problems have already been solved. What's m
by foldr 2y ago
I think this is mistaken. Go already has a way to represent 'open' union types (interfaces), so all of these runtime problems have already been solved. What's missing is just the type system support to do exhaustive matching on the members of the union. With the addition of generics, 'all' that would be necessary is to make the following a legal variable definition:
var foo interface {
struct { A int } | struct { B string }
}
It currently fails with the following error:
"cannot use type interface{struct{A int} | struct{B string}} outside a type constraint: interface contains type constraints"
- jerf 2y agoThe problem with that is that in Go, I need to be able to put methods on those things, for reasons possibly unrelated to the interface in question. For that they need to be named. For that you might as well do what has worked since Go 1.0 and just put an unexported method in the interface and declare several instances of that interface in your package. Honestly interfaces with unexported methods are 90%+ of what people want. It's just not spelled the way they expect. And if you're not going to be happy except at absolutely 100%, a position I can and do respect, there's no point waiting for Go to get any better because I can guarantee you no Go proposal for sum types will fix that you will be forced to have a "nil" value in the sum type, so there's no point in waiting.
- wbl 2y agoThe zero would obviously be the zero value of one of the constituents. Coproducts in most of Go are easy: it's interfaces that make it hard.
- masklinn 2y ago> The zero would obviously be the zero value of one of the constituents. If interfaces are used for union types then the obvious zero is nil, not any constituent. Nil is the zero value of interfaces. It’s perfectly consistent and in line with the rest of the langage.
- wbl 2y agoIt's not clear to me that that would satisfy the categorical properties of coproduct that are wanted.
- foldr 2y agoNobody cares about the categorical properties of the coproduct. But in any case, there's no theoretical issue here, since it would be just as if every enum had a 'Nil' variant implicitly defined.
- foldr 2y agoYou might want to layer some sugar on top to enable naming of the variants. I was just trying to keep my example as close as possible to valid Go code. Even if you have to manually define separate named structs, you still have the benefit of exhaustivity checking in type switches. That's arguably the other 10% that people want.
- eru 2y agoYou also want to support having the same type on both sides: var foo interface { struct { A string } | struct { B string } } Eg in Rust that would be 'Result<String, String>', where your success happens to be a String and your errors happens to be a String error message.
- siebenmann 2y agoInterfaces aren't bit-packed and they force storing all values as a separate allocation that the interface contains a pointer to (escape analysis may allow this separate value to be on the stack, along with the interface itself). I believe that Go used to have an optimization where values that fit in a pointer were stored directly in the interface value, but abandoned it, perhaps partly because of the GC 'is it a pointer or not' issue. In my view, some of what people want union types for is exactly efficient bit-packing that uses little or no additional storage, and they'd be unhappy with a 'union values are just interface values' implementation. (I'm the author of the linked to article.)
- foldr 2y agoA separate allocation is not forced. The implementation could allocate a block of memory large enough to hold the two pointers for the interface value together with the largest of the types that implements the interface. (You can't do that with an open interface because there's no upper bound, but the idea here is to let you define closed interfaces.) In cases where there is a lot of variance in the size of the different interface implementations, separate allocations could actually be more memory efficient than a tagged union. In any case, I'm not sure that memory efficiency is the main reason that people miss Rust-style enums in Go.
- siebenmann 2y agoThe problem with allocating bit-packed storage is that then you are into the issue where types don't agree on where any pointers are. Interface values solve this today because they are always mono-typed (an interface value always stores two pointers), so the runtime is never forced to know the current pointer-containing shape of a specific interface value. And the values that interface values 'contain' are also always a fixed type, so they can be allocated and maintained with existing GC mechanisms (including special allocation pools for objects without pointers and etc etc). I agree with you about the overall motivation for Rust-style enums. I just think it's surprisingly complex to get even the memory efficiency advantages, never mind anything more ambitious.
- 2y ago