Live data from Hacker News

Compile-time safety for enumerations in Go

vladimir.varank.in

11–20 of 116 posts

Re: Compile-time safety for enumerations in Go

#11
post #7
post #4

Earlier quoted context omitted.

I go back and forth on this. On one hand, I _love_ Rust/ML style sum types. They're such a fantastic feature. On the other hand, I wonder how much use they'd get in a language like Go. If it's introduced, would it radically change the way some problems get solved? Is it that different than an traditional enum (which Go also skirts around)? Pattern matching and exhaustive checks are massive benefits of them though. I…

I think the answer to your "back and forth" is probably laid out in: https://jerf.org/iri/post/2960/ Sum types are useful, even very useful in the right place, but I do think there's a lot of people who use them a couple of times, probably in one of those "right places" and then mistakenly label them in their mind as "better". Just, universally, Platonically "better". They aren't in fact "better"; they're a tool. Som…

This article seems almost entirely concerned with code organization, but in my case I basically never do functional programming and am only concerned about data modelling and data structures. In an adjacent, comment I do acknowledge one point (probably not all software really needs sum types) but I still feel as though languages without sum types are missing an important data and state modelling tool. Trying to take something like a state machine and jam it into interfaces or polymorphism kind of sucks; you lose exhaustiveness checking obviously, but also it enforces a lot of constraints about control flow and data. In Go parlance, if each of my states has a method with its receiver set to the state type, then I can only access that data, so for example I'd need to pass something in or out to handle state transitions. Not ideal IMO.

I feel this pain basically any time I hand-write lexical scanners/parsers in Go or even C++.

Re: Compile-time safety for enumerations in Go

#12
post #6
post #2

I dunno if I'd consider having a dummy method on an interface as "elegant", but it does work. A trade-off of keeping the language very simple, for sure. What I really wish Go had was sum types, Rust style. That'd cover enumerations and more.

If you are satisfied with a dummy method on an interface, you can continue by adding more types to that interface. Color is not a great example, so: type Vehicle interface { isVehicle() } type Car struct {} func (c Car) isVehicle() {} type Van struct {} func (v Van) isVehicle() {} func VehicleType(vehicle Vehicle) { switch v := vehicle.(type) { case Car: fmt.Println("car") case Van: fmt.Println("van") default: fmt.Pr…

Yeah, I've done that before. It's not really elegant in my opinion, but OTOH, it's basically the best you can do. Oh well.

Re: Compile-time safety for enumerations in Go

#13

The main downside to this approach is that you'll still have to deal with the zero value, which for interfaces is going to be nil.

While I agree nils are a problem with this: as long as they have access to the type at all, they can create zero values. E.g.

  var c color.Color
That's a valid color whether it's a nil interface value or an empty typed string.

You can return a private type to prevent this, but that also means they can't refer to it as an argument or return value anywhere outside the implementing package, which is a rather severe limit in many cases.

Sometimes* I really miss constructors.

*: all the time

Re: Compile-time safety for enumerations in Go

#14
post #11
post #7

Earlier quoted context omitted.

I think the answer to your "back and forth" is probably laid out in: https://jerf.org/iri/post/2960/ Sum types are useful, even very useful in the right place, but I do think there's a lot of people who use them a couple of times, probably in one of those "right places" and then mistakenly label them in their mind as "better". Just, universally, Platonically "better". They aren't in fact "better"; they're a tool. Som…

This article seems almost entirely concerned with code organization, but in my case I basically never do functional programming and am only concerned about data modelling and data structures. In an adjacent, comment I do acknowledge one point (probably not all software really needs sum types) but I still feel as though languages without sum types are missing an important data and state modelling tool. Trying to take…

> I still feel as though languages without sum types are missing an important data and state modelling tool.

That's because they are. jerf subtly shifts the point to one they can criticise, but it does not change the basic fact that sum types are a critical tool which is just... missing.

There's probably no tool which can't be misused, even the humble boolean, that's not an issue with the tool, and pointing that out is at best irrelevant. It does not change the fact of the matter: you're missing a critical axis of composition. It's like saying screwdrivers are useful but you can misuse them to hammer nails as if that justified trying to screw with a hammer.

Re: Compile-time safety for enumerations in Go

#15
post #11
post #7

Earlier quoted context omitted.

I think the answer to your "back and forth" is probably laid out in: https://jerf.org/iri/post/2960/ Sum types are useful, even very useful in the right place, but I do think there's a lot of people who use them a couple of times, probably in one of those "right places" and then mistakenly label them in their mind as "better". Just, universally, Platonically "better". They aren't in fact "better"; they're a tool. Som…

This article seems almost entirely concerned with code organization, but in my case I basically never do functional programming and am only concerned about data modelling and data structures. In an adjacent, comment I do acknowledge one point (probably not all software really needs sum types) but I still feel as though languages without sum types are missing an important data and state modelling tool. Trying to take…

"In Go parlance, if each of my states has a method with its receiver set to the state type, then I can only access that data, so for example I'd need to pass something in or out to handle state transitions."

I am not clear what you mean by that, exactly, but generally I model a state machine as a type that composes a state:

    type State interface {
         isState()
    }

    // a whole bunch of state types here

    type StateMachine struct {
         State // exported or not exported, depending on local needs
         // additional data
    }

    func (sm *StateMachine) Event1(args...) error {
         // can call current state and change it here
    }
You can add an "Execute" method on to the State and have it return an entire state if that's how you want to do it. The state machine can pass in any cross-state data. There's a number of options. You aren't obligated to do exactly, only, and precisely what you'd do in another language, and program X in Y. For some reason, that's common wisdom in the programming world... except for functional programming. I say "don't program X in Y" is simply true and there is no carveout for functional programming in non-FP languages any more than there is the other way around for OO in FP languages.

Re: Compile-time safety for enumerations in Go

#16
post #11

Earlier quoted context omitted.

This article seems almost entirely concerned with code organization, but in my case I basically never do functional programming and am only concerned about data modelling and data structures. In an adjacent, comment I do acknowledge one point (probably not all software really needs sum types) but I still feel as though languages without sum types are missing an important data and state modelling tool. Trying to take…

> I still feel as though languages without sum types are missing an important data and state modelling tool. That's because they are. jerf subtly shifts the point to one they can criticise, but it does not change the basic fact that sum types are a critical tool which is just... missing. There's probably no tool which can't be misused, even the humble boolean, that's not an issue with the tool, and pointing that out…

Where I'd disagree is that they are a tool, not a critical tool, and they aren't missing from Go, they are simply not the preferred choice. You can cover 75% of the use cases with this approach. It is not 100% of the use cases. But there isn't a language where you can cover 100% of the use cases with 100% effectiveness, which is why we don't have and never will have The One True Language.

Moreover, Go seems to attract this sort of criticism as if Go is Uniquely Broken and it's nirvana in all the other languages... but I've used enough of them to know better. Sum types are great, until you hit the branch of the expression problem where you really need the other side, and if you're in a language that favors them, you're going to get the same 75% experience, just mirror imaged.

Re: Compile-time safety for enumerations in Go

#17

The main downside to this approach is that you'll still have to deal with the zero value, which for interfaces is going to be nil.

No matter how Go solves the issue (assuming it ever does), that will be part of it: unless it goes through a revolution and strips out and forgets about ubiquitous default values (which I don't think it will, C# has barely just dipped its toes into that pool) any sort of sum-type-like construct will need a default value. And I'm not sure `nil` (a clearly corrupted / missing value) is any worse than picking an actual valid value the way non-pointer types do.

Re: Compile-time safety for enumerations in Go

#18
In a more dynamic environment where you could manage values in a controlled manner, how could you validate values ?

One idea is to make a DB table dedicated to enumerations and then use foreign key constraints. Define a unique namespace for each set of enumerated values, and catch foreign key constraint failures on DB writes.

Re: Compile-time safety for enumerations in Go

#20
post #19

Unfortunately not. func main() { c := color.Red cptr := (*string)(&c) *cptr = "orange" PrintColor(c) // successfully compiles, and prints "orange" }

I don't think the solution is trying to cover this. If a user does type conversion then usually they know what they are doing. It's like going out of the way. The solution is to just hide a type behind an interface to be explicit about it and to avoid the error that would have gone unnoticed otherwise.
Post reply on HN