I've not watched the entire video yet; but at least this developer can say he's extremely happy with go and the ecosystem as it stands right now.
It's nicely boring, I've had very few surprises. Coming back to services that haven't been touched in a year, and they compile and deploy without needing a lot of pain.
Performance is good enough, it's not the intellectual masturbation of Scala or Rust.
I was with you until you slagged on the other options. Rust and Scala are designed for other use cases than yours. That doesn’t trivialize their worth.
Scala is a bit of a journey in intellectual masturbation. Rust is complicated as a side effect of safety I feel. It's not trying to be smart but it appears that way for safety and zero cost abstractions.
Depends. Scala can be used as "Java+" with heavily relying on OOP and side-effects, it isn't like Haskell where FP is the hard default.
Rust is actually a very well designed language that takes the best ideas from FP (e.g. error/null handling wtih ADTs) where something like Go is stuck in the 80s without any sensible reason as to why.
The borrow checker and everything related to it is another matter but it actually solves the biggest hurdle in using e.g. C or C++ which are memory related bugs without any additional performance costs like with a garbage collector.
> Rust and Scala are designed for other use cases than yours
This has become a sort-of gotcha for any discussions that involve comparing programming languages, but I think it has been over-extended beyond what is reasonable. It is true that some languages are much better at certain use cases than others, and especially if you consider their ecosystems when making this determination. For example, I don't feel Go makes a very good programming language for desktop applications or games, and there are some use cases where it is either not a good idea or flat-out non-viable due to limitations of its design.
However, I think the complaint of "intellectual masturbation" is definitely not invalid on these grounds. That's a far more general (and vague) complaint about language design. It's one thing to disagree with this assessment, but this counterpoint doesn't really address the assessment, it's just dismissing it.
That said, I do think that complaining of "intellectual masturbation" is still rather shallow. I think that if you were to compare the personalities of people who prefer Go vs people who prefer Rust you would find that the language design choices are not just intellectual masturbation, but they do reflect the priorities and mindsets of the people who design and use the language.
And that said, I think you could also make the argument that Rust has made some very costly decisions in the language design department to uphold its ideals. This does hit me when using Rust: It's not that there's anything wrong with it, but it absolutely 100% makes me appreciate where Go decided to cut the scope. Flat-out ignoring invalid UNICODE filenames may mean that Go is not suitable for programs that simply must deal with bag-o-bytes filenames gracefully, but on the flip side it sure does simplify just about every downstream decision regarding filenames. You can see this pattern repeat throughout the language. In some cases Rust goes through great pains to meet the ideals of those who love the zero-cost abstraction, but it does hit some awkward points. A point that I am always bringing up is the lack of the ability to directly allocate heap memory in safe Rust. With features like const generics, it's tempting to reduce indirections and flatten fixed-size buffers into your structs and so forth, but it's very easy to find yourself in a stack overflow if you do so, depending on the platform's default stack size. OTOH, I appreciate that in Go you don't really need to worry about a stack overflow, but Go's design decisions around the stack and goroutine do add some overhead to making C calls and stack allocations, which is a vastly different trade-off than Rust makes.
I'd say Go programmers can be categorized by the fact that many of them, myself included, actually like this pattern:
In Rust, you'd no doubt prefer to use one of the many error crates that cut down the boilerplate (somewhat, anyway) and let you use strongly-typed enums for your errors. I know I do this when using Rust, but the Go mindset is very different.
I think both mindsets have value, and it's not surprising that people see Rust as intellectual masturbation to some degree, whereas people see Go as mindless boilerplate a la Java. Both assessments are too simple, of course. Rust feels like it will pay as much cost as it takes in the language complexity department to solve the problems they want to solve as fully as they can. Meanwhile, Go feels like it will spend several years trying to decide if adding a single major language feature is really worth the added complexity, and I think there's a zen to that. I don't think the answer is that they're built for different use cases, and their use cases freely overlap plenty; it's more like, they're built for different mindsets, and some of the use cases just wrap a...
I have not written a single python script since I learned Go in 2015. But I still prefer Java for work. Dealing with databases is just completely asinine in Go.
For anything under the "analysis of data" heading, a REPL borders on essential. So many of my quick scripts are ideas I have that involve taking some freely available data and turning it into something else. Some of the steps in this process can take minutes (rarely, hours). You don't necessarily want to save every intermediate product of your work, and you don't want to have to rerun the program from the top every time you change a line. REPLs solve this problem in a way that is really great to work with.
They also solve the problem of needing to hold the standard library in your head. When I'm working in Python, and I can't remember if the method to check whether a string has a particular prefix is `beginswith` or `startswith`, if I'm working in a REPL it's just a tab-complete away. In a language without a REPL, the solutions are "look it up online", "try both and see what works", and "you are using an IDE, right? RIGHT?"
It’s also really gross how we want ultra prescriptive tools that force you to write things certain ways because we can’t trust our own coworkers to write decent code. Or learn things for that matter. I guess people don’t do code review at all?
This was tried before and it was called Java.
But there’s always an eager generation of naive programmers willing to believe the lie that this time is different.
I'm sure this depends on engineering culture, but at Google, where Go was born, there is "one way to write C++" and that is dictated by the style guide.
You have to learn many restrictions, a number of which feel arbitrary/highly subjective, and follow that. Of course, the style guide evolves over time so it's not like legacy code is consistent with new code.
The end result is that I much prefer the Go way of having fewer options in the language, instead of having those options in the language but restricting them via policy.
It's more what you can afford to learn. Most of the domains I've worked in are so thick to grok that also loading in a programming language like scala or rust becomes intractable.
experienced programmer often wants to learn just enough to take project from point A to point B as decided by business needs, and not spend nights of debugging cool stuff.
I've used scala and erlang professionally; I have nothing against learning new languages.
My comment was not really meant as seriously as the commenters here are taking it (I really didn't mean to infer anything about users of those..) -- but I also sort of stand by it -- I spent lots of time in stupid meetings about language features when I used those languages that I would have rather avoided.
It's of course not the languages fault, and more likely a smell of an org problem at wherever it was I was working back then (201x's).
It's completely true that I don't know rust; I tried, and $dayjob does use it for some components but it seems to be really overloaded with syntax and symbols to me -- then again I'm a dinosaur who started on C and maybe I'm just getting old....
This is just a personal opinion. No offense intended if you disagree.
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?
By team I mean team culture and general go culture. And I don't mean attitudes or treatment. I mean design philosophy. Just to be clear. No links just look at some go libraries. Many libraries feel like it's designed for java.
I have used lots of Go libraries. I even wrote some.
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.
To be honest, I saw a plenty of factory of factories in go on top of dependency injection systems with everything is an interfaces nightmares for simple libraries.
Some people need to justify their salaries with the complexity for nothing.
Maybe hate the team is strong, but there's excellent tools that have a really offputting team around them.
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.
Your comment makes it look as if you're saying you hate the Go development team, but it seems that isn't the case.
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.
Lack of support for operator overloading is a bit sad. As a result, Go can't get used in numerical applications, unless you like writing `matrix_plus(A, B)` instead of `A + B`.
Is there a programming language that has a "simple" design like Go but which supports some operator overloading?
I mentioned this before, but I wish that Go has a Numeric interface where operator overload methods can be added and confined only for numerical objects.
I'm really not a fan of small qol features like this. I think it really hurts readability for the general audience. The function itself is minimal overhead and is clearer to the reader.
The world seems like it's moving to ML. That needs matrices, complex numbers, autodiff, different fixed/floating point types. All four of these need operator overloading.
Autodiff requires interpreting the same piece of code using "dual numbers" that would normally expect "real numbers". This needs operator overloading combined with generics.
Your last sentence seems fishy to me. Are you suggesting that support for C++ like operator overloading is a necessary condition for, say, autodiff? I thought Go is a Turing complete language.
You might want the same piece of code to work correctly under different numerical types. Answer: Op overloading + generics.
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?
Operators for primitive types are fine, they are well defined by the language and consistent across programs. Operator overloading is going to change behavior between programs which puts extra cognitive load on the reader.
That said, Go sticks to the most basic operator implementation (strings, numbers etc...). Go doesn't support `+` on slices for instance.
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.
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.
Yes that's the plus of using a language that supports every single paradigm ever invented. I hear that they'll add S-expressions in the next version. [1]
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.
Plenty of people are choosing Go because they neither have nor want the C++ cake. The OP was explicitly looking for a simple language, and C++ is the very opposite of that concept.
So yes, you can have efficient nice abstractions, but only at the cost of an extraordinarily complex base language.
They don't, but require significantly more machinery in the language than just operator overloading (especially if you want it to work at compile time).
Sure. But somebody writes it once and you can benefit from it. If you don't have operator overloading there isn't any chance of doing this.
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.
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.
How can you? Genuinely asking. You can overload operator + or operator *, but how could you make a dedicated ternary (three-way) operator that combines those two operations into something like std::fma? I guess you could overload the function call operator, but that doesn't offer much over just defining a function.
You can have the result of * not be the actual multiplication, but a MultiplicationExpression. Then, the result of MultiplicationExpression + Matrix is a MultiplyAddExpression.
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+").
That makes a lot of sense, thanks. I hadn't even thought about that. I guess you could also have MultiplicationExpression have an implicit coercion operator so you can use it in places where a matrix is expected, which would just compute the multiplication without any corresponding addition. That's pretty interesting.
You define operators on your e.g. Vector types to construct a type that represents lazy evaluation of the expressions and reflect the operations at the type level. So for a * b + c you would get a type like Add<Mul<Vec, Vec>, Mul>.
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.
Do fused multiply-add operations for matrices really matter? There are two relevant cases I could come up with.
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.
I think the right answer to your question would be NimLang[0]. In reality, if you're seeking to use this in any enterprise context, you'd most likely want to select the subset of C++ that makes sense for you or just use C#.
I once had a hard time finding a bug, and it turned out someone had overloaded assignment to the boolean member dbIsOpen in some inherited monstrosity class to open/close a database connection. Since then, I prefer functions with clear names. They may still not do what their name says, but it doesn't lull you into thinking local.
Zig also does it this way and I think it makes sense. Operator overloading gives the same symbols different semantic meaning in different contexts. This hurts readability and simplicity.
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)
Very well said. As someone who rarely touches Go (only used it for a couple of simple web servers, something like a WebSub subscriber), you've named much of what I like about it. I'd love to see more languages achieve all these features, or even make that a goal.
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.
Absolutely. The documentation is first class, and I took love how easy it is to jump into the source. Often, reading the source helps me understand how a package is meant to be used.
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.
74 comments
[ 4.4 ms ] story [ 163 ms ] threadIt's nicely boring, I've had very few surprises. Coming back to services that haven't been touched in a year, and they compile and deploy without needing a lot of pain.
Performance is good enough, it's not the intellectual masturbation of Scala or Rust.
I hope to continue using it in future projects.
Both can be true.
Rust is actually a very well designed language that takes the best ideas from FP (e.g. error/null handling wtih ADTs) where something like Go is stuck in the 80s without any sensible reason as to why.
The borrow checker and everything related to it is another matter but it actually solves the biggest hurdle in using e.g. C or C++ which are memory related bugs without any additional performance costs like with a garbage collector.
This has become a sort-of gotcha for any discussions that involve comparing programming languages, but I think it has been over-extended beyond what is reasonable. It is true that some languages are much better at certain use cases than others, and especially if you consider their ecosystems when making this determination. For example, I don't feel Go makes a very good programming language for desktop applications or games, and there are some use cases where it is either not a good idea or flat-out non-viable due to limitations of its design.
However, I think the complaint of "intellectual masturbation" is definitely not invalid on these grounds. That's a far more general (and vague) complaint about language design. It's one thing to disagree with this assessment, but this counterpoint doesn't really address the assessment, it's just dismissing it.
That said, I do think that complaining of "intellectual masturbation" is still rather shallow. I think that if you were to compare the personalities of people who prefer Go vs people who prefer Rust you would find that the language design choices are not just intellectual masturbation, but they do reflect the priorities and mindsets of the people who design and use the language.
And that said, I think you could also make the argument that Rust has made some very costly decisions in the language design department to uphold its ideals. This does hit me when using Rust: It's not that there's anything wrong with it, but it absolutely 100% makes me appreciate where Go decided to cut the scope. Flat-out ignoring invalid UNICODE filenames may mean that Go is not suitable for programs that simply must deal with bag-o-bytes filenames gracefully, but on the flip side it sure does simplify just about every downstream decision regarding filenames. You can see this pattern repeat throughout the language. In some cases Rust goes through great pains to meet the ideals of those who love the zero-cost abstraction, but it does hit some awkward points. A point that I am always bringing up is the lack of the ability to directly allocate heap memory in safe Rust. With features like const generics, it's tempting to reduce indirections and flatten fixed-size buffers into your structs and so forth, but it's very easy to find yourself in a stack overflow if you do so, depending on the platform's default stack size. OTOH, I appreciate that in Go you don't really need to worry about a stack overflow, but Go's design decisions around the stack and goroutine do add some overhead to making C calls and stack allocations, which is a vastly different trade-off than Rust makes.
I'd say Go programmers can be categorized by the fact that many of them, myself included, actually like this pattern:
In Rust, you'd no doubt prefer to use one of the many error crates that cut down the boilerplate (somewhat, anyway) and let you use strongly-typed enums for your errors. I know I do this when using Rust, but the Go mindset is very different.I think both mindsets have value, and it's not surprising that people see Rust as intellectual masturbation to some degree, whereas people see Go as mindless boilerplate a la Java. Both assessments are too simple, of course. Rust feels like it will pay as much cost as it takes in the language complexity department to solve the problems they want to solve as fully as they can. Meanwhile, Go feels like it will spend several years trying to decide if adding a single major language feature is really worth the added complexity, and I think there's a zen to that. I don't think the answer is that they're built for different use cases, and their use cases freely overlap plenty; it's more like, they're built for different mindsets, and some of the use cases just wrap a...
Well unless it's just a quick script, in which case Python will do just fine.
Tried Scala while learning Chisel and hate it from the bottom of my heart. It's like Kotlin on bad drugs.
My point is, people are different.
Weirdly Go has made me enjoy Python even less and compilation is so fast it's turned into my go-to for quick "scripts."
They also solve the problem of needing to hold the standard library in your head. When I'm working in Python, and I can't remember if the method to check whether a string has a particular prefix is `beginswith` or `startswith`, if I'm working in a REPL it's just a tab-complete away. In a language without a REPL, the solutions are "look it up online", "try both and see what works", and "you are using an IDE, right? RIGHT?"
As we know, any technology that would require experienced programmers to learn something is a crime against engineering.
This was tried before and it was called Java.
But there’s always an eager generation of naive programmers willing to believe the lie that this time is different.
You have to learn many restrictions, a number of which feel arbitrary/highly subjective, and follow that. Of course, the style guide evolves over time so it's not like legacy code is consistent with new code.
The end result is that I much prefer the Go way of having fewer options in the language, instead of having those options in the language but restricting them via policy.
My comment was not really meant as seriously as the commenters here are taking it (I really didn't mean to infer anything about users of those..) -- but I also sort of stand by it -- I spent lots of time in stupid meetings about language features when I used those languages that I would have rather avoided.
It's of course not the languages fault, and more likely a smell of an org problem at wherever it was I was working back then (201x's).
It's completely true that I don't know rust; I tried, and $dayjob does use it for some components but it seems to be really overloaded with syntax and symbols to me -- then again I'm a dinosaur who started on C and maybe I'm just getting old....
Rust is the most pragmatic language out there.
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?
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.
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.
Is there a programming language that has a "simple" design like Go but which supports some operator overloading?
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?
That said, Go sticks to the most basic operator implementation (strings, numbers etc...). Go doesn't support `+` on slices for instance.
Think that one through again
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
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.
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.
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.
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.
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+").
The evaluation then happens as assignment or Vec construction when you have something like:
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.
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.
[0]https://nim-lang.org/
I am.
Also, this would be trivial with a DSL and a preprocessor, so...
https://en.wikipedia.org/wiki/CLU_(programming_language)
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.