top | item 29230537

A rough proposal for sum types in Go (2018)

101 points| isaacimagine | 4 years ago |manishearth.github.io | reply

71 comments

order
[+] ibraheemdev|4 years ago|reply
The type sets proposal for Go has already been accepted as a clarification to the generics proposal [0]:

    type SignedInteger interface {
        ~int | ~int8 | ~int16 | ~int32 | ~int64
    }
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.

[0]: https://github.com/golang/go/issues/45346

[+] saghm|4 years ago|reply
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.
[+] pcwalton|4 years ago|reply
That looks like a great feature to add to Go! It seems to make the original article (which is from 2018) obsolete.
[+] adtac|4 years ago|reply
What's the best way to read about all the new accepted grammar developments in Go in the last year or two?
[+] kadoban|4 years ago|reply
Yes please! I really dislike every golang function returning "foo*, error" when it really means either foo or an error.
[+] feffe|4 years ago|reply
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 :-)

[+] bborud|4 years ago|reply
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.
[+] Ciantic|4 years ago|reply
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.

[+] Andys|4 years ago|reply
Won't the generics feature type sets fulfill this use case?
[+] akira2501|4 years ago|reply
Many functions return foo AND the error. This is occasionally very useful.
[+] pkaye|4 years ago|reply
So why not use Rust instead since it already implements sum types?
[+] titzer|4 years ago|reply
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).

[+] mafalda|4 years ago|reply
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.

[+] valenterry|4 years ago|reply
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.
[+] jerf|4 years ago|reply
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.)

[+] ben0x539|4 years ago|reply
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.
[+] renlo|4 years ago|reply
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
[+] valenterry|4 years ago|reply
Go still seems to be a bit behind. Sumtypes should be standard in every new language - and better type-systems also offer union types now.
[+] kibwen|4 years ago|reply
Note that this is from three years ago, and should have (2018) appended to the title.
[+] dang|4 years ago|reply
Added. Thanks!
[+] xiaodai|4 years ago|reply

[deleted]

[+] ben0x539|4 years ago|reply
Templates? Generics are already on the way, this is a very unrelated suggestion, and seems much less complicated.
[+] avaldes|4 years ago|reply
> Stop trying to make Go complicate.

You mean enterprise-y.

[+] stevefan1999|4 years ago|reply
but golang already have a sum type: multiple return values
[+] saghm|4 years ago|reply
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.
[+] eksapsy|4 years ago|reply
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.