[HN Gopher] A rough proposal for sum types in Go (2018)
       ___________________________________________________________________
        
       A rough proposal for sum types in Go (2018)
        
       Author : isaacimagine
       Score  : 69 points
       Date   : 2021-11-15 18:14 UTC (4 hours ago)
        
 (HTM) web link (manishearth.github.io)
 (TXT) w3m dump (manishearth.github.io)
        
       | titzer wrote:
       | 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).
        
       | kadoban wrote:
       | Yes please! I really dislike every golang function returning
       | "foo*, error" when it really means either foo or an error.
        
         | bborud wrote:
         | 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.
        
         | pkaye wrote:
         | So why not use Rust instead since it already implements sum
         | types?
        
           | tialaramex wrote:
           | They said they liked sum types, that doesn't automatically
           | mean they want to learn Rust. I liked the STL containers when
           | I read about them in the 1990s, but I don't like C++ and the
           | existence of the containers did not change that.
        
           | kadoban wrote:
           | Because I have a job and am not yet a dictator at my company
           | for technical decisions.
        
           | [deleted]
        
           | ziml77 wrote:
           | Because Rust throws out the GC which means that you have to
           | deal with complexities of lifetime management.
           | 
           | (I don't agree that sum types should be added to Go, as much
           | as I dislike the language as it is)
        
         | akira2501 wrote:
         | Many functions return foo AND the error. This is occasionally
         | very useful.
        
           | tialaramex wrote:
           | That's not an error then, or, that's not what I'd consider a
           | result, or both.
           | 
           | Compare Rust's https://doc.rust-
           | lang.org/std/str/fn.from_utf8.html
           | 
           | If you've got a buffer full of bytes, which you hope are UTF8
           | text but maybe aren't, call std::str::from_utf8 and you
           | _either_ get back a string slice with your text in it, _or_
           | you get a std::str::Utf8Error structure which explains what
           | the problem was in an actionable format. You can write code
           | to process this Utf8Error and your bytes - maybe you 're
           | reading blocks of data and it's all valid UTF8 but the last
           | character is continued in the next block for example, you can
           | discern that from Utf8Error and just turn the valid part into
           | UTF8 and keep the rest until the next block is available.
        
           | kadoban wrote:
           | Ocassionally, but pretty rarely in my experience. Both types
           | of functions should exist, it should not be so hard to return
           | an either/or.
        
         | feffe wrote:
         | 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 :-)
        
         | Andys wrote:
         | Won't the generics feature type sets fulfill this use case?
        
       | xiaodai wrote:
       | Stop trying to make Go complicate. Stop try my to add templates.
       | Just stop. KISS
        
         | avaldes wrote:
         | > Stop trying to make Go complicate.
         | 
         | You mean enterprise-y.
        
         | ben0x539 wrote:
         | Templates? Generics are already on the way, this is a very
         | unrelated suggestion, and seems much less complicated.
        
       | kibwen wrote:
       | Note that this is from three years ago, and should have (2018)
       | appended to the title.
        
         | dang wrote:
         | Added. Thanks!
        
       | ibraheemdev wrote:
       | 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
        
         | adtac wrote:
         | What's the best way to read about all the new _accepted_
         | grammar developments in Go in the last year or two?
        
           | benhoyt wrote:
           | The Go grammar/language changes very slowly -- most of the
           | improvements are in the tooling and libraries, so there isn't
           | much. But the best way is the release notes. For the last two
           | years (four versions):
           | 
           | https://golang.org/doc/go1.17: three very minor changes for
           | array pointers and unsafe
           | 
           | https://golang.org/doc/go1.16: no language changes
           | 
           | https://golang.org/doc/go1.15: no language changes
           | 
           | https://golang.org/doc/go1.14: minor change to allow
           | "overlapping interfaces"
           | 
           | Of course, in 1.18 -- coming out in about three months --
           | there's the huge change to add generics (type parameters).
           | The best way to read about that is in the type parameters
           | proposal here: https://go.googlesource.com/proposal/+/refs/he
           | ads/master/des...
           | 
           | Go 1.18 will also add built-in fuzzing support, but again,
           | that's a tooling/library change, not a grammar/language one.
        
         | saghm wrote:
         | 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.
        
           | masklinn wrote:
           | With this proposal the variants are simply types themselves,
           | so you'd have a struct Ok[T] and Err [T], and thus Result<T,
           | E> would be something like                   interface
           | Result[T, E] {             for Ok[T], Err[E]         }
           | 
           | And there's no issue with having T=string and E=string.
        
           | pcwalton wrote:
           | Yeah, the standard solution is to create a newtype for each
           | such variant. I believe this is the preferred idiom in
           | languages like TypeScript that have union types but not
           | _discriminated_ union types.
        
           | cube2222 wrote:
           | They don't implicitly convert (though you can still use
           | literals to create them). So yes, specifying Ok and Error
           | string types should hypothetically work for this.
        
           | ibraheemdev wrote:
           | I believe this would work with type sets:
           | type Result[T any, E any] interface {             T | E
           | }
        
         | pcwalton wrote:
         | That looks like a great feature to add to Go! It seems to make
         | the original article (which is from 2018) obsolete.
        
       | jerf wrote:
       | 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 wrote:
         | 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 wrote:
         | 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
        
       ___________________________________________________________________
       (page generated 2021-11-15 23:00 UTC)