6 ms·
Along with generics, they should probably also reconsider algebraic data types, such a enums with values. This is the best feature swift adds to the table hands
by bsaul 10y ago
Along with generics, they should probably also reconsider algebraic data types, such a enums with values. This is the best feature swift adds to the table hands on, and it seems to me as it's pretty orthogonal to the rest of the language ( although it carries a lot of other features with it, such as pattern matching).
They wrote that they considered it to be redundant with interface programming, but really i don't understand why. Interface is about behavior, not data. An int doesn't "behave" like one, it is one. And something that's either an int or an array of string, doesn't "behave" like anything you'd want to describe with an interface...
As an example, one should see how protobuf "one of" messages are dealt with in go : switch on arbitrary types followed by manual typecasting. That's just gross...
- xyzzy_plugh 10y agoI see where you are coming from, but that isn't the go way. Switch on arbitrary types, followed by typecasting? That's the go way. No surprises. Explicit instead if implicit behavior.
- dbaupp 10y agoMaybe you're visualising something different, but I don't see how ADT enums are implicit. Could you explain?
- coldtea 10y agoIf anything, ADT's would make it even more explicit. And pattern matching will make it impossible to miss cases.
- rwj 10y agoNot recommending either way, but if the language supported algebraic types with pattern matching, it would still be explicit.
- bsaul 10y agoIf you have a look at swift, it's also very explicit, and non-magical ( which is why i like it so much). The difference with go is that you can't make any type error when unwrapping the enum, because the compiler knows what are the different possibilities. I see no reason for go not to adopt it, in all honesty. it's nothing like generics, because it doesn't seem to add complexity to the rest of the language ( imho ).
- brandonbloom 10y ago> you can't make any type error when unwrapping the enum This is also true in Go with type switches: switch y := x.(type) case MyStruct: And with type assertions: y, ok := x.(MyInterface) In both cases, y is of the correct type. The ok is optional, if omitted and the assertion fails, you'll get a runtime panic.
- dbaupp 10y agoy is of the type you asked for, yes, but that isn't necessarily the type that should be there if the programmer makes a mistake, or if the underlying code changes. With an enum, the types are written in one place, and the compiler connects the dots, giving compile-time errors about mistakes/mismatched expectations. This similar to how one can call `foo(x)` and the return value's type is known, no need to first cast the function to the type the programmer thinks it should be. The casting pushes type errors to execution-time rather than compile-time.
- brandonbloom 10y ago> The casting pushes type errors to execution-time rather than compile-time Which is exactly what pattern matching does, which is what I was responding about.
- bsaul 10y agoOk, i'm starting to understand what we're disagreeing on. You're talking about pattern matching with dynamic expressions ( don't know the official name for that), such as what you find in erlang, i'm talking very basic enum unwrapping based on types, which is compile time checked. Swift also adds some kind of dynamic expression to patterns, which may break exhaustiv checking, but in the case of go, just a simple type check would be a great start. Edit ( since i can't reply) : just look at https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html https://developer.apple.com/library/content/documentation/Sw... with something like the barcode example. It's all compile time checked.
- stewbrew 10y agoI don't understand the downvotes since that's about the official stance of the go team -- see the FAQ (cited somewhere else). Agree with it or not.
- brandonbloom 10y agoIt'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.
- i_don_t_know 10y agoI think they thought of the standard OOP solution to algebraic data types. Rather than pattern match on the value and then do the work, you dispatch on the value through an interface and do the work. I don't know go so here's some pseudo code: type Foo = { x : int } type Bar = { y : string } interface DoStuff { doStuff() -> void } function Foo.doStuff() { ...do stuff with Foo.x } function Bar.doStuff() { ... do stuff with Bar.y } function main() { value := getSomethingThatImplementsDoStuff() value.doStuff() } Compare this to ML: type DoStuff = Foo of int | Bar of string let main = let value = getSomethingThatReturnsDoStuff () match value with | Foo of x -> ...do stuff with x | Bar of y -> ...do stuff with y
- brandonbloom 10y agoIndent 4 spaces for code. Here's another way to do this in Go: func f(obj interface{}) { switch obj := obj.(type) { case Foo: // Do stuff with obj.x case Bar: // Do stuff with obj.y default: return fmt.Errorf("obj is of unexpected type: %T", obj) } }
- i_don_t_know 10y agoThanks for the formatting help!
- weberc2 10y agoUnfortunately, this makes a runtime error from something that could be caught at compile time.
- adrusi 10y agoIt's possible to make closed interfaces in Go (give it an uncapitalized method), so there could be a compile-time warning when you don't handle all possible cases in a type switch on a closed interface.
- stewbrew 10y agoMight be relevant: https://golang.org/doc/faq#variant_types https://golang.org/doc/faq#variant_types Sometimes one wishes people from the go time would have spent some time hacking ML-derived languages.