Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> Presumably you're adding to a "closed" set of types, so it's quite easy to grep for the type names in that type set

Yes, that's an option, but it's not nearly as nice as the compiler automatically telling you what's up, nor does it help downstream users of a library that adds a new possibility. They just have to hope the change is communicated to them.

> Never mind the fact that wildcard clauses exist and so this problem comes up in ML/Haskell/etc as well, since the compiler will consider those patterns exhaustive.

This is an explicit vs. implicit situation: by using a wildcard, the programmer has explicitly chosen to give up some compile-time assistance, whereas Go implicitly makes this choice for the programmer.

> Go already has this. It's called type-switch:

... I don't see how a facetious response like this is at all helpful. The downsides of type-switch are exactly what we're already discussing.

> traditional algebraic data types impose order on to fields, which is an undesirable feature IMHO

Go is its own language, and can choose to adopt/adapt features to suit its idioms. Referring everything back to how "traditional ADTs" work seems silly when there's trivial tweaks that fit Go better.

> However, field dispatch loses the nice order-independence attribute.

I don't know what you mean by this (I can guess, but I don't see how it relates to naming fields in patterns), could you rephrase?



> nor does it help downstream users of a library

If you add a new type to a sum type in a statically typed language, that's a breaking change for all downstream consumers. Again, you may view this as a benefit (clients get compiler errors about new cases to handle!), but I view it as a drawback (clients can't upgrade until they handle all new cases!).

> Go implicitly makes this choice for the programmer

And I believe that Go (and Clojure, which omits both types and pattern matching for reasons including those I'm discussing) makes the right decision for the programmer, as openness to future changes without breakage is better > 90% of the time.

> could you rephrase?

You said "one can pattern-match using the names of fields", I took that to mean matching on something like Foo{x: 5}. If you do that and also have a clause Foo{y: 10}, now you have ambiguity and the order you compare x==5 or y==10 first matters, since you might have an object Foo{x: 5, y: 10} which would match both clauses.

If you just meant that you could do Foo{x: x} with blanks, that's fine, we're in agreement then. Clojure offers {:keys [x]} in destructuring for this purpose, and it's great.


For both of the first points: I don't think anyone's proposing that closed sum types be the only polymorphism in Go (removing the existing open polymorphism would be a wild breaking change and so isn't even a remote possibility), so this would be a feature for when alerting consumers that something has broken is good. When flexibility is what your library needs, use interfaces, when reliability (or performance[0]) is what it needs, use ADTs.

One argument against adding the new feature is it forces people to think about which to use, and they may choose an inappropriate one. (This choice argument is far stronger than vague concerns about the feature possibly resulting in breaking changes.)

In any case, libraries can already make breaking changes, and presumably quite a few want to/do (so it's not like having a feature that can also result in breaking changes is anything new), and, adding a new type to existing open set can easily be a semantic breaking change that stops code from running correctly, even if it doesn't stop code compiling.

> You said "one can pattern-match using the names of fields", I took that to mean matching on something like Foo{x: 5}. If you do that and also have a clause Foo{y: 10}, now you have ambiguity and the order you compare x==5 or y==10 first matters, since you might have an object Foo{x: 5, y: 10} which would match both clauses.

No, that is orthogonal, as I also discussed in my original reply.

I was visualising something like `Foo { x: binding }` in the pattern, which would make the value of the x field available under the name `binding` (and, for convenience, `Foo { x: x }` case could be allowed to be abbreviated to `Foo { x }`). Whether the binding on the right-hand side of a field allows pattern matching or not is its own discussion.

[0]: ADTs aren't forced to allocate, and possibly use a more efficient switching scheme (it's just an integer for an ADT; I don't know the implementation details of Go's type-switch so it might be that fast).


I'm going to dodge the breaking changes discussion, since it's a huge tangent, suffice to say that I greatly dislike _breaking_ changes and want languages that make it possible to avoid them or at least provide a migration path via incremental deprecation prior to removal. Go fails here in many ways. All I meant to do was point out that exhaustiveness checks are not without their tradeoffs.

> I was visualising something like

OK then. We're in agreement on that point. This is what Clojure has in it's destructuring syntax, and, as I said, it's great.


> I'm going to dodge the breaking changes discussion, since it's a huge tangent,

How is it a tangent? One of the biggest reasons to want exhaustiveness checking is to avoid the even uglier problem of "compiling but semantic changing" changes.

> I greatly dislike _breaking_ changes and want languages that make it possible to avoid them

It seems you are only talking about disliking breaking changes that cause your program not to compile whole totally disregarding the danger of changes that compile but change semantics.

As respectfully as possible, that seems backwards to me.


> All I meant to do was point out that exhaustiveness checks are not without their tradeoffs.

And all I meant to do was point out that lack of exhaustiveness checks is not without its tradeoffs. :)


Imagine an enum describing the various states of a state machine. I'd definitely prefer the compiler to refuse compiling, rather that hoping to be future proof by ignoring a new state.


> If you add a new type to a sum type in a statically typed language, that's a breaking change for all downstream consumers.

Yes, but that's true regardless of whether the compiler tells you or not. The question is whether adding the value to the set of types that can be returned breaks at compile time or at run time.


> If you add a new type to a sum type in a statically typed language, that's a breaking change for all downstream consumers.

Not true, consumers that have an explicit "catch-all" pattern at the end will work normally




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: