"14 Years of Go" by Rob Pike
codereliant.io
codereliant.io
Go: What we got right, what we got wrong - https://news.ycombinator.com/item?id=38872362 - Jan 2024 (694 comments)
The outline of that post is very clear, you can skim the original very quickly.
That link is conspicuously missing here, giving readers the impression that the talk is only available in video form. Not cool.
Go doesn't have a build system, so I don't have to learn that. (I spend every second I'm using Gradle to curse it's very existence -- and wish I had `go build`)
Go cross compiles natively, so I don't have to think about the toolchain.
Go has go:embed, so I don't have to think about bundling/packaging as a separate step.
Go has fantastic backwards compatibility, so I don't have to spend time getting an old project to even build.
Go's stdlib is extremely high quality, so much so that I've never run into a serious bug in it.
When jumping into other ecosystems, I'm shocked at how much time is spent fiddling with build scripts, packaging, deprecations, unfixed bugs in the tooling etc...
Still wish it didn't explode on null pointers though. And any large dependency authored by Google will be unidiomatic and over-complex, of course (see: grpc)
I'd also mention how great the documentation is. Truly best in class. For instance, see https://pkg.go.dev/net/http
* Has enough examples to fully understand how to use the package.
* Links to the individual source code files so you can read those if needed.
* Has a highly visible link for reporting vulnerabilities.
* Has a great search tool accessible with a keyboard shortcut.
* Clearly marks deprecated functions.
* The page even works without Javascript enabled.
Other ecosystems seem to have a "don't worry yourself about that" approach to viewing a package's source, and it's maddening. In contrast to Go, trying to get from docs to source code in the JVM ecosystem is by no means straightforward.
Is there a programming language that has a "simple" design like Go but which supports some operator overloading?
But then you'll soon want to do A*B+C, and you'll find that fused multiply/add is a much faster operation than doing a multiplication and then addition, and soon you'll either be writing mul_add(A, B, C) anyway, or you'll overengineer a complex solution to make A*B return some kind of object that can recognize the subsequent + operation etc.
Update: corrected * representation
1. You have large matrices. In this case, I'd think that the O(n^2) addition can be ignored, because it gets dwarfed by the the O(n^3ish) multiplication.
2. You have small matrices. In this case, the computation is likely bound by memory bandwidth. Waiting for the matrices A and B takes most of the time. Then you multiply them, which should go quickly, since the matrices are small. Then you do the addition with the matrix C, but since the product AB is already in cache and since you'd have to wait for the matrix C anyway, there is not much to be gained with a fused multiply-add.
Yes, but it's C++, so the cake has 200 ingredients, a 1000 step recipe (the majority of which steps are deprecated and have newer alternatives), tastes bland, and takes days in the oven.
Jokes aside, that is pretty cool, I didn't know C++ templates did that.
1: btw Lisp could do that as well, if they wanted. C++ supports thousands of syntax features, Lisp supports infinite.
The evaluation then happens as assignment or Vec construction when you have something like:
Vec r = a * b + c
You could either inline the functions on Mul and Add in the evaluation loop and rely on the compiler to deduce that an FMA instruction can be used. In the unlucky case, you at least avoid the intermediate a * b vector.Another option, since the expression is represented at the type level is to write a compile-time evaluator that replaces patterns like Add<Mul<Vec, Vec>, Mul> by FMA<Vec, Vec, Vec> using templates.
This general idea is the foundation of libraries like Eigen.
Finally you overload the dereference operator of MultiplyAddExpression to actually run std::fma. With some template magic, this can all even be done at compile time.
You can look at boost::phoenix and boost::qi to see this taken to a huge extreme, overriding all of the C++ operators to write parsers using regex-like syntax (e.g. `parser = *a | +b;` generates a parser which recognizes "" or "aaa" or "bb", equivalent to the regex "a*|b+").
So yes, you can have efficient nice abstractions, but only at the cost of an extraordinarily complex base language.
At any rate, I feel like the goalposts have been moved a bit. You originally said:
But then you'll soon want to do A*B+C, and you'll find that fused multiply/add is a much faster operation than doing a multiplication and then addition, and soon you'll either be writing mul_add(A, B, C) anyway
Which I just wanted to point out that you can fuse these operations with operator overloading as well. I am pretty sure that
or you'll overengineer a complex solution to make A*B return some kind of object that can recognize the subsequent + operation etc.
was a later edit, I don't remember reading it when I first reacted to your comment. If so, doing that is not really nice, since it breaks the flow of the reactions.
At any rate, I think we agree on all the trade-offs.
And typing the multiplication operator in HN syntax sucks :).
> or you'll overengineer a complex solution to make AB return some kind of object that can recognize the subsequent + operation etc.
> was a later edit, I don't remember reading it when I first reacted to your comment. If so, doing that is not really nice, since it breaks the flow of the reactions.
I'm honestly not even sure. I had this in mind from the beginning, but I may have initially had a different vaguer comment about it and edited, I'm not sure. I sometimes edit comments right after posting them and then don't leave a note like I did for the later edit of the *s, since I assume no one read them. If this happened, I apologize.
Either way, yes, I think we are mostly in agreement. I'm not actually a big fan of Go's minimalism, so I actually see the value of such constructs.
https://en.wikipedia.org/wiki/Planar_ternary_ring
If you look at the section to do with geometry, you'll see that Fused Multiply-Add T(a,m,c) is the value of y given x where y=mx+c, where the latter is clearly just a line that's not parallel to the y-axis. You may thank me now.
That said, Go sticks to the most basic operator implementation (strings, numbers etc...). Go doesn't support `+` on slices for instance.
E.g. Dual numbers for autodiff; 2x2 matrices of quaternions for texture mapping; matrices of differing floating point types for machine learning. Why should I implement the same matrix operations a million times over?
Think that one through again
I am.
Also, this would be trivial with a DSL and a preprocessor, so...
I started go 6 months a go and while I like the language I hate the team. There's so much emphasis on making everything elegant perfect, organized and clean and the way go is designed just doesn't fit well with that. I feel like I'm working with a giant java program.
Looking at the libraries I feel this is the go culture. Elegance over simplicity seems to be the philosophy and that leads to java like programs rather than c like programs.
I personally liked the language but the ecosystem and the culture around it I'm not particularly a fan of because it's so at odds with the design of the language.
Funnily enough many years back before generics I didn't feel this way about the language. Now I do, so it might be that?
I get a little of what you're saying, I wouldn't say I hate anyone, but I strongly dislike how a lot of projects are organized. I think a lot stems from https://github.com/golang-standards/project-layout , which pretended to be standard and was so (ab)used one of the creators opened an issue about it. If you look at the actual Go src, it's much, much cleaner.
Can you link to where you have interacted with them personally, so that we can judge whether your hatred is justified?
Your opinion here is just bizarre because typical Go code is as far away from typical (bad) Java code as you can get.
Your typical Go library doesn't have FactoryFactory classes, doesn't overuse interfaces, doesn't have dependency injection, doesn't have callstacks 20 frames deep etc.
So yeah, I would like to know which Go libraries specifically you're referring to because I just don't see that in the popular Go packages.
Some people need to justify their salaries with the complexity for nothing.
To understand idiomatic go, it is better to look at its own standard library source codes.
I feel this way about Rust. Great language, fantastic tools, but the surrounding culture is really off-putting.
You can disagree with the parent (and I do), but it's not like you need to directly interact with a team to dislike them if you disagree with their public statements, actions or controversies.
Hate was too strong of a word and that was my mistake.