Interfaces that contain type sets are only allowed to be used in generic constraints. However, a future extension might permit the use of type sets in regular interface types:
> We have proposed that constraints can embed some additional elements. With this proposal, any interface type that embeds anything other than an interface type can only be used as a constraint or as an embedded element in another constraint. A natural next step would be to permit using interface types that embed any type, or that embed these new elements, as an ordinary type, not just as a constraint.
> We are not proposing that today. But the rules for type sets and methods set above describe how they would behave.
Any type that is an element of the type set could be assigned to such an interface type. A value of such an interface type would permit calling any member of the corresponding method set.
> This would permit a version of what other languages call sum types or union types. It would be a Go interface type to which only specific types could be assigned. Such an interface type could still take the value nil, of course, so it would not be quite the same as a typical sum type.
> In any case, this is something to consider in a future proposal, not this one.
This along with exhaustive type switches would bring Go something close to the sum types of Rust and Swift.
One difference between this proposal and Rust enums (i.e. tagged unions) is that enums let you use the same type more than once with a different tag. Obviously Go doesn't have generics yet, but something like `Result<String, String>` doesn't seem like it would be straightforward as a non-generic type either with a type set, since you don't have any way of differentiating between which "type" of string you might have. I _think_ this might be possible with a typeset by defining a newtype for one or both of the string types, but I haven't used Go in long enough that I don't remember if newtypes will implicitly convert to the type they wrap or not.
Sum types would be nice to describe protocols such a grpc as well (given suitable syntactic sugar to switch on them).
I think sum types, possibly in combination with tuples would have been nicer than multiple return values. But I guess everyone has their particular wish list of what a programming language should be :-)
I disliked it initially. After 5-6 years of using Go as my primary language I’ve come to appreciate it. It is clearer than having to figure out what is returned. And if an error can be returned. It’s easier when it is right there in the function signature.
That's right. This is about the language too, if Go doesn't have pattern matching, using sum types (or tagged unions) is a bit gnarly.
This is the reason for instance in C# Hot Chocolate GraphQL library the newly proposed return type for fallible mutations is, e.g. for hypothethtical register function:
{
user: Option<User>,
errors: [Errors]
}
Instead of more type-correct way of:
union result = user | Errors
Precisely because using unions is gnarly in JS who are the main consumers of the API.
Similarly if GO doesn't have a way to pattern match, the actually using sum types is very annoying and this "foo*, error" can be seen as more convinient.
I didn't see a discussion of equality checking here, so I'll mention it. It's really useful if sum types (aka variants or algebraic data types) do not have identity, i.e. that creating two identical values with the same tag (or case) with the same values compare equal. This allows the compiler to represent sums efficiently, e.g. by packing two 32-bit values into a single 64-bit word. With reference identity (two constructed values are only equivalent if they refer to the same on-heap representation), then this optimization becomes harder (basically only works with escape analysis).
Virgil has variants and also enums. They are slightly different things, though treated much the same under the hood. Neither have identity. (How they are different is that enums can have arguments in Virgil, but there are still is a fixed list of named values, whereas variants have named cases and are potentially recursive).
I’m not a daily go programmer. I’ve learned the language two years ago and know some the background behind it. Based on that I don’t see a place for sum types in Go because it does not solve a problem that is hard to solve with modern Go code.
But considering the proposal itself. I would approach it much differently.
Sum types could be represented as a closed enum.
type Result enum int{…}
const ( Ok Result = iota{interface{}}, Err Result = iota{string} )
The initialization would be done like:
Ok{10}
Err{“Something went wrong”}
The variable itself would act just like and other enum with unpacking being done somewhat close to this:
Ok{variableName} := result
Where the variableName would be set to the zero value in case of a bad match.
The enum prefix would enforce a single const block and no extension outside the module package.
You are seeing it through the lense of an application developer. As a library author the world looks different though - here you cannot know/define the types in advance.
What does this get you that you don't get by putting an unexported method in your Go interface today? Honest question, since I'm not 100% sure I'm sharing the terminology of the author, and I don't know if this is a matter of the author being unaware of this possibility, or aware and not satisfied for some reason I don't understand.
You don't get a literal enumeration of all implemented types in the interface specification itself, but it's still trivial/mechanical to extract all implementations of such an interface with some code tool, and no external package can add to the list.
(Since I've learned from experience this always comes up: If you put a method named 'unexported' on your interface, it doesn't matter if a value in some other package creates a method called 'unexported', the compiler will not consider it a match. An interface in some package with an unexported method can not be implemented by any other package.)
In this proposal, the compiler will yell at you if you switch over the contained type of a value of the interface type and don't have a case for each possible type.
I think a difference is that current behavior is to check the interface at runtime vs static check ensuring that the interface's underlying value is one of those types. Have personally seen a number of bugs (ie panics) from the runtime checks
Multiple returns are a product, not a sum; in the case of (*Foo, error), you could return nil for both, or neither, which gives you four possible combinations (nil and nil, nil and an error, Foo and nil, and Foo and an error). A sum type would not let you return both a Foo and an error, just one or the other.
This is a huge misunderstanding of what Sum types are. Someone already answered why multiple return types are completely different from sum types, I will also mention that a type is usually something that you define for variable types like int and string and structs, not only for what a function returns.
[+] [-] ibraheemdev|4 years ago|reply
> We have proposed that constraints can embed some additional elements. With this proposal, any interface type that embeds anything other than an interface type can only be used as a constraint or as an embedded element in another constraint. A natural next step would be to permit using interface types that embed any type, or that embed these new elements, as an ordinary type, not just as a constraint.
> We are not proposing that today. But the rules for type sets and methods set above describe how they would behave. Any type that is an element of the type set could be assigned to such an interface type. A value of such an interface type would permit calling any member of the corresponding method set.
> This would permit a version of what other languages call sum types or union types. It would be a Go interface type to which only specific types could be assigned. Such an interface type could still take the value nil, of course, so it would not be quite the same as a typical sum type.
> In any case, this is something to consider in a future proposal, not this one.
This along with exhaustive type switches would bring Go something close to the sum types of Rust and Swift.
[0]: https://github.com/golang/go/issues/45346
[+] [-] saghm|4 years ago|reply
[+] [-] pcwalton|4 years ago|reply
[+] [-] adtac|4 years ago|reply
[+] [-] kadoban|4 years ago|reply
[+] [-] feffe|4 years ago|reply
I think sum types, possibly in combination with tuples would have been nicer than multiple return values. But I guess everyone has their particular wish list of what a programming language should be :-)
[+] [-] bborud|4 years ago|reply
[+] [-] Ciantic|4 years ago|reply
This is the reason for instance in C# Hot Chocolate GraphQL library the newly proposed return type for fallible mutations is, e.g. for hypothethtical register function:
Instead of more type-correct way of: Precisely because using unions is gnarly in JS who are the main consumers of the API.Similarly if GO doesn't have a way to pattern match, the actually using sum types is very annoying and this "foo*, error" can be seen as more convinient.
[+] [-] Andys|4 years ago|reply
[+] [-] akira2501|4 years ago|reply
[+] [-] pkaye|4 years ago|reply
[+] [-] titzer|4 years ago|reply
Virgil has variants and also enums. They are slightly different things, though treated much the same under the hood. Neither have identity. (How they are different is that enums can have arguments in Virgil, but there are still is a fixed list of named values, whereas variants have named cases and are potentially recursive).
[+] [-] mafalda|4 years ago|reply
But considering the proposal itself. I would approach it much differently.
Sum types could be represented as a closed enum.
The initialization would be done like: The variable itself would act just like and other enum with unpacking being done somewhat close to this: Where the variableName would be set to the zero value in case of a bad match.The enum prefix would enforce a single const block and no extension outside the module package.
[+] [-] valenterry|4 years ago|reply
[+] [-] jerf|4 years ago|reply
You don't get a literal enumeration of all implemented types in the interface specification itself, but it's still trivial/mechanical to extract all implementations of such an interface with some code tool, and no external package can add to the list.
(Since I've learned from experience this always comes up: If you put a method named 'unexported' on your interface, it doesn't matter if a value in some other package creates a method called 'unexported', the compiler will not consider it a match. An interface in some package with an unexported method can not be implemented by any other package.)
[+] [-] ben0x539|4 years ago|reply
[+] [-] renlo|4 years ago|reply
[+] [-] valenterry|4 years ago|reply
[+] [-] kibwen|4 years ago|reply
[+] [-] dang|4 years ago|reply
[+] [-] xiaodai|4 years ago|reply
[deleted]
[+] [-] ben0x539|4 years ago|reply
[+] [-] avaldes|4 years ago|reply
You mean enterprise-y.
[+] [-] stevefan1999|4 years ago|reply
[+] [-] saghm|4 years ago|reply
[+] [-] eksapsy|4 years ago|reply