[HN Gopher] Leveraging the Go Type System
       ___________________________________________________________________
        
       Leveraging the Go Type System
        
       Author : gopherguides
       Score  : 81 points
       Date   : 2021-02-09 15:51 UTC (7 hours ago)
        
 (HTM) web link (www.gopherguides.com)
 (TXT) w3m dump (www.gopherguides.com)
        
       | morelisp wrote:
       | IMO this article really does language learners a disservice by
       | stopping before at least mentioning stringer, which is even more
       | critical to cleaning this up than iota (which does get a
       | mention). It doesn't only generate most of the boilerplate code
       | but does so in a safer and more efficient manner.
       | 
       | On the other hand, the efficiency concern it presents is real,
       | and just repeating "sum types" is not, alone, enough to address
       | it. Many languages with sum types would still store, and even
       | more still serialize, a string.
        
       | VitalyAnkh wrote:
       | This article should be written with
       | Rust/Haskell/Scala/OCaml/TypeScript ... or any languages with an
       | insane type system. Anyway it can't be Go, a language without sum
       | type.
        
       | kace91 wrote:
       | Semi related question: what resources do you recommend for go's
       | state of the art regarding best practices, idiomatic project
       | structure, tooling and the like? Preferably books, but I'll take
       | anything.
       | 
       | I searched recently and most of what I saw were either in depth
       | books about the language itself, outdated blog posts, or non-
       | relevant books that just happened to use go to explore an
       | unrelated topic.
        
         | corylanou1 wrote:
         | Well, we obviously have paid courses that cover all this, but
         | if you want my personal opinion on package layout, Ben Johnson
         | really has a great article on it here:
         | https://www.gobeyond.dev/packages-as-layers/
        
           | benbjohnson wrote:
           | Thanks for the mention, Cory. Great article, btw!
           | 
           | To the parent post, there are several articles on Go
           | Beyond[1] that walk through development of an open-source,
           | real-world application called WTF Dial[2]. The source is
           | available on GitHub and you can ask Go application design
           | questions on the Discussions board[3].
           | 
           | [1] https://www.gobeyond.dev/
           | 
           | [2] https://wtfdial.com/
           | 
           | [3] https://github.com/benbjohnson/wtf/discussions
        
       | centimeter wrote:
       | This is a great argument to not use Go. This could all be
       | implemented way more efficiently, easily, and correctly using
       | ADTs (sum types).
        
       | nerpderp82 wrote:
       | I keep wanting to get into Go, and I pick it up on and off over
       | the years but just can't. It brings so much good, but it has
       | actually removed too much.
       | 
       | It needs sum types and pattern matching. Once you have used them,
       | not having them is a huge gray void. Const expr, interfaces, sum
       | types and pattern matching get you 85% of the way there.
        
       | marcus_holmes wrote:
       | The points about iota are interesting. I always define the first
       | constant as "unknown" when defining a set of iota-driven
       | constants. That way the zero value is "unknown" so if I create a
       | new struct with no initialiser it doesn't accidentally inherit a
       | value I didn't mean it to have, and instead gets the "unknown"
       | value.
       | 
       | It also doesn't touch on the other useful "system" funcs to
       | include on a type (Scan, Value, Marshal, etc)
        
         | jerluc wrote:
         | I feel like there are times when zero values can make things
         | awkward (e.g. boolean flags often need to be expressed in the
         | "negative" form because the zero value is false), but this is a
         | great idea for iota!
        
           | marcus_holmes wrote:
           | Totally agree. Time zero values are my bane. I've started
           | using sql.NullTime for all time values regardless, just so
           | I'm clear about what is what.
           | 
           | * yes I know time.IsZero() is a thing, and the semantics are
           | similar, but I want the compiler to warn me if I'm trying to
           | use a time without checking if it's actually initialised
           | first.
        
         | corylanou1 wrote:
         | Agreed. There are a couple of clever ways to handle iota, but
         | as my next article suggests, in general, I steer away from them
         | if you really just need constants.
         | 
         | I didn't go into any of the Scan/Value/Marshal etc as it was a
         | little beyond the scope of this article. I can certainly do a
         | follow up article on it though
         | 
         | Thanks for the feedback!
        
           | marcus_holmes wrote:
           | Yeah I read the article about iota. It was interesting, and
           | there's stuff there I agree with, but on the whole iota is
           | too useful not to use (imho).
        
       | ed25519FUUU wrote:
       | Fun read. Personally I would have stored the `string` and called
       | it a day without add to a stringer function. It's just easier,
       | and there's no way those 14 words are going to cause a noticeable
       | change in performance.
        
       | shadowgovt wrote:
       | I really like this example, because it highlights both the
       | strengths and weaknesses of Go in the same example.
       | 
       | On the one hand: Good language support for a specialized type and
       | a method on that type, without making the type a heavyweight
       | class (i.e. all that magic can happen at compile-time; Genre can
       | still be an int "under the hood").
       | 
       | On the other hand: wow, that's a lot of boilerplate to write just
       | to get the string representation of a token the compiler already
       | knows about. As other commenters have noted, `go generate` will
       | decrease the amount of code that needs to be hand-written, but it
       | still smells like something the language should handle without
       | explicit code.
        
       | cbdumas wrote:
       | I had heard that the Go type system was just annoying and too
       | simple to be useful, and this blog post has convinced me that
       | that is 100% true.
        
         | corylanou1 wrote:
         | It might be a little premature to judge an entire language on
         | an introduction article for beginners. This article isn't
         | intended to show every possible way Go can solve a problem, but
         | intended to show people new to Go different ways to think about
         | types. In a production scenario, it's unlikely I would use this
         | solution. This is really an "academic" exercise in types for
         | Go, not a practical application.
        
           | cbdumas wrote:
           | My reply was at least partially facetious as this wasn't my
           | first exposure to Go. If you like the simplicity of Go's type
           | system I can't argue with that, but I do think your article
           | demonstrates the way that simplicity at the language level
           | pushes complexity into the users code. Pick any modern
           | statically typed language and this whole post will boil down
           | to something like:                 data Genre = Adventure
           | | Comic                  | Crime                  | ...
           | deriving (Show)
        
       | denysvitali wrote:
       | I'm a Go fan and I love the language, but what I can't really
       | stand is the fact that enums do not really exist, and it's
       | representation (stringification, for example) is not implemented
       | by default.
       | 
       | So far I still haven't found a clean way to have proper enums,
       | and this article shows us that unfortunately neither the author
       | has :(
        
         | throwaway894345 wrote:
         | I don't mind that the compiler doesn't provide a default string
         | implementation by default. I'm happy to use string values in my
         | enums (in most cases const string vs int doesn't matter much
         | for performance) for convenience; what I _do_ wish for is full-
         | blown ADTs. I want to be able to express ` 'Hello' | 'World' |
         | 42 | struct{Name string, Age int} | []int` and know that the
         | compiler will enforce. You can sort of get there with Go
         | through various workarounds and with enough boilerplate (and
         | no, I'm not one to complain about error handling or for loop
         | boilerplate, but the boilerplate to workaround ADTs is much
         | more tedious and much less discernible.
        
           | mssundaram wrote:
           | Do you have any examples of how you would implement a
           | makeshift sum type like your example?
        
             | [deleted]
        
             | morelisp wrote:
             | X interface{} // int(3) | int(5) | []float64 | *Z
             | 
             | It sucks.
        
               | throwaway894345 wrote:
               | We can recoup a fair bit more type safety than this if
               | we're interested in going through the effort:
               | https://play.golang.org/p/4Fd7aKRtmvz. See my sibling
               | comment for more details.
        
             | throwaway894345 wrote:
             | Here's one such workaround:
             | https://play.golang.org/p/4Fd7aKRtmvz
             | 
             | It's not great for a number of reasons. First of all, `nil`
             | is always a valid permutation even if we don't want it to
             | be. Secondly, it's a lot of work to express the constraints
             | we want. Thirdly, it's not perfectly type-safe; someone who
             | was determined to shoot themselves in the foot could
             | construct other instances of our "singleton types" if they
             | really wanted to. Fourthly, boxing all of these values will
             | almost certainly cause unnecessary allocations which would
             | be a bummer if we wanted to create a lot of these quickly
             | (this is an implementation detail and may not be any worse
             | than a language with first-class support for sum types).
        
               | morelisp wrote:
               | Fifth, and most critically for me why it's not really any
               | better than a comment on an interface{}: a user's matches
               | won't be checked for exhaustiveness if you add a new
               | type, let alone them poking around the internals. You can
               | box them safely, but not unbox them.
        
         | arendtio wrote:
         | I agree, this article is a perfect example of the weak parts of
         | Go.
         | 
         | I love Go for many decisions that have been made during its
         | development, but the handling of enumerations is certainly not
         | part of it.
        
       | bitwize wrote:
       | We have a useful lower bound for type-system richness: if you're
       | developing a new language in the 21st century and your type
       | system is not at least as rich as Hindley-Milner, do not expect
       | programmers to take your language seriously.
        
         | pjmlp wrote:
         | If you want to have some fun, compare Go's type system with
         | CLU.
        
       | sugarpile wrote:
       | There are times this makes sense to do (mapping an enum to a db
       | without enum support eg mssql) but this isn't how I'd do it.
       | 
       | Personally I use stringer[1] to handle all the string generation
       | and then implement the valuer and scanner interfaces on that
       | type. Boom transparent mapping between useful values and their
       | corresponding db representation without having to write so much
       | boilerplate.
       | 
       | [1] https://pkg.go.dev/golang.org/x/tools/cmd/stringer
        
       | lhorie wrote:
       | Personally, I think the only part that makes sense is making a
       | type to hide the implementation. That String() method is kinda
       | nasty: It's O(n), and you need to edit it every time a category
       | gets added/removed/changed.
       | 
       | I think it'd make more sense to just make a idiomatic struct:
       | type Genre struct {           id int           name string
       | }         func New(id int, name string) Genre {           return
       | Genre {id, name}         }
       | 
       | Then, you can define your categories normally:
       | var (           Adventure = genre.New(1, "Adventure")
       | Comic     = genre.New(2, "Comic")           // etc         )
       | 
       | And have a O(1) String() method that doesn't need to be edited
       | every time a new category gets added/removed/changed:
       | func (g Genre) String() string {           return g.name
       | }
       | 
       | You can also change the implementation of the type without
       | breaking consumers (e.g. maybe you want a pointer to a struct
       | instead of a struct)
       | 
       | This also means that you don't accidentally leak an "is-a"
       | relationship between the nominal type and the underlying
       | implementation type                   var foo Genre = Adventure
       | foo + 1 // ought to throw a compilation error!
        
         | gher-shyu3i wrote:
         | > Then, you can define your categories normally:
         | 
         | > var (
         | 
         | > Adventure = genre.New(1, "Adventure")
         | 
         | > Comic = genre.New(2, "Comic")
         | 
         | > // etc
         | 
         | > )
         | 
         | Yet there is nothing preventing anyone from reassigning
         | Adventure to genre.New(2, "Comic") or some other arbitrary
         | value. The fact that golang doesn't have the equivalent of
         | Java's `final` is just poor design.
        
           | lhorie wrote:
           | There's nothing preventing someone from editing the source
           | code to `const Comic = Adventure` either. Either you have
           | access to edit String() AND have access to mess with the
           | definitions in source code, or you don't have access to
           | either. The idea that `final` can protect you against
           | yourself is kinda silly IMHO.
        
             | gher-shyu3i wrote:
             | Think about the use case of a library import. In golang,
             | the user has the ability to change library "constants",
             | wreaking havoc in the process.
             | 
             | Secondly, `final` serves as strong and clear documentation.
             | We know just from looking at the definition that this is
             | variable is not going to be re-bound.
             | 
             | On a side note, this compiles in golang:
             | func main() {             true := false
             | if !true {             fmt.Println("wat")             }
             | }
        
               | lhorie wrote:
               | I mean, at some point, garbage in garbage out, no? For
               | example, in javascript, you can do `Array.prototype.map =
               | null`, in C you can do `#define if while`, etc.
               | 
               | It's highly unidiomatic to mutate bindings from libraries
               | in any language, even ones that technically allow you to,
               | so the observation that go is one of those languages
               | feels like nitpicking at obscurities.
               | 
               | My Java is rusty, but I recall that several years ago I
               | ran into some stuff in the Java core API that was
               | needlessly marked final requiring some nasty wrapping
               | around a huge class to get around. Trade-offs, trade-
               | offs.
        
               | gher-shyu3i wrote:
               | > I mean, at some point, garbage in garbage out, no? For
               | example, in javascript, you can do `Array.prototype.map =
               | null`, in C you can do `#define if while`, etc.
               | 
               | Yes, but a good language is supposed to minimize chances
               | of error, especially one that was designed after C, C++,
               | and Java. The industry progressed, and we have a better
               | understanding of what some good ideas are, and which to
               | avoid. The fact that golang allows null pointers is
               | inexcusable IMO. C# was able to retrofit a nullable
               | handling mechanism into the language, and practically any
               | useful language that was designed after that had some
               | sort of nullable type handling (Scala, Kotlin, Rust, and
               | I believe Zig and Nim as well, etc.).
        
           | morelisp wrote:
           | > The fact that golang doesn't have the equivalent of Java's
           | `final` is just poor design.
           | 
           | That Java dropped C++-style `const` is also poor design.
           | 
           | We're just all really bad at this.
        
             | gher-shyu3i wrote:
             | C++'s const has some issues, so I can see why. Plus, it's
             | possible to design around it by means of immutable data
             | structures (e.g. see Scala).
             | 
             | I think that Rust's approach is superior to C++'s here, but
             | even that has some issues so they have to retort to
             | interior mutability via RefCell.
        
       | nonameiguess wrote:
       | "Make Genre an int instead of string to save space, but then bake
       | the same strings into the String method of Genre anyway."
       | 
       | I don't understand why you would ever want to possibly save space
       | in a database at the cost of consuming it in your binaries
       | instead. You're almost certain to deploy more copies of your
       | binaries than you are of your database.
        
         | morelisp wrote:
         | Are you going to deploy more copies of your binary than rows in
         | your database? Or points in an event stream?
         | 
         | Plenty of problem spaces still wish to repeat the same value
         | many times _and_ give it a more human readable name when
         | necessary _and_ not coordinate a shared DB connection or even a
         | dynamic string pool.
        
       | mssundaram wrote:
       | It doesn't take much "leveraging" to do this in a language with
       | ADTs. E.g. Typescript                   type Book = {
       | id: number;           name: string;           genre: Genre;
       | };                  type Genre =           | "Adventure"
       | | "Comic"           | "Crime"           | "Fiction"           |
       | "Fantasy"           | "Historical"           | "Horror"
       | | "Magic"           | "Mystery"           | "Philosophical"
       | | "Political"           | "Romance"           | "Science"
       | | "Superhero"           | "Thriller"           | "Western";
        
         | christiansakai wrote:
         | As a JavaScript developer I once abhorred TypeScript, until I
         | realized TypeScript's type system is really powerful, it can do
         | something like <Parameters<GetSomeFunc>> and
         | <ReturnType<DoThisFunc>>.
         | 
         | Mind. Blown.
        
           | mssundaram wrote:
           | Yeah, when I first came across TS, I didn't see the point,
           | mostly because I didn't see how powerful Typescript's type
           | system is - it gets more amazing by the day, too.
        
         | ed25519FUUU wrote:
         | The benefit to having a type is mainly IDE experience and
         | catching it at compile time. It's easiest to say
         | `Genre.WESTERN` than using the string `western` and hoping you
         | didn't accidentally type `westrn` and get a runtime error.
        
           | wffurr wrote:
           | Compile time checks on the strings are exactly what you get
           | with that TypeScript definition above.
        
           | mssundaram wrote:
           | const someBook: Book = {           id: 1,           name:
           | "Title",           genre: "wstern"          // Compile error:
           | [tsserver 2322] [E] Type '"westrn"' is not assignable to type
           | 'Genre'.         };
           | 
           | That's not how string literals work in Typescript. If you
           | mis-type the string you'll get a compile error. It won't make
           | it to runtime.
        
       | echlebek wrote:
       | Rob Pike already made an easier way to do this, using go
       | generate, in 2014. https://blog.golang.org/generate
        
         | tsimionescu wrote:
         | The fact that you have the generate code with that approach
         | already makes it worse.
        
       | phyrex wrote:
       | Frankly, this article has the opposite effect of convincing me.
       | The author starts with a perfectly reasonable data model and
       | munges it for some questionable space savings (that only apply in
       | the dumbest of databases that don't do any sort of compression),
       | for the price of a lot of boilerplate, and significantly less
       | insight into the data if you look at it in serialized format
       | (either directly in the database or on the wire). Maybe someone
       | can explain to me how this is supposed to be better?
        
         | corylanou1 wrote:
         | You aren't wrong. I think it depends on what your needs are. As
         | well as what the scenarios are.
         | 
         | If you are serializing this data a lot, then having ints vs
         | strings is a significant advantage.
         | 
         | Again, it depends on your use case. If you don't care about
         | serialization size, then I agree, it's extra effort that you
         | may not need.
        
         | shadowgovt wrote:
         | One of Go's strengths is that it should make it easier to swing
         | between the optimized abstraction and the less-optimized
         | abstraction. If you start with that data represented as strings
         | and then find yourself backed into a performance corner and
         | _need_ to change the representation, Go types and type-based
         | compile-time method selection can make it a bit easier to make
         | that change.
         | 
         | Even in this era of fast computers and preposterously large
         | storage, one sometimes hits a situation where the way to get
         | the improvements you need is to start number-representing your
         | strings.
        
           | corylanou1 wrote:
           | That's a great point. Anyone who has spent significant time
           | with Go knows how easy it is to do a large refactor (or even
           | a minor one) due to it being a types language.
           | 
           | And yes, you may not start with ints, but you could easily
           | add code for the marshal/unmarshal later on to convert those
           | strings to ints for serialization purposes. And it wouldn't
           | require a change to any of your other code, not to mention
           | that if you stored these values in a database already you
           | don't need to perform a migration either.
        
             | gher-shyu3i wrote:
             | Go has many shortcomings that make it difficult and
             | extremely annoying to do refactorings. Things like no
             | constructors where types are declared as follows
             | A {             Field1: field1,             Field2, field2,
             | }
             | 
             | Now adding a new field to A would require ensuring that all
             | code paths initialize Field3, otherwise you're going to
             | have silent errors at runtime. This has been solved ages
             | ago in Java and C# and similar languages by means of
             | constructors.
        
               | shadowgovt wrote:
               | A constructor is just a function though... If I add a new
               | field to a Java class and fail to add its initialization
               | to the constructor, I have the exact same problem because
               | Java initializes the field to its default value when the
               | constructor is called.
               | 
               | You are correct that every situation where the struct in
               | Go is initialized "bare" would need to be addressed if a
               | field is added, but Go considers this a feature, not a
               | bug (and, conversely, considers "bare initialization" of
               | structs at dozens of places in your code to be bad
               | practice if that struct could ever grow new fields). If
               | you're bare-initializing structs, you're comfortable
               | using them in a "loosey-goosey" context where zero-
               | initialized fields are permitted (or, better, useful...
               | https://www.youtube.com/watch?v=PAAkCSZUG1c&t=6m25s). In
               | Go, the issue of required structure is addressed by
               | wrapping the struct in an interface and then providing a
               | function in the package that can create an instance of
               | the interface. Used in that way, you get something very
               | similar to a Java class (though Go doesn't force you into
               | the "everything is a class" paradigm that Java demands).
        
               | gher-shyu3i wrote:
               | > but Go considers this a feature, not a bug
               | 
               | I see this dismissive philosophy in golang a lot (I'm not
               | saying you're doing it, mind you). The entire language is
               | full of arbitrariness. I've seen enums being dismissed by
               | the golang team just because, without providing any
               | meaningful arguments. Same with how nullability was not
               | addressed in the language, contrary to how proper modern
               | languages have tacked the issue. The excuse? "that's how
               | the underlying machine operates". Quite meaningless and
               | dismissive really.
               | 
               | Time and time again, it's been shown that the goal is to
               | have a simplistic (not simple) language that also makes
               | it easy to write the compiler for. This breaks down at
               | larger scales because reality is complicated.
        
               | saagarjha wrote:
               | Usually you would create a constructor without that
               | parameter which sets the field to something backwards-
               | compatible.
        
             | stouset wrote:
             | This perspective ignores the fact that there is no shortage
             | of typed languages that don't have all the--in 2021--
             | inexcusable downsides of golang.
        
               | morelisp wrote:
               | There is unfortunately a shortage, still in 2021, of
               | languages that have a null set of inexcusable downsides.
               | 
               | I will trade - unhappily - ADTs for value types,
               | automatic memory management, some language-level
               | concurrency support, a compiler that builds our largest
               | project in under two minutes, and a community large
               | enough I can spend my time training new hires on
               | fundamentals and business problems and not tool
               | onboarding.
        
               | pjmlp wrote:
               | .NET Native, OCaml, Eiffel, Common Lisp, D, Nim just for
               | starters.
        
           | mhh__ wrote:
           | Is this compared to a good language or (say) server-side
           | JavaScript because the languages I tend to use allow me to do
           | that and Go looks looks very Algol-68-y from that perspective
           | as opposed to some hyper abstract wonder-language.
        
             | shadowgovt wrote:
             | What languages do you tend to use?
        
               | mhh__ wrote:
               | I work for the D foundation, so mainly D for things I get
               | to choose.
        
         | newlisper wrote:
         | Bringing Java's premature over-engineered over-abstracted
         | practices to Go.
        
           | shellac wrote:
           | Java has Enums, so why would you bother with all of this?
        
           | [deleted]
        
         | treis wrote:
         | I think this is mostly a toy example. In the real world the
         | Genre would have it's own data, books can belong to multiple
         | Genres, and you'd need the concept of SubGenres. So you'd have:
         | 
         | Book
         | 
         | Genre
         | 
         | BookToGenre
         | 
         | GenreToGenre
        
         | dastx wrote:
         | That boiler plate usually can be generated for you. See Go's
         | stringer which does all the magic for you. All you have to do
         | is define the type and constants, and a go generate directive.
        
           | itake wrote:
           | Code generators don't solve the problem of the language being
           | too verbose/boiler plate. Instead of expressing a concept in
           | a simple fashion, you have to generate 100s of lines of code
           | that needs to be maintained across versions of golang and
           | your data model.
        
           | tsimionescu wrote:
           | And install stringer in every dev env, and debug all the
           | boilerplate anyway when things go wrong.
           | 
           | Code generation is always the last resort.
        
             | dastx wrote:
             | I'm not saying it's the solution. I'm just pointing what is
             | idiomatic go. Certainly there are arguments for and against
             | code generation.
             | 
             | Where I work we are a go house, and the number of tools
             | I've seen in our pre-commit configurations and in our
             | development environment setup scripts is pretty intense. So
             | is the number of questions around said tooling. There's a
             | lot more friction than I'd expect. I can't even use lack of
             | experience as an excuse for these questions because they're
             | really talented people. Some of them have worked on the
             | product for years, and have literally moulded the product's
             | tooling into what it is today.
        
           | lma21 wrote:
           | would you have a sample code? sounds interesting
        
             | dastx wrote:
             | Example from the docs:                   // pill.go
             | package painkiller                  //go:generate stringer
             | -type=Pill         type Pill int              const (
             | Placebo Pill = iota           Aspirin           Ibuprofen
             | Paracetamol           Acetaminophen = Paracetamol         )
             | 
             | Then running `go generate` will yield:                   $
             | cat pill_string.go         // Code generated by "stringer
             | -type=Pill"; DO NOT EDIT.                  package
             | painkiller                  import "strconv"
             | func _() {          // An "invalid array index" compiler
             | error signifies that the constant values have changed.
             | // Re-run the stringer command to generate them again.
             | var x [1]struct{}          _ = x[Placebo-0]          _ =
             | x[Aspirin-1]          _ = x[Ibuprofen-2]          _ =
             | x[Paracetamol-3]         }                  const
             | _Pill_name = "PlaceboAspirinIbuprofenParacetamol"
             | var _Pill_index = [...]uint8{0, 7, 14, 23, 34}
             | func (i Pill) String() string {          if i < 0 || i >=
             | Pill(len(_Pill_index)-1) {           return "Pill(" +
             | strconv.FormatInt(int64(i), 10) + ")"          }
             | return _Pill_name[_Pill_index[i]:_Pill_index[i+1]]
             | }
             | 
             | Note: indentation is messing up because of Hacker News, so
             | ignore the indentation.
        
       | singhrac wrote:
       | I'm going to be honest, after writing some programs in Go a few
       | years ago, I was hoping for something nicer by now. I know
       | writing Go is good because it's boring, but all this boilerplate
       | just feels like reading Java in 2005.
        
         | pjmlp wrote:
         | Including code generation, remember Eclipse EMF?
         | 
         | Some people love reinventing the past.
        
         | mssundaram wrote:
         | Without Algebraic Data Types, Go is just awkward. This example
         | could be so simple typed as the sum of string literals.
        
           | [deleted]
        
           | [deleted]
        
       | cdelsolar wrote:
       | You can also use `go generate` to generate the code that
       | translates constants into strings.
        
         | jerf wrote:
         | That's kind of misleading, like there's some sort of built-in
         | support in some sort of go command that does that already.
         | 
         | "go generate" is really a red herring. It's completely
         | meaningless. It allows you to define some commands that get run
         | when you type "go generate", but... if I define a shell script
         | called "blah", I can get some commands to run every time I type
         | "blah". go generate doesn't do anything useful.
        
       | beatbrot wrote:
       | So what you are describing is enums but way more complicated?
        
         | corylanou1 wrote:
         | In a way, yes. Go doesn't have the concept of enums, so this is
         | as close as it gets.
        
           | stouset wrote:
           | Further demonstrating the point that simplicity in the
           | language just winds up forcing complexity into your code.
        
         | Groxx wrote:
         | And unsafe, since you can trivially make a `Genre(1138)` and
         | that's correct, compile-able code.
         | 
         | The same is true for a `Genre(0)`, which is easy to make by
         | accident with just a `var genre Genre` declaration (that's a
         | `Genre(0)` already). Or by ignoring an error return, like in
         | `json.Unmarshal(data, &genre)` (that func returns an error, but
         | that fact is invisible in text, and you are not required to
         | assign or handle it. fun for code reviews!).
         | 
         | You can also have a Genre-arg func like `func GenreToString(g
         | Genre) string` and call it with `GenreToString(12345)` and
         | that'll also compile.
         | 
         | ---
         | 
         | Go does not have anything even remotely like enums. It has a
         | short-hand for auto-incrementing constants, nothing more. As if
         | that were somehow the most valuable part of constants / enums.
        
       | musingsole wrote:
       | I am once again underwhelmed by the amazing might of a type
       | system.
        
         | mssundaram wrote:
         | I hope you mean _Golang 's_ type system. It is indeed
         | underwhelming. However it is arguably the poorest type system
         | in popular usage. Therr exists much better, which I encourage
         | you to explore - Typescript, Haskell, Rust
        
           | pjmlp wrote:
           | Oberon-07 is even smaller, as Wirth decided to revisit Oberon
           | and pursue the path of the minimalist type safe systems
           | programming language. If any language of Oberon family would
           | ever become mainstream I would vote for Active Oberon
           | instead.
        
         | [deleted]
        
       | ibraheemdev wrote:
       | I don't really think that this article should be called
       | "Leveraging the Go Type System", considering that the entire post
       | was dedicated to "working around Go's lack of enums". That said,
       | simplicity is a core part of Go's philosophy, and being a simple
       | language isn't necessarily a bad thing, but it does make
       | seemingly simple tasks very frustrating at times.
        
       | leetrout wrote:
       | For an example with Go generation and using iota see stringer[0]
       | and Rob Pike's related blog post[1]
       | 
       | [0] https://pkg.go.dev/golang.org/x/tools/cmd/stringer
       | 
       | [1] https://blog.golang.org/generate
        
         | everybodyknows wrote:
         | Perhaps the Golang managers would do well to elevate _stringer_
         | into their suite of supported commands:
         | 
         | https://golang.org/cmd/
        
         | dmitriid wrote:
         | Go has so many `go generate` tools to overcome its
         | shortcomings, it's ridiculous.
        
           | throwaway894345 wrote:
           | This is true, but I'll take these shortcomings over
           | deficiencies in tooling, ecosystem, performance, learning
           | curve, etc any day. In other words, I can ship software
           | pretty easily with a suboptimal type system, but much less
           | easily if I have to select for candidates with a decade of
           | experience in a particular VM runtime or if the performant
           | runtimes are incompatible with important parts of the
           | ecosystem or if there is no sane package management tooling
           | or if the ecosystem itself is lacking important high quality
           | packages. I'm of the opinion that in-language features are
           | overrated.
        
             | tsimionescu wrote:
             | To be fair, Go's package management is far from sane
             | (though better than nothing, of course). The tooling is
             | also sub-par for modern languages, especially in terms of
             | free development environments, though go-pls is slowly
             | improving.
        
               | throwaway894345 wrote:
               | > To be fair, Go's package management is far from sane
               | (though better than nothing, of course)
               | 
               | It's not perfect, but it's among the best. It's far
               | better than the popular offerings for Python, C, C++, and
               | Java. NPM was also pretty terrible last I used it (needed
               | to regularly blow away node_modules and reinstall things,
               | random ENOENT errors, etc) and I think the .net world
               | almost adopted a sane project file structure before
               | reverting back to MSBUILD but I might be wrong there.
               | OCaml was also pretty terrible last I used it, but people
               | swear to me that Dune has fixed everything (though those
               | guys have been telling me for the decade that no one
               | needs shared memory parallelism and also it's right
               | around the corner, so maybe take that with a grain of
               | salt).
               | 
               | > The tooling is also sub-par for modern languages,
               | especially in terms of free development environments,
               | though go-pls is slowly improving.
               | 
               | Go doesn't have great IDEs because it's always been more
               | of a text-editor language, and few languages have editor
               | plugins of comparable quality. It also doesn't have a
               | great debugging story (there is Delve, but I've heard
               | that it's not a great debugging experience). These don't
               | really chafe me because I don't like IDEs and I'm the
               | unpopular guy who hates debuggers. Those are the biggest
               | "gaps".
               | 
               | For other things though, Go is great. Testing? Built in.
               | Benchmarking? Built in. Profiling? Built in. Cross
               | compilation? Built in. Static linkage? It's the default.
               | Formatting? Built in. Documentation generation? Built in.
               | Code and documentation packaging and publishing? Built in
               | (to git). Not only are all of these things built in, but
               | they're standard across the ecosystem so developers can
               | onboard with minimal learning curve.
        
               | tsimionescu wrote:
               | > It's not perfect, but it's among the best. It's far
               | better than the popular offerings for Python, C, C++, and
               | Java.
               | 
               | Maven is significantly better than Go modules: it doesn't
               | require special support from your git server, it doesn't
               | rely on magic tags with magic formats, it does strict
               | dependencies by default, it handles branching any way you
               | want, it works with binary artifacts not just source
               | code, it has explicit support for custom repos, it
               | doesn't require renaming your package to do a major
               | version upgrade, it doesn't rely on DNS for package
               | names, it works for small cross-language dependencies,
               | and probably others.
               | 
               | > For other things though, Go is great. Testing? Built
               | in. Benchmarking? Built in. Profiling? Built in. Cross
               | compilation? Built in. Static linkage? It's the default.
               | Formatting? Built in. Documentation generation? Built in.
               | Code and documentation packaging and publishing? Built in
               | (to git).
               | 
               | Testing is actually pretty bad, go test is OK, but the
               | lack of any kind of basic testing tools like a deep
               | equality or mocking helpers make it much harder to use
               | than most. Code coverage measurement is also pretty bad,
               | and un configurable.
               | 
               | Benchmarking is decent, it's nice to have it built in, no
               | complaints there.
               | 
               | Profiling is horrible, just some text format that's
               | barely documented, no memory dumps, no thread bases
               | profiling, no tooling to analyze complex profiles.
               | 
               | Cross compilation is nice, but it's not even required in
               | the most direct competitors (Java, C#).
               | 
               | Static linkage is the only option, and neither a good nor
               | a bad feature.
               | 
               | Formatting is forced on everyone, and is completely
               | unconfigurable. Their choices often mess up for merges
               | for stupid anesthetic reasons.
               | 
               | Documentation is built-in to Java and C# as well, and it
               | supports more than basic text for describing your code.
               | They also don't rely in any way on your choice of source
               | control system.
               | 
               | Overall, Go's ecosystem is definitely sub par compared to
               | Java or C#. It's not the worse, and it can do the job,
               | but you'll never be as productive.
               | 
               | Their support for small fats starting binaries is the
               | only thing where Go really shines.
        
               | pjmlp wrote:
               | Makes sense, after all it is catching up with Mac OS,
               | OS/2 and Windows 95 IDEs.
        
               | throwaway894345 wrote:
               | Yeah, I imagine it's a real problem for the people who
               | don't know their way around a shell.
        
               | pjmlp wrote:
               | Or those that grew up with CP/M, MS-DOS and Xenix and
               | aren't stuck in those days.
        
           | pjmlp wrote:
           | Yeah, remember macro based generators before C++ got
           | templates, or Eclipse EMF before Java got annotations?
           | 
           | Although we did survive with them and delivered code into
           | production, it doesn't mean we keep using them 20-30 years
           | later.
        
         | ViViDboarder wrote:
         | This is cool and something about Go that I hadn't researched
         | much yet. It does make me wonder, however, why not just include
         | some higher level structure for this? Or even some method built
         | into the compiler? It feels odd to have to install a dependency
         | like `stringer` just to build. Do I had it to my go.mod file?
         | Also, the comment structure, while flexible, could be greatly
         | simplified for common cases like this if there was, for
         | instance, an enum type.
         | 
         | I really do enjoy Go, but there are several examples of things
         | like enums that are left out because there is some way of doing
         | it otherwise to only the theoretical advantage of "elegance" at
         | the cost of developer productivity.
        
       | maxekman wrote:
       | I have used Go in production for enterprise/business systems
       | since 2015 and what I miss most is true sum types that really
       | limits the state space of composite types. It's being discussed
       | somewhat actively for Go 2, and I would love to see more support
       | for the idea. It's really useful for business logic.
        
       ___________________________________________________________________
       (page generated 2021-02-09 23:01 UTC)