It's not. It's a standard way of implementing variant types. See https://golang.org/doc/faq#variant_types and https://golang.org/src/go/ast/walk.go?s=1311:1342#L41
True, it's not used often. Sum types aren't a great fit for Go (as the FAQ says), but sometimes they are exactly what you want.
> Otherwise you're just fighting the language, which doesn't make any sense, as you can just switch to Rust then.
I find these types of comments really dismissive and unhelpful. A single comment declaration and one Sunday's worth of hacking (thanks to the incredible tooling provided by Go) makes my experience using Go better. Why do I need to switch to Rust? It would take several months and significant social capital to do. (And I'm saying this as a member of the Rust library team!)
As I skimmed through the code, in my opinion you could easily have a Walk(v Visitor) method on each of those node types.
This way you'd have no need for casting, and in the Walk method you would be able to use type-specific functionality without the need to cast. The only disadvantage I see is that the Walking functionality would be spread out among the code in different places, but this isn't a problem with modern editors.
I'll note this down somewhere and if I have time I'll test it out sometime.
https://www.reddit.com/r/golang/comments/60dik1/a_utility_fo...
In this case though, as this is the ast package, and walking is a standard functionality of an ast, I think this could just as well be implemented with interfaces.
Edit: I realize this came off as snarky, sorry.
I'm somewhat serious though. In my current Go installation I count 519 type switches in the standard library.
Some exist for perfectly valid factoring reasons. For example, the x509 package switches on the type of the public key it receives in order look up some x509-specific values for encoding. It would be a layering violation for the public keys themselves to have x509-specific encoding values attached, and Go doesn't permit the x509 package to declare new methods on types defined in another package. (They _could_ define a new interface which extends PublicKey, and declare a new local wrapper type for each supported public key type, embedding the external ones then attaching methods as needed - but this is sufficiently clunky that I've never seen it done in Go core).
But there are plenty of other cases, especially in the crypto packages, where the type switching seems gratuitous. For example the crypto.PublicKey interface is actually empty - 100% of public key functionality is implemented through some kind of type assertion or switching. Maybe there really is a good reason for this, but it's not obvious to me.
It's not.
A thing to bear in mind is that this tool is checking interface types that are strictly internal to a package. They contain a package-private implementation method. It is a very common pattern for me to have a package implement a server internally that runs in a goroutine or set of goroutines that is internally communicated with along a "chan command", and does a type switch within its main loop to dispatch the commands. It is useful to have completeness checking on this sort of thing. It would also make sense to have AST nodes, header types, or any number of other internal usages handled this way. The addition of an external tool to do completeness checking goes 80% of the way towards resolving people's complaints with this pattern, as long as they don't mind an external tool. (Which I acknowledge is a matter of strong opinion.)
Your criticism seems to focus on how objects present themselves externally. From that point of view, it is correct and I generally agree with it... the only issue is that it's not relevant here, because we're talking strictly about internal objects.
I'm noodling around with ideas here here, not promoting Go as this language; if it were designed for that there's probably some things it would do differently. I just find it sort of intriguing that if you don't mind using tools, a lot of the common criticisms of Go can be largely mitigated without necessarily paying the price of putting the complexity in the language itself; even if that doesn't personally appeal to you it's interesting to consider it as a deliberate design paradigm. Is this a good idea? It's almost like having a modular language where you only use the complex stuff where you really get an advantage from it but don't pay for that stuff where you don't. You couldn't use this to bodge in things like the Rust borrow checker that really needs the whole program to use it to obtain the benefits, and a lot of what Haskell does with the typechecker would be infeasible, but it's not hard to see things like "sum type completeness checking" and "generics via code templating" and even things as large as "optional typing" being feasible this way, without the core language authors having to arbitrate all these things, and without the whole community necessarily having to adopt these things to see advantages. Compare with, for instance, the rather large mess Python has become over the years by absorbing feature after feature into the core language. See also the gometalinter [1], in which the community aggregates a collection of smaller tools all written fully independently, with little-to-no coordination needed, all standalone, and then tied into what is, frankly, not a half bad language linter, especially if you play with the config a bit.
That's basically called code generation. Visual Studio had it more than twenty years ago in the form of the "MFC App Wizard".
Problem is, what if the macro-generated code had a bug, and it was expanded in 77 different places (not to mention in subtly different ways)? We want to correct the macro in one place and re-expand it.
Writing code-generating code, but then just retaining the results of the generation, is a serious "anti pattern".
I wasn't clear. In the Go community, you don't retain just the results of the generation, you retain the source as well, you just publish the results.
Furthermore, when someone says they're musing about with ideas, "That's an antipattern" isn't a helpful contribution. Why is it an antipattern? Go deep, not just the superficial reasons. The reasons matter, because it may turn out the reasons don't apply, or that it was an antipattern because of specific conditions in the local landscape that may either not apply or could be made to not apply (e.g., functional programming is an antipattern in Go and the only way to work in Haskell, declarations of "antipattern" are almost always only locally appropriate).
Further, what prompted my musing in the first place is that you can convert "That's an antipattern" into the more scientific theory-type expansion of "If you do that, you will produce unmaintainable code." However, in the Go community, to some extent, we see people doing "that" but not ending up with unmaintainable code. (Please do not simply assert that it must be unmaintainable because that's what this antipattern produces, as that is begging the question. I see code that I would not describe as "unmaintainable" coming out of this process. Such as in the very thing that prompted this discussion; there is nothing about an external totality checker that makes the code unmaintainable.) Reality trumps theory. Since the theory that code generation is often a bad idea is indeed fairly well established, that tends to imply that rather than the theory being "wrong" it is incomplete. What if the community has oversimplified its view of code generation? What if there is a particular combination of difficulty and benefit that might allow us to harness it, as I said, to more truly modularize a language in a way that the entire language is not always pressing down on a programmer in every line?
My theories aren't matching reality, so I find myself interested in the why of that mismatch.
https://github.com/kisielk/errcheck
I agree that having things like errcheck and go-sumtype all wired up using gometalinter is pretty novel and only partially gross.
I disagree. If a function wants to limit what you care about then it should return a concrete type. Interfaces exist to increase flexibility, and that includes being able to assert an implementation to gain safe access to the further structured information that underlying object contains.
> Otherwise you're just fighting the language, which doesn't make any sense, as you can just switch to Rust then.
No, using a language feature is not fighting the language. On the contrary, enforcing a pattern of increasing API surface area for functionally checking properties (i.e. the Dave Cheney error method) that would be much more cleanly accessed as structured data is fighting the language and in my opinion borders on antipattern.
In my experience, whenever I end up writing a large type switch on an interface I take it as a code smell. All those cases should be an interface method. Only in rare instances (base case serialization of builtin types for example) would I really think type switches make sense.
In case you need to handle errors, in the end the type doesn't matter at all for you. You are interested what the implications of the error are, so the library exposing the error should just expose functions like isTemporal(e error) bool. In case you need to inform the user about the exact error, no problem, the returned error has the Error() function which shows the message, no matter the type, use that.
In case you're not matching errors, then you shouldn't worry about the type. The function returns an interface because all that should matter to you is that interface with its common behavior.
Otherwise you're just fighting the language, which doesn't make any sense, as you can just switch to Rust then.
EDIT: Though I'm really happy that people make more tools for analyzing Go code. Good job!