Less is exponentially more (2012)(commandcenter.blogspot.com) |
Less is exponentially more (2012)(commandcenter.blogspot.com) |
> ...[rant about 'inheritance' and 'subclassing' and 'hierarchies']...
> If C++ and Java are about type hierarchies and the taxonomy of types, Go is about composition.
How do you get all the way through designing a programming language without realizing how incredibly wrong-headed this is? "Inheritance hierarchies are bad", so... you exclude generics (which are compositional, and have nothing to do with inheritance), and only support subtyping polymorphism via 'interfaces' (ie. inheritance)? That is the exact opposite of his claimed principles!
(And then as a minor concession to programming usability, he adds generics anyway, but only for built in types (maps and arrays), so advanced data structures are still an exercise in void* wrangling.)
But that has nothing to do with having an expressive type system and generics.
We can look at Rust for an example. It's "OO model", such as it is, is fairly similar to Go's. You can define a thing that combines data and behavior. You can define traits/interfaces that objects can implement. You can use those trait/interface types in place of concrete types. But Rust also gives you generics, sum types, and pattern matching. IMO, that makes for much cleaner code than doing the equivalent in Go.
«Rob Pike doesn’t know what type theory is or why it is useful»
Look at his latest statement on «generics» in Go: https://evrone.com/rob-pike-interview
He acknowledges that parametric polymorphism is a good thing that Go is gonna get, but then repeats his ignorant view on types by conflating type theory with shitty OOP inheritance and class hierarchies.
there was no need to rush an implementation of generics.
Then one day I had to implement the swap operation for sorting. Again. And I thought that even C's qsort was better, and WTF am I wasting my time on this half-assed language. I dumped it, tried rust, and even with its slow compile times I couldn't be happier.
Now a few releases later, they fixed sort so I can only implement compare. Sorry, it's not nearly enough. Essential parts are simply missing. Exhibit A is source code generation, a clear indication go isn't enough on its own. I'm not working with an ecosystem where my human time is less important than the language philosophy. I want to express a repetitive pattern in the language, and then never think about dumb bureaucratics again.
It was a near miss, though. SOme things are clearly correct. I want to look again when they finally have generics, and some basic collections. I hope that day comes and I can give it a new chance. But until then, less was simply not enough .
This isn't Go fanboyism; I've worked with lots of languages in my career, and I still get really excited and try new languages all the time, but they all tend to optimize for nice-to-have things (such as a type system that can handle those 1-5% of use cases where Go would have you drop to interface{}) in exchange for essential things like sane tooling. Rust stands alone as a possible exception, but only since it shipped non-lexical lifetimes, rust-analyzer, and a long tail of other improvements that have recently brought its usability down into a range that is acceptable for general application development.
Not being able to package build and runtime dependencies with proper versioning is an incredible pain.
Ironically, the fact go tried to make me use GitHub as a stopgap package manager (& denied one was necessary) while they built their own was what made me run away screaming.
They've since fixed that but seeing them fail at what Perl managed in 1995 and what became standard is larger languages around the turn of the millenium in a brand new language in 2010 wasn't a great omen.
Java and C# are getting native compilation soon (GraalVM, CoreRT), and when they do, it's hard pressing to see why anyone would settle for an inferior language (golang) over mature and better designed systems.
Writing code is not where time is spent. Repeating oneself, duplicating code, is fast and easy. Debugging someones code that felt like "expressing" themselves through it however, takes time, and a lot of it.
I really have no idea why some people so often need to use generics, and similar, beyond the built-in map and slice/array, when I seemingly do not, even though I write Go as my full-time job. Sure there a lot of difference between apps/domains, but the bulk of the code is usually not that different.
But more code written equals more code to read. Adding a field to a struct means modifying all the boilerplate too. It is all to common to forget just one place. Boilerplate plus maintenance attracts bugs.
Generics are easily overused and abused, but a well placed generic can save a ton of time.
As an example, and the pnly generic I wrote in a week, today I had to generate a ton of test data programatically in java. So I wrote roughly a 3 line method
T select(int index, T... values)
and used it on each of the fields with different types like this: struct.anInt=select(i,0,1,INT_MAX);
This allowed me to quickly iterate trough all combinations of common and edge cases. If I had to specify all cases individually, there was no chance I'd exhaustively test every combinations.They're also impossible to implement and extend without generics.
If your mental model of writing a program is giving instructions to an abstract machine, generics may seem pointless. However if you're interested in representing a problem and its solution, and allowing the language tooling to translate that into an optimal list of instructions for an abstract machine - generics are pretty critical.
Yes, they were introducing even more complexity to a complex language, but how else are they supposed to incorporate better ways of doing things while maintaining backwards compatibility?
I suppose one could always just build a new language that doesn't have the historical cruft, but in this case that's Rust and not Go.
Whoa, hold it there... inheritance and subclassing are OOP, types is a different subject, right?
I wish this weren't true. It's quite damning.
It's less bad in a real-time conversation, with fast feedback about how well we're being understood. But it still hints that we don't have the concepts clear in our own minds.
From golang documentation:
"Although Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy. The concept of “interface” in Go provides a different approach that we believe is easy to use and in some ways more general. There are also ways to embed types in other types to provide something analogous—but not identical—to subclassing. Moreover, methods in Go are more general than in C++ or Java: they can be defined for any sort of data, even built-in types such as plain, “unboxed” integers. They are not restricted to structs (classes).
Also, the lack of a type hierarchy makes “objects” in Go feel much more lightweight than in languages such as C++ or Java."
After this and a few other issues I had with Go's design, I just started learning Rust. Though I hear good things about Swift these days, hopefully they will provide full support for all popular platforms in the future.
* C++, easily doable with templates.
* Python, easily doable with duck typing.
* Rust, doable, though with some restrictions. I needed to require the derivative function to return the same type as its input, rather than returning something that can be scalar multiplied and added to the same type as the input.
* Java, not possible. Adding/multiplying of built-in types can be done with + and * , but user-defined types require .add() and .multiply()
* Go, not possible. Go doesn't support generics, and I'm certainly not going to copy/paste a numeric method once for every system that I examine. Also, same issue as Java with no operator overloading.
Complexity has to go somewhere. By aiming for a simple language, Go forces the complexity to be in the developer side instead.
I find Go too limiting and definitely not offering "so much more". The author is probably right mentioning "big teams". Sure if I have whole shebang of programmers each nibbling at small particular task and appropriate budget it will work. But comparing the amount of work per developer per time I can have with more sophisticated "traditional" languages does not put Go in any good standing in my opinion.
I really like go, and I daresay it makes many things easy to implement and understand, but I do find the flexibility/lack of sophistication allows one to end up writing what amounts to improved C. Great for smaller projects, but surprisingly difficult to manage as it grows.
And in what ways was it better? Power and simplicity (at least for writing those kinds of programs).
Developers are constantly looking for new languages to fiddle with without any objective reasoning.
Second, you seem to have a strange definition of "success". Limbo was a success? Well, some people used it, and some software got written in it, and some people used the software. Not much software and not many people, though, in the grand scheme of things. Same with Oberon-2. Even if you consider those two languages to have been successes, Go is a far greater success - it's successful in a way that neither Limbo nor Oberon-2 ever were.
* I guess many people will disagree for the lack of generics (among other things), but the compiler and static types are powerful tools.
For example, in Java one can trivially create a class like `Maybe<T>` with a method `Maybe<R> map(Function<T, R> mapper)`. This can be used with any classes filling in the parameters. I believe golang is unable to express this.
https://github.com/protocolbuffers/protobuf-go/blob/master/i...
Specifically the last one of embedding an empty array of mutexes into a struct prevents that struct from being copied. This is something that is easily and explicitly accomplished in C++ by making the constructors private.
I also adore goroutines and miss them in any other language. They are really fibers and let you do light concurrency without fiddling with async constructions. Async programming is just an ad hoc way of implementing fibers, and it imposes more cognitive load because now you have two different approaches to every problem in the same language (one async, one not).
IMHO minimizing language-imposed cognitive load should be a core goal of any programming language. Human brains are finite, and programmers should spend the majority of their mental energy thinking about the problem being solved not the language.
I have immensely enjoyed the "less is exponentially more" philosophy with Go. In Go there is one or a small number of way to do something. Contrast that with C++ where there are any number of ways to do something and at least as many styles of coding. Too much choice results in complexity.
IMO the generics debate is overblown. The systems I've build with go tend toward heavy data processing (ETL, correlation, extraction and the like) and service (JSON APIs supporting front-ends; event some front ends with Go Templates). Never once was I like "my life sucks because I don't have generics." Fair and balanced: Processing JSON in Go takes some getting used to, especially when the JSON structure changes. However, there are some good libs to help. It's just a friction point given the typed nature of Go.
The operational simplicity of Go cannot be overstated. Go's production of a deployable binary with zero dependencies is a blessing. There's an amazing deploy tool you can use called: cp. Compare that to the library version hell that is C++ (and I'm also a C fan and very experienced dev). No. Thank. You.
The cross compilation of Go code works beautifully. Just 2 weeks ago I had to demonstrate a tool to a customer and due to COVID it was virtual. We had problems with MS Teams connecting to our dev platform so literally 15 minutes before the demo I cross-compiled to a Win binary and moved it to the machine hosting the Teams call. Victory.
Because of go fmt, all Go code looks the same. That's a massive advantage to read/understand/use OPC (other people's code). Ditto for the automatic documentation generation -- it looks the same and behaves the same across Go projects.
I abhor the Java ecosystem for so many reasons I just don't have the energy to go into. It's my opinion, so not up for debate. If Java works for you, by all means have at it. Robust solid technology for sure. Not hating here; just choosing.
So, my Go journey has been and continues to be fantastic. It is my preferred platform for pragmatic reasons--the same sort of reasons it was developed by Google in the first place. My team and I can get stuff done quickly. We can scale it. We can leverage all the CPUs on a box running big data processing flows. We can have automatic code-linked documentation and formatted code and can leverage the same from others. We have build in testing. We can make high performance services and servers with ease. We don't have to deal with ridiculous configuration nonsense. We get instant compilations. We get great tool support (vscode + Go for the win). We get a batteries very much included stdlib.
And most of all we get a platform that understands and managed complexity in a favorable way.
$0.02. Ok, maybe $0.05 (inflation).
The beta of the last language I wrote had maybe half a dozen fans. I threw away the compiler/language because I didn't really know how to write a compiler back then. I'm writing a new one now
https://news.ycombinator.com/item?id=16548684 (2018)
https://news.ycombinator.com/item?id=6417319 (2013)
Discussed at the time: https://news.ycombinator.com/item?id=4158865
Things that in other languages I can do in half a dozen lines of code, I find I'm writing 4x time more in go.
And I find it pretty unreadable, it's not nearly as expressive as other languages I enjoy using.
There's too many missing features I use heavily in other languages that make my life easier that I really miss them in go.
I will say I do like that its opinoinated on the formatting. Just takes away an entire tiresome argument.
I tend to conflate higher expressiveness with being "clever" until you're really proficient in the language. I think Go's main value prop is that it performs well with very little ramp up time compared to languages with more powerful features.
If you have a team of people who are really strong with something like OCaml or Scala, they'll be amazingly productive. But if you have a rotating team of engineers with varying backgrounds, it's hard to beat Go when you're talking about time-to-productivity.
Want to reverse an array? You can't just list.reverse() or list[::-1] like you'd do in Python. Want a simple lookup if a value exists? You can't ['a', 'b', 'c'].include? 'a' like you'd do in Ruby. Want to reach for low-level primitives? You'd have to delve into CGO territory, which is not always "elegant". Want different concurrent paradigms (eg. an actor model), well, maybe you're better off without it.
But it's up to you whether that kind of 'simplicity' is desirable or not.
Testing for membership in an array is O(n) in almost any language - and there's a lot of new coders who will happily call it inside a loop. My second internship, I reworked an O(n^4) method to O(n^2) for some code written in R by a senior statistician. This raised the feasible N from 4 to about 13 (in terms of what results you could get in two or three days of compute).
Google doesn't want anyone making that mistake in production, where throwing more servers at the problem costs a whole lot of money. And they hire a lot of junior devs.
I'm sure as somebody who's writing their own raytracer, you know the performance cost of this lookup intimately, and you've made the performance trade-off when picking your data structures. (E.g: maybe the include function is called rarely, or it really is just 3 items, etc). But Go is made for teams of varying skill levels, and so it takes the position that doing uncommon things should take some extra work.
I agree, goroutines (or perhaps something a little more generic that doesn't conflate stack reification with scheduling) are absolutely the correct abstraction, and this will be proven in a few decades time. But until then we're stuck with people rationalizing the limitations of async constructs in their favorite languages; constructs primarily chosen because of quirky design constraints, principally C interop and C-based VM implementations that leak the semantics of the C ABI stack.
If you can gather some energy, I'd be interested in that.
-- Compile times!
-- Operational complexity vs. Go.
-- Built-in tooling vs. Go.
-- Verbose XML configuration files.
-- Lots of libs favor XML vice JSON.
-- Deeply-nested code directories.
-- Class-centric model (only).
-- Verbosity and boilerplate.
-- Factories of factories of factories. :)
-- JVM install and config (many dials = control but also complexity).
-- Impractical / painful to write without an IDE.
-- Concurrency model, as compared to Go.
-- Std lib not matched to work I use Go for.
-- Memory consumption.
-- Performance (for the work I do).
-- Multiple inheritance (allows unnecessary complexity).
-- https://astaxie.gitbooks.io/build-web-application-with-golan... vs. https://medium.com/@ssaurel/create-a-simple-http-web-server-...
-- Oracle.
(edits: reformat pretty; compile times)
It was never about github's downtime. It was about having to manage the code of your dependencies yourself.
>you can use a caching proxy if you're really concerned about reliability
yay one more piece of infrastructure that will break.
>There are probably lots of good reasons for why the GitHub model is inadequate, but for whatever reason these aren't brought up in the aforementioned scoffing
I really think they were.
Had Go been created at Bell Labs or ETHZ, and it would have shared the same fate as its influences.
You missed the sarcasm on my comment.
But I disagree with you. Bell Labs was adequate branding for C and Unix, but not for Limbo or Plan 9. Sun's branding (and relentless support) was necessary for Java, but who did the same for Python, Perl, or Ruby?
It's not just the branding or the marketing. Sooner or later, how well the language enables you to write programs has an effect on language popularity and uptake.
Programmers aren't just fashion-driven sheep led around by marketing departments. They actually use the languages, and they can in fact tell when the language makes things easier or harder.
Ruby, mostly unknown until Rails.
Perl, got integrated early on on UNIX as the standard IT tool.
Don't confuse "they came to a different conclusion than me" with "they're sheep".
Often enough to account for the popularity of Go? I strongly doubt it. It could have accounted for Go's popularity in the first year or two. But now, Go has been used by too many people to do too many things. Go's popularity now is based on its ability to help you get things done, not on Google's name.
If you really can predict the future please tell me lotto numbers rather than being abrasive.
All of the original creators were clearly of the "best tool for the job, even if you have to build it from scratch" persuasion. How many different languages and programming constructs can you find on Unix or Plan 9? The notion of centralizing all development around a single all-purpose language, a la C++ or Java, is anathema to their stated engineering principles and the opposite of the design elements of the many computing environments they spent their careers designing and using.
One can quite easily disagree with their design choices and tradeoffs. I find static compilation short-sighted. But I do appreciate how it fits into their overall approach; and in that way I find static compilation less short-sighted in Go than in Rust. Some aspects of goroutines are problematic. Hassles with dealing with timeouts exposed flaws in their scheduling and message passing abstractions. But those flaws are in the details, not the fundamental model; and the better way to implement those details is more difficult to determine. (I've had to wrestle with such questions myself as I've written multiple async I/O libraries and frameworks. If Go had come out 10 years earlier my entire career trajectory may have been different.)
FWIW, I don't write any Go software. I admire it from a distance, the same way I admire C++, Rust, and Java[1], while I slave away in C and several other languages. Though, there's an honesty and clarity in Go that is lacking in other languages, precisely because Go isn't trying to be an all-purpose language, and because the authors have an understanding of (and deliberately leverage) the interplay between design goals and implementation constraints that can only come with having designed and implemented umpteen different languages before.
[1] I did have a short tryst with Java a long time ago, but it actually ended up driving me into the arms of C. Not because of the language, but because of the ecosystem--licensing, tooling, interop, etc, was an extremely poor fit for Linux, and in many ways still is.
That's the problem: burning out engineers.
Google has been developing a language to make it easier to change cogs in the machine.
The quotes from Pike are pretty clear:
http://nomad.uk.net/articles/why-gos-design-is-a-disservice-...
golang compile times are similar for any non-trivial project. Ironically, they're even much worse than Java's for quick changes (e.g. change a single file then recompile to run tests) since golang has to spit out a binary that is in the 10s of MBs compared to Java which just needs to change a single class file.
> -- Operational complexity vs. Go.
The JVM is superior for operations, observability, and monitoring to anything that golang has to offer.
> -- Verbose XML configuration files. > -- Lots of libs favor XML vice JSON. > -- Factories of factories of factories. :)
Nothing to do with the language.
> -- Deeply-nested code directories.
I've seen the same in golang code due to its packaging.
> -- Verbosity and boilerplate.
golang is more verbose and error prone for any non-trivial code base
> -- JVM install and config (many dials = control but also complexity).
The JVM is configurable and gives the user the ability to tune his program (say latency vs throughput), unlike golang which doesn't. Secondly, you can compile to native code using GraalVM.
> -- Concurrency model, as compared to Go.
Java is getting green threads (see project Loom), and they'll be superior to what golang offers.
> -- Impractical / painful to write without an IDE.
Irrelevant for any non-trivial code base, since golang needs and IDE as well.
> -- Std lib not matched to work I use Go for.
let's see what golang has to offer anything remotely similar to java.util.concurrent.*
> -- Memory consumption.
Depends on how you set up your JVM (Xmx, GC settings, etc.). Java is getting value types soon to address this even more.
> -- Performance (for the work I do).
They're on par, and for any non-trivial code base, Java tends to be faster.
> -- Multiple inheritance (allows unnecessary complexity).
Java doesn't support multiple inheritance.
That's fine but pretending you're an SME and throwing off false equivalencies is really dishonest.
The JVM is superior for operations, observability, and monitoring to anything that golang has to offer.
>> Disagree on operations part. Take a bare machine and with various configurations (some you don't control) and be ready to drop and run. Nothing beats Go. You cannot be simpler than copy a binary. Yes, the JVM has all sorts of observability. And also a load of tuning that often needs to occur. Go just runs and generally has outstanding performance.
> -- Deeply-nested code directories.
I've seen the same in golang code due to its packaging.
>> Go code is generally not as deeply nested. Often you have <project>/<package>/<file.go>. Not stuff like:
tomcat/java/org/apache/tomcat/websocket/server/xxxx.java
> -- Std lib not matched to work I use Go for.
let's see what golang has to offer anything remotely similar to java.util.concurrent.*
>> Friend, I'm talking about all the packages here: https://golang.org/pkg/
> -- Memory consumption.
Depends on how you set up your JVM (Xmx, GC settings, etc.). Java is getting value types soon to address this even more.
>> Yes, mu point (Xmx, GC settings, future things). Many dials == complexity.
> -- Concurrency model, as compared to Go.
Java is getting green threads (see project Loom), and they'll be superior to what golang offers.
>> More Java getting/future vs. Go has today. "they'll be superior to what golang offers." Since we are predicting the future, can I also get next week's S&P500 level for my trades?
>> Let me know when you can remotely close to writing <200 LOC that can handle 1m websocket connections from a laptop. There are 2 source files here. https://github.com/eranyanay/1m-go-websockets/tree/master/4_...
Again as I opened with, these are my experiences. If Java and the future versions of Java and the tolling, IDEs, CI/CD tools, inspectors, etc, work for you. Great. Have at it.
I will keep deploying rock solid Go applications with minimal complexity and maximal performance/reliability, out of the box. I shall enjoy flatter code trees, working without a IDE when necessary (totally possible), enjoying simple make files for builds, and solving a ton real world problems with minimal external libraries (again, dependencies ~= complexity ~= risk).
Checkout some great stats from the 2019 Go Survey here: https://blog.golang.org/survey2019-results
I will depart with a final thought about Go: Why are increasingly more and more web-scale infrastructure projects being written in Go? (CockroachDB, Openshift, NATS, Docker, Istio, Etcd, Docker, K8S, large parts of AWS, Azure, and CGP).
All those projects are related to Docker and k8s, so naturally get written in Go.
Thankfully it is also possible to use Rust, C++, Java and .NET with them without writing a single line of Go.
As per the creators of the language who claimed that they got the idea of writing a new language while waiting for C++ to compile, it was. You can read up about this from them, they were quite clear about it. They even expressed surprise as to why they didn't get the migration of C++ programmers they were anticipating to golang, but instead got Python programmers.
It's not like the lack of green threads stopped people from writing highly concurrent code. Libraries like Vertx, Reactivex, and Akka have existed for a while now. Also, all major platforms have a notion of green threads/coroutines (Java is getting the former and C++ is getting the latter). This was practically the only thing golang had going for it, and once they're implemented for those platforms, it's hard to make a case for golang given it's bad design decisions.
> Hassles with dealing with timeouts exposed flaws in their scheduling and message passing abstractions.
That's a good point, which is why Java's green thread implementation is superior to golang's. Timeouts and cancellations are designed in from the start, as well as hierarchies of green threads (similar to Erlang's).
Again, it just show's that golang does the minimum, and pushes complexity onto the user under the guise of simple language design.
> licensing, tooling, interop, etc, was an extremely poor fit for Linux, and in many ways still is.
The JVM is open source and has been for a while now. Could you elaborate on why it's not a good fit for Linux?
So the future invention of something that doesnt currently exist is going to be better than something that does exist is your argument?
* https://www.baeldung.com/java-flight-recorder-monitoring
* https://docs.oracle.com/javase/tutorial/jmx/overview/javavm....
If we're comparing language exposed we've got pprof which can profile and generate flamegraphs etc all part of it as standard that blows your argument away IMO [0]
golang is not a magical solution that doesn't have problems. It has its fair share. Its gc is subpar for throughput purposes for instance.
pprof is different. JFR provides live streaming events with minimal overhead. Regardless of what language you use, this feature is invaluable. It's not just for performance, but for debugging, monitoring, and tracing.
Here’s some pretty detailed notes from Google about one high volume project they rewrote from C++ to Go.
https://talks.golang.org/2013/oscon-dl.slide#1
Use whatever language you like. Just because you like X and someone else likes Y does not invalidate either language.
And to you totally off base comment about cargo cult of web scale and ‘pump up their CVs’ .... in my case I happen to work with huge data (petabytes) so yeah it matters and has nothing to do with FAANG. In this context I have found Go to be an insanely great tool—-productive, reliable, maintainable, multi-core, networked and fast. Thankfully I will never have to write a line on .NET.
Apparently the Android team did not adopt gomobile the same way.
Also apparently having Go on Fuschia is not well seen and it will eventually be replaced by a C++17 or Rust implementation.