5 ms·
It's redundant because Go already has type-switch and type-assertions. Your comments about ints vs arrays vs behavior misses an important fact: An object of any
by brandonbloom 10y ago
It's redundant because Go already has type-switch and type-assertions. Your comments about ints vs arrays vs behavior misses an important fact: An object of any type can be promoted to interface{} (aka "dynamic") and then may be "pattern matched" on via `x.(type)`. Sure, it's pretty crummy pattern matching to only be able to dispatch on a single tag, but there are some fundamental problems with traditional algebraic data types and pattern matching:
1) Abstract data types encourages closed systems. You may view this as a positive: It enables exhaustiveness checks. But I view it as a way to make your program more fragile. Go's type asserts let you convert to interface types, so you can add new interface implementers later and not have to go fix-up old type-assertions.
2) First-to-match pattern matching complects order with dispatch. Each match clause has an implicit dependency on _all_ of the clauses before it, since you can match Foo{x, 1} and if that fails match Foo{x, y} where you know now that y != 1. This is sometimes useful, but as your patterns grow larger, it's simpler to just match Foo{x, y} and then branch on y == 1. A series of type-asserts with if statements has a little bit of order dependency on it: interface clauses and named wrapper types are first-to-match, but type-switch on struct clauses are completely order independent because there can only be one concrete representation underlying an interface{}.
3) Relying on positional fields causes two classes of problems: A) it's harder to grow your system later, since every pattern match needs to mention the new field you added and B) you can't _not_ mention a field by name (or at least give it a placeholder name) at every use as well. This is the same issue as the Foo{x, y} vs Foo{X: x, Y: y} notation. It's considered good practice in Go to use the later, since it's more future proof, ie Foo may grow a Z field and it will be initialized to zero.
- dbaupp 10y ago> so you can add new interface implementers later and not have to go fix-up old type-switches. Or, rephrased: the compiler doesn't help you find the old type-switches that need new branches. As you say, being closed gives exhaustiveness checks, and this is often a good thing: it's harder to accidentally not handle new cases in existing code (or not handle existing cases, when writing new code), and it makes refactoring much easier. For some code the flexibility of being open isn't the right choice, just like for some code, it is. > First-to-match pattern matching complects order with dispatch This seems orthogonal to ADTs: one can retain retain behaviour like Go's current switch, and only allow switching on the variant, not doing more detailed pattern matching. This retains order independence. > Relying on positional fields causes two classes of problems Again, totally irrelevant: one can pattern-match using the names of fields.
- brandonbloom 10y ago> the compiler doesn't help you find the old type-switches that need new branches 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. I just don't find this to be that much of a problem in practice. 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. > only allow switching on the variant Go already has this. It's called type-switch: https://golang.org/doc/effective_go.html#type_switch https://golang.org/doc/effective_go.html#type_switch > one can pattern-match using the names of fields Of course they can, but traditional algebraic data types impose order on to fields, which is an undesirable feature IMHO. However, field dispatch loses the nice order-independence attribute.
- dbaupp 10y ago> 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?
- brandonbloom 10y ago
- bsaul 10y agoI'm thinking pretty much the opposite regarding fragility and growing your system. The fact that adding a case in the enum have the compiler tells you all the places your programm needs to handle the case is a tremendous bonus ! The problem is that we're not talking about the same things. You're talking about making new things behave the way the an existing interface wanted, and i'm talking about a data structure updating. Once again, interface is not data..
- brandonbloom 10y ago> the compiler tells you all the places your programm needs to handle the case is a tremendous bonus Except this is simply not true if you have any wildcard patterns in any of your matches. Besides, it's easy to grep for one or each of the names of the types in your closed sum. Which is what you're going to do anyway if you have any wild card patterns! > interface is not data I don't know what you intend to communicate by saying this.
- bsaul 10y ago"this is not true if.." Well, yes indeed, you can ignore features of a language, but i don't see how it prooves any point. As for the grep, i really have no idea what you're talking about ( maybe advanced pattern matching features of advanced language ?). I'm really talking about something very basic, like what swift is doing. My point about interface vs data is that those are two different things, and that i don't think you can't try to solve an issue on type compositions with a feature working on functions only.
- solatic 10y agoFragility? Those exhaustive compiler checks are anti-fragile. There's a binary - either your compiler tells you early on where you need to adapt your code, or your automated test suite / customers will tell you in integration / production. Your code is likely to break either way, the question is how early / cheap would you like the fix to be. Code which is cheaper to fix is less fragile QED
- lmm 10y ago> 1) Abstract data types encourages closed systems. You may view this as a positive: It enables exhaustiveness checks. But I view it as a way to make your program more fragile. Go's type asserts let you convert to interface types, so you can add new interface implementers later and not have to go fix-up old type-assertions. Is the idea here that all types should be open? Should I be able to e.g. add 1.5 as a possible instance for the Int and String types? Interfaces should be open, but one also needs the ability to represent closed datatypes.