3 ms·
> 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 downstrea
by brandonbloom 10y ago
> 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.
- bsaul 10y agoImagine 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.
- dbaupp 10y agoFor 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).
- brandonbloom 10y agoI'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.
- dbaupp 10y ago> 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. :)
- codygman 10y ago> 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.
- spion 10y ago> 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
- ori_b 10y ago> 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.