6 ms·
I think that switching on types in Go is gererally a total anti-pattern. In case you need to handle errors, in the end the type doesn't matter at all for you.
by cube2222 10y ago
I think that switching on types in Go is gererally a total anti-pattern.
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!
- pbnjay 10y agoI made a similar comment on the reddit post. This seems like something handled better by interfaces. 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.
- burntsushi 10y ago> I think that switching on types in Go is gererally a total anti-pattern. It's not. It's a standard way of implementing variant types. See https://golang.org/doc/faq#variant_types https://golang.org/doc/faq#variant_types and https://golang.org/src/go/ast/walk.go?s=1311:1342#L41 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!)
- cube2222 10y agoActually, after reading the walk.go code I still wonder what the best way to do it would be. (Not saying it's bad now, though I'll still remain my devils advocate) 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.
- pbnjay 10y agoGreat minds think alike (IMHO) https://www.reddit.com/r/golang/comments/60dik1/a_utility_for_running_exhaustiveness_checks_on/df669jk/ https://www.reddit.com/r/golang/comments/60dik1/a_utility_fo...
- cube2222 10y agoAnyways, my conclusion is. In most cases it IS an anti-pattern. Only if the, let's say "observing" function (the one that decides on the type), is really just observing the behavior nad trying not to interwind with the mechanics of the actual structures is it justified I think. In that case it provides greater cleanliness of your code. 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.
- andrewstuart2 10y ago> The function returns an interface because all that should matter to you is that interface with its common behavior. 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.
- russell_h 10y agoYou should tell that to the Go team so they can stop doing it everywhere. 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.
- jerf 10y ago"I think that switching on types in Go is gererally a total anti-pattern." 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.
- jerf 10y agoSeparately, the way Go is a very, very simple language that is picking up a lot of useful tooling makes me wonder about the possibility of designing a language that even more deliberately reifies that. What if you had something like a Lisp, but instead of the macros staying in the code you had a culture of resolving the macros into the simple language and publishing the end result instead of the macros? Bearing in the very purpose of such a structure would be to forcibly ensure that the core language remains simple and the shared libraries stay simple, the core idea here is to use a second-order effect to constrain the power of macros and macro-like things, so I'm not "surprised" by the way this makes macros or syntax checks less powerful, nor am I surprised that writing such a tool requires a higher "activation energy" than simply plopping a macro down. It is well known that macros don't stay simple and often grow quite complicated; some people attribute this and the fact that macros become their own language and thus no two Lisp libraries are necessarily written in the "same language" to why Lisp hasn't managed to conquer the world. 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. [1]: https://github.com/alecthomas/gometalinter https://github.com/alecthomas/gometalinter