Nine years of Go
blog.golang.org
blog.golang.org
My take is that Go is a great language when you're working with incompetent people. It's so limiting in the abstractions you can build with it, that it is very difficult to write code that is not immediately obvious in its purpose. Go is easy to teach, easy to review, and it's so syntactically deficient and the tooling is good enough that it's hard to bikeshed. This ultimately improves the experience of writing code at work, and its static type system makes it easier to reason about large code bases for the folks who are used to using Ruby and Python at scale.
But Go is also awful in so many ways, its such an obvious step backwards in its design and the community is so dogmatic about Go and the Go authors.
On large code bases you almost invariably end up working around the deficiencies with the empty interface, code gen tools that add non standard syntax, and lots, and lots of copying code and code patterns over and over again. Any common pattern you see emerge, that you'd like to create a generic structure for, is probably impossible to encode without type variables. Go also notoriously pushes many errors that would have been caught at compile team to runtime because of its weak type system. With Sum types, it's difficult to make improper states illegal, and default values lead to subtle bugs because they essentially behave as predictable garbage.
Go honestly keeps me from having faith in software engineering as a craft. It's the acceptable of the status quo, or worse, moving backwards in order to fit the needs of the lowest common denominator.
Do you think this is intentional? Similar to how java is incredibly restrictive, and thus great for corporate programming environments?
-- Rob Pike
Fast compilation times in Go are only a surprise for a generation that never used native programming languages with native support for modules.
As for C++ compilation times, yes they are an abomination when doing make world from scratch, including third party dependencies, with heavy template code.
However modules are around the corner and they were just voted into C++20.
This.
What kinds of errors would you expect to be "caught at compile time" with a different type system?
What kinds of "default values lead to subtle bugs"?
Have you looked into...
a) the Go2 generics draft design
https://go.googlesource.com/proposal/+/master/design/go2draf...
b) the Go2 error handling draft design feedback (the initial concept is unworkable :-)
I've done codegen through a DSL and through comments. The DSL is actually typed, which is nice, but it's also its own mini-language that you have to teach people on top of the Go basics. It's also a general rule that it should be used sparingly because it tends to generate very verbose and ugly code.
> What kinds of errors would you expect to be "caught at compile time" with a different type system?
The empty interface, which must be cast to the contained type to be useful, is an obvious one. Generally type casts, error handling, pointer dereferences, all can lead to run time bugs.
At the macro level, a language with a rich type system should make invalid program states fail to compile. With true phantom types, for example, one can make a state machine that will fail to compile if a state transition invariant is not satisfied.
> What kinds of "default values lead to subtle bugs"?
The issue is that the notion of a "default value" makes little sense. For many applications 0 is not a good choice for the initial value of every integer. Someone who forgets to set the integer to an appropriate value will run into the same error as in C, except instead of a trash value, they will find a consistently bad value, which is slightly better.
Better to throw an error if an uninitialized value is used, and demand that all struct fields be set unless the struct author has explicitly set default values.
> Have you looked into...
Yes, I have seen the proposals. I am an opponent to generics in Go. I think Go excels at exactly that which I described in my first post. If you add more to the language, I believe it will morph into a poor mans Java instead of a better Go. Better to simply move to a language that was built from the ground up with the intention of providing abstractions (I like Rust), and use Go when its upsides outweighs its downsides. Many times, they do, especially in businesses where the talent pool is limited.
I have come to see it as a tool for the folks that want a plain better C and are happy with having a GC around to keep them productive.
There is no need for Go to chase Java.
I let you research the year.
Despite taking longer to build, it's a treat to work on Golang. The explicit error handling and static typing have made our code much more reliable, with a lot less unit/integration tests required. Concurrency is dead easy to understand and build over. We are OK with using dep, definitely miss a debugger or an REPL. Having generics would make it much easier to write code. But overall it was worth it. Our response times are much lower now with a lot less server nodes required.
And talk about the build size, time and the fact that you could generate a build and run it bare metal anywhere.
Nowadays there is Graal and SubstrateVM for those that rather not pay for compilers.
I personally use Visual Studio with the go extension and delve and the debugger works quite well.[1]
I believe that the Goland IDE also has an integrated debugger.[2]
[1] https://github.com/Microsoft/vscode-go/wiki/Debugging-Go-cod...
Bizarre that a programming language designed by non first time language designers, backed by a multi-billion dollar company and used extensively throughout that company(by "new" engineers), doesn't have a world class debug story.
This is especially true in Go where the type checker gives you a lot of confidence that your code works before you run it.
— Brian W. Kernighan, in the paper Unix for Beginners (1979)Having a debugger available does not drop your IQ by 20 points.
Not necessarily, but it can have that effect. Sometimes I spend a long time in the debugger, following the chain from effects to causes to the causes’ causes and so on, getting a little dopamine hit every time I uncover a new level – it’s exciting, like watching a detective show – and ultimately discovering the root cause… only to have it be something dumb or obvious, something I might have found in less time by just patiently rereading the code.
https://github.com/derekparker/delve
Apparently Delve supports Win and OSX/macOS too, though I've not tried it on those in years. On BSD though... there's no debugging story. :(
You can use a world class debugger, known as gdb, with Go, and yes, they do provide extensions for gdb to make it understand high-level Go constructs (such as goroutines, maps, slices, channels, ...): https://golang.org/doc/gdb
So if you want me to spell it out, here you go: it's bizarre to expect a debugger from modern compiled languages which have been FOSS from the beginning.
Also fails regarding other FOSS languages like Python, which have had a debugger since ever.
I said compiled language, which Python isn't. Yes, gdb won't help you debugging scripting/VM languages.
Anyway, this is getting off topic, as we're talking about the alleged lack for debuggers for Go binaries for today's Go users, and neither of the things you mentioned are relevant. In my experience, nitpicking rarely leads to anything fruitful.
And if I have to mention a FOSS compiled language instead of Python, with debugger support out of the box, then Oberon, Modula-2, FreeBasic, Gambas and FreePascal come to mind.
Break your software down into smaller components and write tests for each of them.
I found a video of someone debugging Go code in VS Code, and I saw them set breakpoints, step around in a function, and hover over a variable to see its value, in addition to the sidebar containing all of the values of all of the local variables and the stack traces.
gdb works well with Go: https://golang.org/doc/gdb
I've used the community dep project for a while, which was okay. I switched to the GO111MODULE experiment and it's more than okay.
See Go2 Error Handling feedback:
https://github.com/golang/go/wiki/Go2ErrorHandlingFeedback
And possible requirements for Go2 errors:
https://gist.github.com/networkimprov/961c9caa2631ad3b95413f...
There are so many incompatible, NIH wannabe solutions to packaging in Python. Recent tools like pipenv have made things easier from the user standpoint, but if you dare to look at how the sausage is made it's still an absolute mess.
Even if newer packaging tools are better, there are still tens of thousands of packages that use the older, nonstandard, even messier solutions.
I first heard of that in context of writing to the screen, actually, when you don't want to redraw all the things every frame, or simply can't afford to, and only draw the things that have changed (i.e. are "dirty"). So if you were to keep track of the old position of an object, you might redraw the area of background where that object was, and then just draw the object at the new position, instead of drawing the background for the whole screen, and then the object.
Browsers are also kinda heavy on this when it comes to both layout and drawing. E.g. when the content of a div with fixed size changes, you only have to layout and draw the text in the div -- but if the size isn't fixed, you might relayout and redraw everything on the page that follows the div, but might not need to touch anything that comes before it. I'm sure it's incredibly complex, but still much faster than not doing it.
Here's a scenario. A woman gets married. Her last name changes. She updates the profile from Smith to Jones. The last name is now dirty. The code that save the object can say, "Are you dirty?" The struct says, "Yup". Saving code says, "Right! In you go." On the other side, the bit gets flipped back to false (it's cleaned). At the end, the revision updates by one and goes back to the client.
Another scenario. A user opens the edit profile box, doesn't change anything, but hits save. Without a dirty check, the object, which hasn't fundamentally change, gets saved with a new revision number.
Now what is all this about the revision number? I don't want to allow changes to a profile whose revision number is greater than the revision number my client passes. Let's say the Profile has revision 5. The client, which has been disconnected for a bit, says, "Update the profile, at revision 2, with all this new great data." The system needs to reject that update. The client needs to tell the user, "Sorry. That didn't work. Here's all this new data." If the client is especially Canadian, it will also say, "Oh, by the way we've saved your data, would you like us to copy it over the new data". I've never seen Canadian code, and mine is especially Floridian, but it could happen like that.
All of this is fairly easy in OO languages, which Go is not. It's actually easy in Go too if you use -ters and Setters (Go doesn't like GetFirstName(), but is okay with FirstName()). If I use functions attached to structs, easy. Just curious how other feel about this.
Or just use getters and setters and if anyone calls you stupid, ask them to demonstrate a better solution that still solves your problem.
The problem is the package managers available are fragmented and fundamentally incompatible. You have dep which is the official “experiment” and 10 others that share no compatibility whatsoever. Until there is either an official package manager or at least a standardization of the package format, you will run into these issues.
It's even mentioned in the blog post: "Last spring, we published a draft design for Go modules, which provide an integrated mechanism for versioning and package distribution. The most recent Go release, Go 1.11, included preliminary support for modules."
Because go modules came later, it will take a while for the community to convert, and there will still be dark corners running on Glide for probably another year or two (or longer).
>Go 1.11 includes preliminary support for versioned modules as proposed here. Modules are an experimental opt-in feature in Go 1.11, with the hope of incorporating feedback and finalizing the feature for Go 1.12. https://github.com/golang/go/wiki/Modules
They are officially present and will be compatible in some way with future releases, but its still having the kinks worked out.
> 6 months
> preliminary support
No, I didn't miss hearing about it, just like no one missed hearing the Python 3 release.
You've got nine years of open-sourced Go...even in the best case scenario, it'll take more time that six months after a draft design for this to not be a common, massive pain point.
However, the toolchain is lagging [1], sometimes in quite serious ways. Pretty much every third-party tool that reads Go source code — linters, code generators and so on — uses an unofficial, largely undocumented package called golang.org/x/tools/go/loader to parse Go code into AST structures. This package has not been updated to understand Go modules, and so it fails, and it has been abandoned in favour of a new, also largely undocumented library, golang.org/x/tools/go/packages, that works completely differently. There's a tracking issue [1] that discusses the progress of converting these tools. There's no document that describes how to migrate.
It's annoying because we've come to rely on some, such as Mockery and go-sumtype, that still don't work with Go 1.11. In both cases the authors seem to have abandoned the projects, and updating the code isn't always so trivial.
FWIW, the .../loader package is explicitly documented as experimental. The .../packages has a deadline of 1 Dec 2018 for breaking changes (both info found in their respective godoc)
Given that modules support is still officially experimental, there are bound to be rough edges. But by 1.12, I think we will have a solid modules story.
what is high and low level depends very much on the context, but I don't see GO to be lower level than C#...
* I only need inner loop high performance - it's a fantasy console, and intentionally limited. It will have some C code, but I don't want to write a lot of it.
* I want the project to be easy to build with few external dependencies(raylib is tops at getting this right)
* I want static types and zero-indexing for the "OS layer" part, at least, since I have to build some original data structures for IDE functionality(e.g. there will be a text editor).
It was the last part that made me switch away from Lua, and then it was the first two that made me consider Go.
If so, it seems a bit too buggy still for really wanting to write production code with it. :(
G3N seems ok:
Although it doesn't yet have a long history of maturity either. :/
function exit(code: integer); external; cdecl;
No need to have two phase compilation or run external tools.Edit: Tons of others, really
* Hugo, a static site generator: https://gohugo.io
* Gogs, a git service: https://gogs.io
* Syncthing: https://github.com/syncthing/syncthing
* CockroachDB, a scalable SQL database: https://www.cockroachlabs.com/
[0]: https://gitea.io/
It would be nice if someone wrote an article that did a deep dive on the current state of both of them.
Looking at Git activity, I can clearly see that Gitea is more actively under development by every metric.
However, since they are now divergent code bases, what does Gogs offer that would make someone inclined to use it over Gitea?
It's like TensorFlow and PyTorch had a bastard child. Pure Go. Train your NN like you would in TF and PT.
nats
traefik
Go is a very useful language for this space, as it's easy (we aren't developers after all), it's safe, fast, compiles to a single binary, easy cross-compile support, software written in it tends to be robust, great basic library, etc.
* rclone, like rsync but for cloud storage APIs: https://rclone.org/
https://github.com/avelino/awesome-go
https://github.com/uhub/awesome-go
https://github.com/gobuild/awesome-go-tools
https://awesome-repos.ecp.plus/go.html
This one went further than just assembling the list: