Live data from Hacker News

Compile-time safety for enumerations in Go

vladimir.varank.in

71–80 of 116 posts

Re: Compile-time safety for enumerations in Go

#71
post #68

Earlier quoted context omitted.

It’s actually not and is one of those Java isms that have made its way into generalized software engineering. If I write a function that takes Foo as an argument. I have a Foo implementation exposed elsewhere in my program. It is absolutely expected that I mean MY Foo, not your Foo. If you pass me your Foo, you will get unexpected results that are not a bug, not a side effect, not my responsibility. It’s yours, the c…

> it’s is not my responsibility to validate all permutations of unknown types to ensure you’re passing me mine. Of course it is! If you provide an API that takes a Color, and you define valid Colors as exclusively Red, Green, and Blue, then you are absolutely responsible for validating input Color values and rejecting anything which is not Red, Green, or Blue. If you delegate that responsibility to the caller, then y…

By the same token, Rust has no type safety because std::mem::transmute exists. In practice, it's not a problem because transmute is not a valid way to construct or interact with values (outside of a few special cases). By calling transmute you're explicitly opting out of all safety and abstraction, and entering here-be-dragons territory.

(That said, the Go syntax looks far less "scary" than `unsafe { transmute::("orange") }`, which I'd definitely call a design issue.)

Re: Compile-time safety for enumerations in Go

#73
post #68

Earlier quoted context omitted.

> it’s is not my responsibility to validate all permutations of unknown types to ensure you’re passing me mine. Of course it is! If you provide an API that takes a Color, and you define valid Colors as exclusively Red, Green, and Blue, then you are absolutely responsible for validating input Color values and rejecting anything which is not Red, Green, or Blue. If you delegate that responsibility to the caller, then y…

By the same token, Rust has no type safety because std::mem::transmute exists. In practice, it's not a problem because transmute is not a valid way to construct or interact with values (outside of a few special cases). By calling transmute you're explicitly opting out of all safety and abstraction, and entering here-be-dragons territory. (That said, the Go syntax looks far less "scary" than `unsafe { transmute:: ("or…

The code in my example is an entirely valid way to interact with values, and doesn't represent an "opting out" of safety or abstraction.

Re: Compile-time safety for enumerations in Go

#74
post #32

This is not elegant. It also has overhead. Stick with the simple `const Red Color = "red"`, you're not gaining a lot by doing all these strange type contortions in Go. Seriously consider what you are protecting against, and whether it's an imagined bogeyman. "Oh but someone may try to cast arbitrary values to my package type" Okay but who is that going to hurt? You or them? Will they get hit with errors early on in t…

It's stuff that just creeps in. Enum values can come from outside (json, anything non-literal). Now you have to do validation, because the bad values parse and for sure exist at some stage in your program. It's not a bogeyman.

Accidental zero value enums are a constant plague as far as I've seen. Almost any defense is worth it.

Re: Compile-time safety for enumerations in Go

#75
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…

I like how you start the article by deriding this idea of using sum types dogmatically because “FP is Better” (the dogmatic capital-case “Better”, even) and then close the article with a therapeutic angle about making do with what you are given.

> I don't really care if you do or do not stay angry at the language you're forced to work in for your job. What I do want for you is to be able to make the best of the situation you're in and not be unhappy.

Because maybe enumerations in Go are subpar compared to other languages (FP or not).

Re: Compile-time safety for enumerations in Go

#76
post #8

I sometimes do this but idk if I would consider it 'elegant' The other 'gotcha' is that in switch statements the compiler can't tell whether you enumerated on all your cases as there is no true enum type so it's not uncommon to have a catch all default case that either returns an error or panics and hope you can catch it during tests if you missed a case. I just wish go had proper sum types.

>I just wish go had proper sum types.

It's by far my favorite feature of Swift.

Enums + Payloads + switches are incredibly simple yet so effective. You can make a simple state object, isolate it with an actor, then stream it anywhere with Combine (or now Observability). You'll get full compiler safety too, forcing any new state to be handled everywhere.

You can even stack that with _generic_ payloads that conform to protocols, and if you're feel brave you can make those payloads equable completions for _typed returns_.

for example (I hate formatting on here, but I'll give it a shot)

// A user state enum that conforms to loggable.

// The state is generic, any object T that conforms to "Partner" protocol can be a partner

enum UserState: Loggable {

   // Your states
   case loggedOut(error: String?)
   case loggingIn(email: Email)   // Email type is a string that's been validated
   case loggedIn(authToken: String, user: User, partner: T)
   case loggingOut(exampleBlock: (String) -> Bool) // You can embed a callback here
   

   // Utility Functions

   /// Is the user logged in?
   func isLoggedIn() -> Bool {
      switch self { 
         case .loggedOut, .loggingIn: return false
         case .loggedIn: return true
      }
   } 

   /// Loggable conformance
   func logText() -> String {
    // switch that converts state into something that's safe for your logging service
   }
}

In the above, any subscriber that gets a UserState object can switch on it, and for example if you're logged in you 100% get the auth token, user, etc. It's impossible to be logged in without those in-context.

It might look something like this in your UI:

/// Called by a subscriber that's hooked in to the state stream

func onUserStateChanged(newState: UserState) {

   switch newState {

      case let .loggedOut:
      // Impossible? Error UI

      case let .loggingIn, .loggingOut:
      // loading UI

      case let .loggedIn(authToken: _, user: user, parner: partner):
      // set some text "hello \(user.userName)"
      // display an image: \(MyRemoteLoader.url(url: partner.primaryLogoURL))
      // Button to website: Button(url: partner.homePageURL)
   }
}

add a new state later? The compiler will error in the 500 places in your codebase you didn't handle the new case with one build command.

Re: Compile-time safety for enumerations in Go

#77

Earlier quoted context omitted.

Welcome to Go. “Wait, Go doesnt have X?” I’m convinced its an incomplete language masquerading as a simple one.

Well, that's an opinion. Depending on how you define "complete," any language with fewer features than Scala might be considered incomplete Unless you mean: is it enough of a language to be useful for its intended usages? In which case my decade of paychecks would like to let you know that it is, in fact, sufficiently complete for many business needs.

This is either skirting close to the Nirvana Fallacy or using unwarranted hyperbole.

Enumerations aren't an advanced language feature at all and Go was released in 2010.

Re: Compile-time safety for enumerations in Go

#78
Another approach could be:

  package color

  type Color struct {
    val string
  }

  func (c Color) String() string {
    return c.val
  }

  var (
    Red   = Color{val: "red"}
    Green = Color{val: "green"}
    Blue  = Color{val: "blue"}
  )
Since `val` is not exported, external packages cannot create arbitrary `Color`.

Re: Compile-time safety for enumerations in Go

#79
post #68

Earlier quoted context omitted.

It’s actually not and is one of those Java isms that have made its way into generalized software engineering. If I write a function that takes Foo as an argument. I have a Foo implementation exposed elsewhere in my program. It is absolutely expected that I mean MY Foo, not your Foo. If you pass me your Foo, you will get unexpected results that are not a bug, not a side effect, not my responsibility. It’s yours, the c…

> it’s is not my responsibility to validate all permutations of unknown types to ensure you’re passing me mine. Of course it is! If you provide an API that takes a Color, and you define valid Colors as exclusively Red, Green, and Blue, then you are absolutely responsible for validating input Color values and rejecting anything which is not Red, Green, or Blue. If you delegate that responsibility to the caller, then y…

‘type Color string’ is just a type alias for string.

Likewise GLint is just a type alias for int. There are only value types (str, int, float, etc), everything else is a construct. The only true types are those value types (and pointers to them). If you call a type a Color and I call a type a Color, you are using my lib to build a program (not me using yours), you must adhere to my contract of what a Color type is to my API. Period. You can not call a function with an unknown type and expect it to behave properly.

If it panics, it’s your fault.

Re: Compile-time safety for enumerations in Go

#80
post #68

Earlier quoted context omitted.

> it’s is not my responsibility to validate all permutations of unknown types to ensure you’re passing me mine. Of course it is! If you provide an API that takes a Color, and you define valid Colors as exclusively Red, Green, and Blue, then you are absolutely responsible for validating input Color values and rejecting anything which is not Red, Green, or Blue. If you delegate that responsibility to the caller, then y…

‘type Color string’ is just a type alias for string. Likewise GLint is just a type alias for int. There are only value types (str, int, float, etc), everything else is a construct. The only true types are those value types (and pointers to them). If you call a type a Color and I call a type a Color, you are using my lib to build a program (not me using yours), you must adhere to my contract of what a Color type is to…

The point here is that, in Go, "what a Color type is" isn't something that can be enforced by the compiler. And more specifically, Go doesn't allow you to define reliable enums.
Post reply on HN