6 ms·
Show HN: A utility for running exhaustiveness checks on “sum types” in Go
- notheguyouthink 10y agoCool! I didn't honestly expect to see more Go code from BurntSushi. Thought he was a Rust convert :) (not that someone can't be both, of course)
- burntsushi 10y ago> (not that someone can't be both, of course) Indeed! I've been writing Go code daily for years. My open source life is just a bit more consumed by Rust these days. :-)
- infogulch 10y agoFrom the readme: > The go-sumtype command recognizes this pattern, but it needs a small amount of help to recognize which interfaces should be treated as sum types Why is this? Have you tried detecting sum types automatically? Maybe some heuristics like: package-level declaration of the interface, has an unexported method, has more than one type that implements it, maybe also the interface itself is exported.
- burntsushi 10y agoIt seems unwise to do exhaustiveness checks automatically on an interface that "might" be a sum type. The declaration is a single line and has the benefit of being completely unambiguous.
- rkangel 10y ago> (not that someone can't be both, of course) The Go vs Rust narrative seems to have thankfully mostly gone away (although I'm sure it will never die out completely). The areas that they're targeted and the things that they are best for have now become clearer to the community at large, and they don't necessarily overlap that much. That said, I don't use either of them at work, so I'd interested to hear a counter-argument.
- tylerhou 10y agoWhat are the areas that they target? I'm not too familiar with the communities surrounding either language.
- rkangel 10y agoGo tends to be more Web related - back end services communicating over networks, etc. It's taken over things that in the past would often have been done in things like Python but as scales increase performance becomes more important. Google's backend web services is teh classic example of usage. Rust is more in the C/C++ role - 'systems programming'. This leans more towards things like command line tools, system daemons, OS kernels, embedded work on small processors etc. Doing performance intensive tasks in Firefox is probably Rust's most well known application. That's not say that there are overlap areas where they can't both work well, but those aren't so much the areas of emphasis.
- geodel 10y ago> The Go vs Rust narrative seems to have thankfully mostly gone away I am not aware of that. Has something changed recently? Though IMO Rust's strength could lead interesting usage in Data processing/analytics platform like Hadoop/Spark etc as opposed to Go like web services.
- kibwen 10y ago"Go vs. Rust" was initially only a thing because 1) Google initially positioned Go as a systems programming language, 2) early prototypes of Rust were very Go-like (with both a garbage collector and segmented stacks), and 3) web developers who were already used to seeing Chrome vs. Firefox found it an easy conceptual extension. But Go hasn't been marketed as a systems language for a long time and Rust removed all its Go-like features years ago; the "Go vs. Rust" that we still see is just residual fallout.
- notheguyouthink 10y agoIt's funny, as a Go and Rust fan, they're still in the same space to me conceptually. I'm sticking with Go at the moment because moving my shop to Rust is quite difficult (for varying reasons), and because Go offers a nice compromise on ease and power. However i miss no-null types so bad. All that compile-time information is amazing. I really hope Go adopts some features from Rust.
- gothrowaway 10y agoGolang irritates people for various reasons. For some reason I've begun to feel the language is was partly designed to put prima donnas who push theoretical languages in blogs all day rather than ship in their place. These Haskell and Scala people talk talk talk all about correctness and syntax tricks. Meanwhile golang is nimble and just winning every race. I wish they'd learn from it rather than get so defensive. Also, it's just a confirmation people get too attached to complicated features in languages as a crutch. YAGNI Programming can be straight-forward and clear. Without it spiraling into a contest of clever tricks. You look clever when you get product out the door. I've been programming 10 years professionally, and 15 years as a hobby. I've yet to hear what a sum type is until yesterday. I got nothing wrong with people dabbling and hacking on the weekend, but sometimes it irks me when people split hairs over being "correct" and never actually get stuff done. I think a lot of the "syntax tricks" is a mechanism used by people to feel superior. It's like, "hey if I can't actually build something useful, at least I can blog about how to write a monad transformer". I'm not trying to be offensive, I'm just failing to see the value in this and trying to make sense of what I'm seeing. Where was the business issue that could only be solved through sum types in the language syntax? Enlighten me.
- gothrowaway2 10y agoSpeaking from my own experience, I don't have the opportunity to use them, rather I have to use the language with less features in the work place. The articles are meant to persuade people into using languages with more features. I feel at home with FP, HKT, and ADTs, actually I'm out of my comfort zone without them.
- burntsushi 10y agoIn our code, we have a closed interface type with many variants, not unlike an AST. We frequently add new variants to that interface. It is nice to have a tool tell us which parts of the code need updating to account for the new variant. This tool does that. It trivially reduces bugs and saves development time. If you think people like me "don't get stuff done," then please, take a stroll through my Github. ;-)
- cube2222 10y agoI 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!)
- grabcocque 10y agoIsn't trying to hack a language to have the type system you wish it had, rather than simply using the language you wanted in the first place, something of a futile exercise? If you want a language with ADTs and exhaustive patterns out there, there's plenty to choose from. Using a language with a weak type system and then trying to wish that fact away seems pointless.
- meddlepal 10y agoNot everybody gets to choose the language they want to use at work unfortunately.
- pjmlp 10y agoWe get to choose, when there is freedom to choose to whom we work for. Of course, changing jobs isn't always an option.
- michaelmior 10y agoAnd someone can still love their job even if the language in common use isn't their personal preference.
- burntsushi 10y agoThat's the case for me at least. I was even involved in choosing Go at our company a few years back. A language doesn't need to be perfect in order to enjoy using it. :-)
- bbcbasic 10y agoThe Haskell tax is high in my city as is the cost of living.
- Zikes 10y agoIf you'd like to point us to a language exactly like Go in every way, except that it does have variant types built in, then by all means. Excepting that, we all have to accept that languages have their pros and cons, and we are each responsible for weighing those pros and cons against our personal preferences and values.
- wyc 10y agoLanguage-level sum types are something that I miss a lot when I'm writing Go. I find myself using this error-prone pattern whenever I'm trying to represent state: type PersonType string const ( EmployeePersonType PersonType = "Employee" ManagerPersonType PersonType = "Manager" ClientPersonType PersonType = "Client" ) I'm not the only one[1]. Another option is to use iota. type PersonType int const ( EmployeePersonType PersonType = iota ManagerPersonType ClientPersonType ) You'll then use generators such as stringer or jsonenums[2] to take care of translating your internal representations to something more system-level and printable. This gets very messy as now we need to write un/marshallers and code generators for SQL databases, NoSQL databases, JSON endpoints, etc. I'm likely to just keep doing what I'm doing. Although my described approaches aren't great, I'm glad to see people thinking about how to improve it with tooling. For reference, this is the same thing in Haskell: data Person = Employee | Manager | Client I really prefer this format because the set of possible Person type options is explicitly defined and easy to exhaustively check. You'll get no type coercion from arbitrary strings like "Daughter". And you can also extra data types using type constructors: data ApptTime = NotScheduled | WalkIn | At UTCTime At least by attempting to use sum types in Go, you get some compiler checks, which are a lot better than none. Has anyone figured out a better way? [1] https://github.com/kubernetes/kubernetes/blob/master/pkg/api/types.go#L2860 https://github.com/kubernetes/kubernetes/blob/master/pkg/api... [2] https://github.com/campoy/jsonenums https://github.com/campoy/jsonenums
- aston 10y agoYou may want to take a look at https://github.com/alvaroloes/enumer https://github.com/alvaroloes/enumer which can generate functions around your enum to give/accept string representations, plus JSON and SQL scanning.
- Animats 10y agoThis is an old argument. If a language allows a switch statement where no case being executed is an option, there's an opportunity for bugs. If a language doesn't allow that, some people bitch about it being too pedantic. A strong argument for insisting on covering all the cases (possibly with an explicit default) is that someday, someone may add a new enum value, type, or whatever, and code that fans out on that value may invisibly become invalid.
- lobster_johnson 10y agoThis looks very nice. I've been working with sum types (as far as they can be called that) in Go a lot recently, and it's frustrating how bad it is as algebraic data types in general. Aside from the lack of exhaustiveness checks, one particularly annoying area is writing code for enumeration and transformation. I needed one set of types to have this: type Node { Walk(WalkFunc) TransformBottomUp(TransformFunc) (Node, error) TransformTopDown(TransformFunc) (Node, error) } I implemented it as an interface precisely because of the lack of exhaustiveness checks, even though it feels wrong to attach code to my pure-data types. But the code to do it is pure boilerplate. I considered doing something like having GetChildren() and SetChildren() and writing some generic transformer code, but they you have to deal with slices, which means a lot of allocation. I also considered having interfaces like Nonary, Unary, Binary, etc. that encapsulated the number of children, but that's terrible, too. I might go with simple switch statements instead, and use this utility.