[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)