I love programming languages. And I love to reason about them. When I see an article comparing languages, I'm all in.
So I clicked on this one. Started skimming through it, looking for code. Found none. Came back here to share my disappointment.
I'm always confused about articles that talk about code and have little code to show. Are there programmers who enjoy reading them? Or are these articles made for non-programmers?
Another thing that always throws me off is fluff. The article starts with "Choosing a programming language isn’t easy.". What does this add to the article? It immediately makes me think the author stuffed this article for no reason. So I have to skim even faster to not being cheated of my time. Is there anybody out there who thinks having the article prefixed with "Choosing a programming language isn’t easy." makes it more valuable?
That’s a bit of a straw man.
1. You are using go’s built in imaginary type, and writing one from scratch in Java.
2. That is no longer idiomatic Java. Use Autovalue and the comparison is a little more fair. The LOC and complexity still come out in go’s favor for this, no doubt.
3. Beating down on Java is fun, but with Kotlin interop and adoption rate, I think the comparison will become JVM vs X.
He even names examples he is comparing but the examples are not there; there are many links in there and I am not going to click them all... So why are those examples not there?
This article deals with "big picture" aspects, not specific syntactic details, so there is no reason for it to include code.
Backward compatibility, compilation speed, circular dependencies, small vs big specs, etc. What code do you want to show about these aspects?
> The article starts with "Choosing a programming language isn’t easy.". What does this add to the article?
It's an introduction. It's a very common writing technique. You don't start with the core content of the article, you start with a paragraph or a sentence about why you wrote the article in the first place. You did the same with you "I love programming language" opening.
Backward compatibility, compilation speed,
circular dependencies, small vs big specs,
etc. What code do you want to show about
these aspects?
Code that shows these aspects. What broke in Java because of language changes and why. How handling circular dependencies in one language makes code uglier then in the other. Examples of the specs.
You did the same with you "I love
programming language" opening.
That I love programming languages and to reason about them is an information about myself that the reader does not know in advance. That choosing a programming language is not easy is public knowledge that the readers already know. At least if they are programmers.
That is the point of my post. Asking who the target audience is. And how it is different from myself.
>That choosing a programming language is not easy is public knowledge that the readers already know. At least if they are programmers.
Well that bit is certainly not true; passing by certain communities and you’ll be let known theres only one good answer: {lisp,c#,haskell,rust,js,c/++}
- The article focuses on the properties of codebases, not individual code snippets. While it would be very valuable to do compare the properties of entire codebases, that is demanding on both the reader and the author.
- Opening the article with a statement like “Choosing a programming language is never easy...” is an acknowledgement of the fact that he is not claiming Go is unequivocally the best in all scenarios. The author is signaling that he is a reasonable person, and while that may seem like fluff it is a necessary component of communicating with a wide audience.
If you think that somebody writing a 6-word sentence at the start of an article to set the tone is "cheating you of your time", perhaps stop reading random articles on the internet, and then also leaving comments about them.
Yeah but I think for non-trivial programs, say 10k+ lines of code, it's really difficult to show proper examples, not to speak of exhaustive coverage. Go in particular doesn't have many fancy language constructs and a lot of well written code in Go 1 might look a bit repetitive, especially the error handling. But once you wrote a larger program and followed best practices with error handling, you might be delighted how much more compact error messages are and still offer a lot of greppable things to debug after.
Probably there are languages that have a really expressive syntax that solve problems also in a scalable manner. For instance Python with its list comprehension that are a real code and time saver for software that does a lot with lists. (No coincidence it's so popular with ML.)
I do like go, but really this arguments start to be a little unprofessional if you ask me.
All those arguments are fine and correct in small projects or libraries, there go is perfect.
But they really don't hold on big project like docker or containerd.
While it is clear what lines of code does, at a tactical level, it is extremely difficult to follow what happen in the big picture, the strategic level. Where the values comes from and where they go? When? How and why?
Honestly I keep seeing this in all big go project, despite the lack of generics.
Again if you need a small micorservice, use go! It is the best option right now. But I wouldn't bet any big codebase on it.
I think complexity in software becomes difficult to grok as software gets bigger, and go does seem to go out of their way to code easier to understand at a hilariously micro scale at a huge cost at the macro level.
Absolutely the opposite. The ability to understand Go code seems roughly constant with regards to codebase size.
On teams of enough engineers, especially as those engineers rotate in and out of maintenance roles, richer abstractions tend to obfuscate intent and hurt maintainability more than help it.
I just don’t buy it. We already work at a layer dozens if not more abstractions away from what’s really happening underneath the surface. This is essentially an argument that every layer below is good and justified, but that any additional layer is just too much.
For complex, specialized systems what I'm often seeing is that essentially every decent large system gets split into a de-facto domain specific language (no matter if it's with language features or through some specialized libraries or webservices or whatever API) and the implementation of the business logic in that DSL. And, in general, the implementation of that DSL is something like ten times smaller than the amount of code using that DSL.
The maintainability of the code is mostly determined not by how good the underlying language is but how good is that DSL. To take an example that's been widely discussed in HN, let's think about machine learning - the look, structure and maintainability of your code is highly influenced by the choice of your framework/"DSL" e.g. pytorch vs tensorflow1.x vs straight numpy vs Caffe will have major differences despite being in the "same language".
And in that regard, the features of the core language matter only in regard of whether they facilitate making a good DSL. Of course, some languages fail because their choices result in the "core DSL" i.e. standard libraries that everybody uses being fragmented and confusing; Go has it quite good IMHO for the standard library; but for every specific domain it also matters if the DSL for that domain is good, whether it's an ORM layer or a OpenGL interface or a deep learning framework or an physics simulation library - whether the language abstractions mean that these DSL's (often being interfaces/wrappers to some third-party code written in another language) are going to be good and convenient, or whether the language structures limit the DSL so it becomes unwieldy to use.
> For complex, specialized systems what I'm often seeing is that essentially every decent large system gets split into a de-facto domain specific language
Every time I've seen this, it's been a disaster. Custom DSLs for business logic are, 100% of the time, a red flag of a development process gone off the rails.
(We may work in different industries.)
Go prevents you from doing this at a very low level. This is a huge advantage.
If you're writing a React or Django or Laravel or Rails app, then what you're doing is essentially work within that DSL which is quite different from the underlying Javascript/Python/PHP/Ruby language. The same goes for data analysis frameworks such as Pandas, or deep learning such as Tensorflow or Keras - if you're working in that most of your code and calls are going to be within that API/DSL, not "pure" Python. And I'd definitely consider all of the abovementioned as successes and a good thing - if Go would prevent people from writing their Web-CMS systems in a good Web-CMS framework/DSL, or writing their machine learning systems in a good ML framework/DSL, then that'd be a serious limitation.
And it's worth noting that many (all?) of these particular systems were actually initially made as custom DSLs for internal use within a single company - Rails for 37signals/Basecamp, Pandas for AQR Capital Management, Django for Lawrence Journal-World, Tensorflow by Google Brain, etc. They were the exact thing that you describe - a custom DSL invented for the business logic that the particular company needed repeatedly. And they were not a disaster but the right thing to do in their case - mostly because they had the ability to make a somewhat good DSL though in all those cases it was an 'okayish' DSL initially and became good only through years of substantial changes. Yes, for every such case there are many bad internal DSLs. "Not Invented Here" is a common problem. You need to ensure that you're using good DSLs, and good DSLs generally gain wide adoption within that domain instead of every company building their own shoddy DSL.
I've seen a bunch of nightmarish financial systems with horrible DSLs. However, even in that case the DSL problem was that this DSL was horrible not that a DSL shouldn't have been used - any fix or rewrite would generally require replacing it with a better DSL/framework/whole-encompassing-structural-API/whatever, instead of sticking to the core programming language (which would just result in another emergent "DSL" of some core libraries/classes, which would be lousy compared to something you intentionally design to be usable).
I would say most of the examples you mentioned are actually examples of well implemented libraries, with well named methods and abstractions, rather than DSLs. If you want to imply that a good large codebase is similarly structured into libraries of independent components rather than one big class hierarchy then I would agree.
I'm probably trying to make a point that when writing code to a large domain-specific library or framework, at a certain level of intensity of coupling it de facto becomes a DSL. You're writing code using "primitive" data types, data structures and functions specific to that DSL instead of the data types of your core language; the control flow and data flow is not the same as in your core language but determined by that framework.
The differences between programming languages are smaller than the differences between DLS's implemented in the same language. Code in Rails (Ruby) and Django (Python) has more in common than code for Django and Pandas. Writing Tensorflow code in Python is somewhat similar to writing TensorflowJS code in Javascript, but very dissimilar to writing Pytorch code that does the same thing.
They're not really independent libraries, they're frameworks that suggest, influence and sometimes even mandate most of the infrastructure around your code and the structure of your code in a much more stringent manner than the core language does. There's a fundamental difference between making a few calls to a black box library versus having the content and style of most of your code being dominated by the traits of that "library" - the large frameworks overwhelm the small(ish) core languages. So I believe it's justifiable to consider the frameworks I mentioned as DSLs.
What would you need to do refactoring operations on such code?
Given that there are classes, arguments are specialized over classes, slots have types, ...
Didn't know about those type annotations in lisp, but i suppose what you mean is that an ide could parse all the code, infer the type of all the function / variables etc (just like the lisp interpreter would) and provide you with refactoring facilities ?
That's a good thing. But then comes the git merge (or any other automatic code altering tool). How do you know something hasn't been messed up in the process ? (unless you wrote unit test for every argument of every function)
Replace "DSL" with "idiom" and I totally agree with you. It's often possible to look at a code snippet and recognize what codebase it's from, even if it's doing something pretty generic. There are explicit local APIs, implicit local APIs (often associated with local data structures or stores), and local habits such as looping over items in a particular way. Managing memory, scheduling work, logging, and so on all tend to follow local rules. Those rules together are the moral equivalent of a DSL.
So you're right, for programming in the large it's very important how well the language supports creating good reusable APIs and data structures. I think that's why so many people make such a big deal about generics in Go, though personally I think it's a poor example and the complaints are tiresome. I think I'd focus more on whether the language has things like macros and lambdas/continuations that let non-API idioms be expressed consistently and conveniently, as though they're part of the language (even though I think creating a true DSL is usually a bad idea). Not sure Go meets this standard TBH.
Having been a professional Go programmer for ~5 years prior, now working in Rust full time, I actually take issue with Go for large code bases[1].
For me it's mainly due to the lack of generics and helper tools that naturally spawn from generics. Go's solution to having less LOC inside a function is _only_ to create helper methods/funcs. These helpers are bound to specific data structures and often are only applicable to that one use case in that one method that they came from. Your ability to make that helper function apply to more code depends heavily on what data structures are being used.
This form of non-generic bloat was far worse for me than the classic examples of `Fooi32()` and `FooString()`. The latter I don't mind at all.
Rust aids this process to me by, well, having generics. Converting one data structure into another becomes a breeze, and converting a Slice of those data structures is just as easy. I'll be really interested to see how Go feels once generics make it in - though I suspect I'll stick with more explicit memory management.
Speaking of memory management, it would be nice if Go had a way to intelligently indicate which structures are safe for concurrent use and which aren't. I've found in Rust that the explicit nature of the concurrent safety is not something I knew I wanted in Go. I know Go will not likely ever match Rust's safety, but if it at least indicated that something was an thread-unsafe implementation that would be golden.
[1] edit: Oh, and there is a disclaimer in this that the projects I'm involved in seem to heavily rely on managing several data structures in repeated ways. Aka, very generic friendly.. so not having generics made my projects extra painful. This is not likely the norm.
> actually take issue with Go for large code bases
This has been my experience as well. People just parrot what the golang authors say about it being a language suites for "large scale development", whatever that means, without providing anything to back it up. Just because they said it was must be it's true? In practical experience, it's a verbose, clunky, and awkward language to use for any non-trivial code bases.
> Rust is still too young to take advantage of this, but in due time it will.
I actually find Rust's standard library great. I think my only complaint so far is the lack of time handling. Luckily community packages (like Chrono, Diesel, Tokio, Hyper, Itertools) have implemented great solutions to a few missing details.
Hell, Iterators alone make my life so much better.
Go is designed to do concurrency via message passing, not shared memory. Yes, this is slower, but it also means you don't need a fancy borrow checker slowing down your compile.
instead you pay for context switches in the scheduler and suffer an order of magnitude slowdown in production. and when you profile and find the performance issue, you rewrite the portion of code to use mutexed shared memory to hit your performance targets.
Let me rephrase. Go was designed with the old best practice to share data of languages before Rust came: “you do not communicate by sharing memory, instead you share memory by communicating”. It is slower but you have to do this because Go can’t guaranty memory safety and doesn’t have system to prove that at compile time.
Then the team behind Rust after many research found that you can actually guaranty memory safety if you can track how data are used by each thread. To do this you need to track how each value is used and borrowed by each thread to insure memory safety. And so the borrow checker was born. It gives you the best of both world: speed And safety while adding only a little bit of time to the program to compile.
If Rust is slow to compile it’s not because of the borrow checker it only adds a little bit to the bottom line, but it’s because of Generics and other things. Which is also something that Go is lacking and doesn’t have yet.
> Go is designed to do concurrency via message passing, not shared memory.
... and yet experienced gophers will criticize newcomer's code for using channels too much and they will tell you you need to use traditional stupid shared memory approach instead of channels in many cases.
>Absolutely the opposite. The ability to understand Go code seems roughly constant with regards to codebase size.
That's what some Go proponents say, not necessarily reality, and there's no study about this, just hearsay.
I find Go hurts readability for larger programs because it prevents many structural abstractions that make code more clear and concise (as opposed to mere fancy "clever" code, which it still allows), and because it forces too much boilerplace (error handling, no generics, etc).
Every large projects contains re-implementations of the same things (from data structures to algorithms, to parallel processing handling) that could have been done once and re-used forever with generics and a little more power.
If I recall correctly one of the arguments for lack of generic was that they would make more obvious code, hence easier to follow and read. Actually it seems like the lack of generic just make more obvious code, hence more difficult to read.
The other argument was compilation speed, that honestly I cannot complain about.
> If I recall correctly one of the arguments for lack of generic was that they would make more obvious code
I don't believe that is a reason ever articulated by the core team, but a post-hoc justification by apologists. The reason the core team gave was that they had not settled on a design that would fit well with their other goals for the language, and it simply didn't make the short list for 1.0.
>While it is clear what lines of code does, at a tactical level, it is extremely difficult to follow what happen in the big picture, the strategic level. Where the values comes from and where they go?
Compared to python or Java? Well written python basically resembles Go. And most Java I've come across tries to spread logic across class boundaries so you never get to have all the code in front of you at one time.
Definitely Java has a very similar problem, of having so much rote boilerplate in every single class that its hard to see essential deviations from the pattern that you need to understand in order to follow the code. I have not ever seen this problem in a Python code base. It is not littered with "if err != nil" for one thing, but Java has exceptions too and still has this problem.
I attribute it more to a static type system that lacks features I've grown use to in modern languages. Most of my experience in Java is 1.6 and prior so its possible code bases have improved since then.
And yet, people are building big code bases on top of both C and Go without generics all the time. Like C, Go lacks support for elaborate abstractions. It's still a better C for many problems.
I know quite a few who would consider that an option.
Embedding a higher level language that's controlled from a more primitive host language allows solving many otherwise tricky problems without the struggle.
I took a quick look at what these are. Couldn't they implemented with macros like (this builds a type "Utf8String" which "depends" on the value of the "ENCODING" parameter to figure out what set of functions to call for the guts of the functions that handle the type, e.g. RawStringUtf8Length):
It isn't exactly pretty, but seems to do the work. The ENCODING parameter is only conceptually an Encoding enum, but this can be enforced with some dummy (unused) code that does use of the ENCODING value as Encoding (and if extra strength is needed, as enums are weakly typed, instead of native enums, you could define macros that create stronger "enums" out of structs).
You can write Fortran in any language, these people are missing the point. The simplicity is a feature, it also allows you to say exactly what you mean without sweet talking the compiler.
Well, your list does include three practical options. Using interface{} is the go-to option and usually works fine.
The overhead of interface{} isn't as high as people sometimes think. You have the overhead of boxing the value, but the actual type checking is just a single comparison and jump.
Most of the time it won't actually be "too much". The comparison itself has very little overhead due to branch prediction.
But yes, if you're writing performance critical code (which you generally won't be), then you'll need to use code generation to avoid boxing.
This kind of discussion is pointless without some kind of real world reference point. The vast majority of Go projects will never run into a situation where the overhead of using interface{} is a performance bottleneck. If you're working on a project where you consistently have to think about the cache performance of your code, then C++ or Rust would probably be better choices. Go isn't meant to be all things to all people.
If you want a real-world example: I struggle with my SAT solver, gophersat[1].
I have different kinds of constraints, mainly propositional clauses (a or b or c) and pseudo-boolean constraints (2a + b + c >= 2). Long story short, a propositional clause is a special kind of pseudo-boolean constraint, so I can process my clauses as if they were regular pseudo-boolean constraints, but this is slower than using dedicated code.
Lots of problems only deal with propositional clauses, so they take longer to solve than they should, because I cannot say "this is a problem with only propositional clauses", vs "this is a problem with all kinds of clauses". I could use interfaces, and tried, but this is way slower, to the point it doesn't make sense to use them.
And I can't use C++ or rust for this problem: there are already tons of very good c++ SAT solvers, but my goal was to provide a SAT solver I can use in my go programs without resorting to cgo black magic.
"This is way too much in a tight loop. I want my structure to fit in the cache and I want to have as little tests as possible."
If that is a hard requirement, then the answer is just that Go isn't a suitable language. AIUI the current generics proposal in Go, it won't fix that problem for you either.
Where I disagree with the programming community at large is how often that's actually a hard requirement. A lot of programmers grossly overestimate their performance requirements on the microscale [1], and so concerns about "premature optimization" remain alive and well decades later. However, there are undeniably still a non-trivial number of cases where that really is a hard requirement, in which case the answer is, don't use Go.
(Preferably, figure that out before you start. As a fairly heavy Go user, I actually kinda cringe when reading one of the several "We sped up Go by 10x on our particular big thing and got to barely-acceptable performance" articles, rather than consider it something to be celebrated. I mean, cool that you sped it up that much and all, but honestly the correct answer was to notice in the design phase that you were doing something you probably shouldn't have done in Go, modulo concerns about easy prototyping and such.)
[1]: I qualify with "microscale" because I also think a lot of programmers underestimate the performance requirements on the macro scale. But that's actually a very different issue. There's a lot of very sluggish and slow code out there in the wild that is architecturally wrong; at every individual step the code is reasonably optimized and fast, the problem is having the wrong steps.
the term « boxing » was very often used before C# had proper generics, and the same goes for java when Object was used everywhere ( priori to generics). Then generics came in an everybody talked about type safety and performance gains. Then 10 years later horrendous over-complexified designs started to cripple code bases all over the world.
I honestly wonder if go isn’t cursed to follow the same road.
"Boxing" just means storing a (possibly tagged) pointer to a value rather than the value itself. It has nothing specifically to do with generics.
But yeah, I share your concern. It would be nice to have a better way to write container types in Go, but generics always seem to end up getting abused.
My Java days are long gone, but modern C# code is not overly complex and, if you use fp principles where you can, even really quite simple to read. Ofcourse you have OOP design pattern addicts who write hard to follow overarchitected stuff, but your language cannot prevent that; people write convoluted stuff in any language.
Blaming just generics for that does not work imho; in Java people were writing really insanely over complex code close after the first release; I had an ex C++ guy before 2000 writing Java code that used every design pattern in The Book, everywhere he could and it ended up with 1000s of files and those strange long names that became the standard for Java a few years later.
> The overhead of interface{} isn't as high as people sometimes think.
The cognitive overhead certainly is. Computers exist to help us do things faster and with fewer mistakes. This is a prime example of using a computer to help us make more mistakes, worse.
Yes, using interface{} introduces a small chance of runtime type errors. I have not heard of any Go projects finding this to be much of a problem in practice. You can also use the type-safe wrapper pattern to avoid this if it's likely to be a problem.
There is no doubt that not having generics makes some code worse than it would be with generics. But I think the problem is often overstated.
In C you'll content yourself with a library for operations on long, long long, or void*.
But if you are working on a project where the details of the tree do not matter (so you can use a library), you will probably use C++, not C. I still have a hard time understanding what niche Go fills, it's too high level to compete with C or Rust, but here we are comparing its expressiveness with C.
The question of intrusive containers is kind of orthogonal. You can implement intrusive containers using generics. Aslo, kernel code would use intrusive containers even if there were easy-to-user non-intrusives available directly in C. The reason, of course, being that with intrusive containers you can avoid huge amount of memory allocations.
The Linux kernel is the last place anybody should go to find simple, clean interfaces. That's just not how Linux rolls--almost everything else has a higher priority. The classic intrusive data structure in C is the link-list as implemented in BSD (though I'm sure it predated BSD): https://man.openbsd.org/queue#LIST_EXAMPLE
A red-black tree implemented in the same simple style by an OpenBSD developer a couple of decades later was adopted almost overnight by all BSDs: https://man.openbsd.org/tree#EXAMPLES
Unlike the Linux version, the BSD version is as type-safe as a C++ template, permits a node to exist in as many collections as you want (one member field per collection), and like a template the BSD version can inline the comparison function. You even have the option of making all the routines extern or static, which you can't even do with C++ templates.
What the Linux version has going for it is that it doesn't need to rely on the preprocessor so the implementation looks a little neater and more like a C++ template--no columns of backslashes as required with preprocessor-based generation. But that's a horrible tradeoff. queue.h and tree.h have basically been frozen for decades.[1] After initial curiosity wanes (day #2 of many thousands), nobody reads the headers because the interface is simple and never changes.
[1] Various systems have tweaked things here and there (e.g. to add parameterized trace points), but the original APIs never changed and code has always remained portable at the source level. The only big change over all that time was most systems (BSDs, macOS) eventually dropped CIRCLEQ after someone (I think a FreeBSD developer) posted benchmarks showing it sucked relative to TAILQ. I actually preferred CIRCLEQ; the entire header is small enough that for awhile I just copied it around everywhere.
And thru end up using clunky told like code gen to make up for the lack of generics and for mocking purposes. the resulting coffee ends up being a mess, and ironically, introduces more layers of abstraction (unnecessary interfaces just for the sake of mocking) than code bases in Java and C#
> The Python code snippet del a[i] deletes the element at index i from a list a. This code certainly is quite readable, but not so transparent: it’s easy to miss that the time complexity is O(n), where n is the number of elements in the list. Go doesn’t have a similar utility function. This is definitely less convenient, but also more transparent. Your code needs to explicitly state if it copies a section of the list. See 2 ways to delete an element from a slice[0] for a Go code example.
But the examples given in [0] could also be implemented in Python as well with, I dare say, the same time complexity.
Agree with the title, but I would like to add that learning Go (and Rust) made me look differently at many concepts in Java and made me better Java developer.
Do you use React? MySql? VsCode? YouTube? Chrome? Not all departments have to share the same vision. Go is open source and its code up on a repository, open and visible.
In other words, you cant escape the wrath of google, so what you should do is give up. It's like showing up at a Greenpeace march and pointing out to a protester they they are wearing shoes that were made in a facility that probably contributed to the destruction of the planet. Why bother protesting you imperfect hypocrite?
If one wanted to hurt Google, stop using their products which generate revenue/profit i.e. Google Search, YouTube etc. For me Golang does not fall into this category.
I'm not protesting anything. Sure I would like to use something else but I don't see any other search engine with better, more relevant results yet. Nor do I see a better video platform or a better webmail (I am being subjective here ofc). You can't tell people to give up good things just because [insert evil things here] and offer no better alternative. You can escape the wrath of google of course, there's always Bing, Duckgo and others; Python, Ruby, Elixir, PHP, Java; Proton, yahoo mail, Zoho; Vimeo, DailyMotion;
Gmail, Search and Go (yes it's my fav language so far) runs or helps my business greatly and with a direct impact on my income so yes...I'll gladly share my fetishes with the big brother. I don;t want to but I have no better alternatives. Do you?
I think your frustration comes from the fact that you want to avoid using their products but can't find better alternatives. Also, no need to call people names and...use your own username; don't go green in fear of internet points.
But all departments is still controlled by the company itself. No matter how good hearted the Go devs are, they are still paid by Google and I don't want to contribute to their power any more than I have to.
Not a perfect analogy but here it goes....as people, we are a mix of good and bad. Others have done things I do not like but that does not necessarily stop me from appreciating the good things about them. The world is a lot of gray than some people appreciate. At school and on the screens in the west, we are taught that the world is good vs. evil....
I wrote a PDF parser in golang a few years ago and it was an extremely unpleasant experience. No polymorphism means I essentially have to cast everything to void* aka interface{}. Rewrote it in C (the rewrite was quite easy actually) which at least made the lib accessible to other languages.
There's plenty of polymorphism in Go, that's what interfaces are for. In C you would typically use a struct stuffed with function pointers, how is that an improvement?
Its still a better C in many ways, strings and character sets is a big one.
It's definitely a better C, but that's about it. Let's not kid ourselves. It's in many ways a language stuck in the past, for senile people, OCD's, or drones who are forbidden to produce non-uniformity and who want everything verbose and explicit.
Any comparison of languages in the form "language X is generally better than language Y" with no additional project context or problem domain is going to be an extremely naive one, to the point it's not relevant to anyone having to make pragmatic tech choices.
This article is no exception.
I'm not partial to Go, Java or Python, but I do wish this sort of article stopped making headlines, leaving room for finer analyses and experience reports. These are what we really need.
Maybe it's because of the way my brain works, but I prefer clarity and brevity above almost everything else.
That's why I like Python over Go, C# and others. Python lets me fit more functionality (expressed as code) in my working memory, and I have to ignore less line noise and boilerplate.
Immutability and recursion patterns do shrink the search space to a minuscule spot. It may fit ones brain. But some people have a state driven thinking process, which I despised for decades but recently got to understand. I see why people like to code in C-land. The issue is that, since it's not a straightjacket like ~pure FP, if you don't have a brain that sees state, state variable, lifetimes, etc etc, you're free to write piles of crap. Especially when young because you may believe that complex => more code, and complex is good.
ps: note that I'm not criticizing FP or static/math oriented programming. I'm just trying to pu both sides into a shared perspective
Pure functional languages do have state. They don't have _implicit_ state. You can absolutely reason about state with mathematical abstractions, it's just not "free" and there's a learning/comfort curve.
I would not want to teach novice programmers a purely functional language...
And that's what it comes down to, almost always. How hard is it to hire other people to work on your code, and how hard is it to bring people up to speed, who need to code, but aren't programmers. Scientists, mostly. Business people increasingly.
Sure, but that was just an example to illustrate my point. Some of these languages are viewed as hard to learn simply because they are unfamiliar. But to someone who's never programmed before, all languages are unfamiliar, and it turned out, at least in Jane Street's case, that OCaml was easier for the business people to understand than Java or C#, or even Excel macros.
Oh I don't doubt, that SOMEONE is using these nice languages. Still, the community of OCaml or Haskell is not nearly as big and diverse as the Python community.
And that counts in all sorts of ways. You'll have much more trouble finding answers to any questions you have. You'll have less chances for community meetings or educational content. You'll definitely have more trouble finding new developers for your projects, both in business and in open source.
Again I am not trying to be a contrarian, but in my experience quantity != quality. You can take this statement the other way and say too much information is bad. There is a reason there are so few Go Stack Overflow questions and answers: most of Go's documentation is excellent and easy to follow and learn and they have excellent community resources, videos and guides. From what I have seen so far, the OCaml and Reason docs are great, and their communities are extremely supportive.
There's a huge difference between "you can write functional methods in any language" and "writing functional code is default/idiomatic".
Erlang doesn't support mutation at all, for example, and uses immutable types for everything. But it does let you send messages to other processes, which is a side-effect, so Erlang is not purely functional. But that doesn't mean that JavaScript is somehow just as functional as Erlang. Because it's not.
I believe they have also cited an advantage of using OCaml being the quality of applicants they were able to pool for hiring by virtue of mainly using a functional language and being able to draw from the enthusiastic and skilled community around it. It's weird, right? In some ways, this actually has nothing to do with OCaml itself or its merits, or even the functional paradigm. I hadn't heard your example before but that's also quite interesting. I wonder how much weight these kind of side-effects account for their success, and how much is actually due to the merits of the technology itself?
I not sure I agree. The university I went to used Haskell for its introduction to programming courses and I think it worked pretty well for most people, and especially for people who had never programmed before. The people that did struggled the most where actually those who already knew a bit of programming, but weren't very strong programmers.
The flip side is that all the students who had now had been introduced to programming via Haskell where completely confused when trying to understand OO and Java in the next course.
I'm not talking about teaching programming to people who show up voluntarily in a computer science introductory programming course, but those who have a full-time job doing/studying something else and have to learn how to code, on the side.
Considering Elixir and Elm are more niche than Python, and Python is being used more widely, Python will beat both of them in terms of a first language for people not studying computer science...
I completely agree, it's easier to write in Python. This is unfortunately extremely limited, as the convenience comes at the cost of a lack of control over memory and fine details. Other C-based languages may look convoluted but every line will tell you exactly what's going on (which comes with its own set of costs as well).
That lack of control doesn't count for a vast amount of people or cases. Data Science, Artificial Intelligence, fields with huge computing demands flock to Python.
Especially C++ and C# are notorious for hiding what actually happens, and in a worse way. Their performance however is more predictable, which is important in some cases.
Python is just an arbitrarily chosen glue language in regards to data science and machine learning. Pretty much all the heavy lifting (where having a lot of control is required for performance) is done by modules written in C or C++.
There's not a lot of difference between usage of such modules in Python "hiding" complexity and abstractions used in Java, C#, C++ or Rust except those languages don't actually need to use FFI to get acceptable speed. Computer Science is pretty much just layers and layers of abstractions and the level of abstraction that Go or Python chose to operate on isn't special in any way.
Numpy is written in C and PyTorch is written in C++. If you mean their APIs, you are right that they are more pleasant to use in Python, but that's because Python trades off performance and control for convenience and ease of use.
I only meant to say that in my opinion something like C++ "hiding" behavior in libraries, classes or through operator overloading is not different from Python where easy to use modules hide extreme amounts of complexity. In fact, C++ is better, because it uses FFI way less than Python and by being a lower level language it limits the maximum possible difference in abstraction level between visible and "hidden" code.
It's not really arbitrary. Python is an outstanding glue language, maybe Ruby or Haskell could compete (how is Perl6 nowadays?), but none of those you mentioned.
Rakudo Perl 6 is doing very well indeed, thank you. First official release in December 2015, second official release in November 2018. Monthly developer releases. See https://perl6.org for more information.
I agree that Python is great as a glue language. Maybe "arbitrary" wasn't the right word to use. What I meant was that Python hides what actually happens in exactly the same way that lower level languages do, just at a higher abstraction level.
However, to argue that its easier to write "good" code, is hyperbole of the highest order.
A programming language is a brush with which to paint your logic. If your picture is murky its because you, the programmer has made it murky.
There are many tradeoffs with "good" code. Readability, speed, compactness, modularisation, use of libraries. As with any other practical subject, your skill comes from practice. Yes, new tools can make it _easier_ to be more efficient, or faster. They might even badger you into good habits.
But
They cannot make you "good". Its the expensive camera fallacy. Sure a Medium format SLR will take _spectacular_ pictures, but only if you use it properly. A good photographer will be able to take wonderful images using a camera phone from 2006.
Which underscores my point. When you are building a table in real life you make it all from one or two materials. You'd keep to one or two types of fasteners, because its simple quicker an easier.
You don't change halfway through from turned oak to carbon fibre, "because its higher performance." unless you have a bloody good reason to change.
Sure I've used screws as nails, it works pretty well, but we all know its a dirty hack.
The Python code snippet del a[i] deletes the element at index i from a list a. This code certainly is quite readable, but not so transparent: it’s easy to miss that the time complexity is O(n), where n is the number of elements in the list.
Go doesn’t have a similar utility function. This is definitely less convenient, but also more transparent. Your code needs to explicitly state if it copies a section of the list. See 2 ways to delete an element from a slice for a Go code example.
I think this assertion is wrong. del in python does exactly what it says it does. There are precisely three reasons to not use del:
o Because you have written your program, got all the low hanging performance fruits, and now need to optimise how you delete from (large) lists, and dequeue doesn't work for you
o you are arrogant and want to show everyone how clever you are because you understand how list operation are handled under the hood. So you have written your own.
o a.remove()/a.pop() you think is more readable.
It is far more important to be readable than to be fast in 90% of cases. If we all really cared about speed, then React/electron/et al would never be a thing.
I think you're missing my point here. Firstly all of it is far slower than a native toolkit. secondly client side DOM rendering is almost never fast, compared to server side HTML/CSS.
Please do not use code formatting for quotations. Use the old de facto standard of a “> ” prefix. Please also don’t use it for lists; just leave two blank lines between each point, so that they’re in their own paragraph.
"Go makes it easier (than Java or Python) to write correct, clear and efficient code."
I really appreciate taking a minimalist and opinionated approach to language design but all design choices done in Go comes at a cost and the above broad statement simply is not correct. And to me it just adds to the impression of the Go community of being on the immature and almost cultish side.
Immature or cultish might be too strong, but I will say it is very fitting for being a language associated with Google, which is also very opiniated and arrogant, but maybe 80% of the time right. It's that 20% of the time though that you hope for some humility and open-mindedness.
I did not use Java outside of school. I've rewritten and/or worked in rewritten codebase that started in async Python (Twisted) and async Perl (AnyEvent) and ended up in Go. I found the Perl much more approachable than the Python. However, Go allows my teams to actually know what is coming and going from a function, how code flows in the project, and makes concurrency easier to grok.
Go succeeded because it compiles to native code, has good performance and an efficient standard lib - but in spite of being a terrible programming language. There are a great many programmers who cared about producing great software rather dwell on the quality of PLs, and for these people Go hit a particular sweet spot.
I have really tried to like Go, but the imperativeness is just something I struggled with. Even good Go code is littered with breaks, mid-function returns and imperative loops. For those who want cross-platform native code but find Rust too complicated, hopefully C# will fill the void once CoreRT ships.
I hear this comment made often. For some people like me, Go is simple, and that makes it a great programming language. I definitely wouldn't call it terrible!
Otherwise, a language is more than its feature set. A big part of what makes a language enjoyable is its culture as well. I think C# is a great language, but I prefer Go's culture much more.
To me, Go culture is very arrogant, so much in fact that it almost reminds me of some Lispers of old.
Unfortunately similar thing can be said about Rust. However, in Rust, most of the crap seems to be comming from zealous newcommers and a loud minority, while the leadership and people involved seem relatively humble (or at least not too arrogant). In Go, OTOH, the arrogance seems to be stemming all the way from the top, judging from some talks & blogs by Pike, Cheney et al. Pike in particular seems to me to be a very arrogant, unpleasant person. I respect him very much as an engineer, but I will never like the way he speaks, promotes Go and spreads FUD about other languages.
Like GOTO it makes the execution harder to reason about as you could easily miss it. It doesn't bother me personally but I can sort of see an argument for it.
It's not outdated, it depends on context. Several modern languages, including Rust, Swift, Scala, Kotlin, and F# are built around having expressions as constructs (if, match, try, etc. are all expressions). Once you have that powerful feature it makes sense to write code in a certain way to make use of that, and it ends up being clearer a lot of the time.
To expand on your first point, golang is popular because it has the Google brand behind it. The golang authors worked on a similar language before they were at Google and it didn't go anywhere
I actually think Go is a very good language. I don't measure languages on aesthetics, but team productivity and quality. Go has been unreasonably successful in making my team productive.
I tend not to care all that much about the language itself. What I care about is productivity and that I don't need to have a discussion about what language people would rather program in every 3-4 months. This is why I get suspicious when people say they are language enthusiasts. I leave it to the team to choose language, but it has to fit some minimum criteria and I don't want whining afterwards and I don't want a mishmash of languages "because it fit the domain better".
For instance, you have to add literally one line of code to terminate Java application at the end of main function. Why would such a minute feature of language make Java programs harder to reason about? Why would anyone even present this argument as a serious one?
Yes, Java does have its weaknesses. It's an old language and people were still figuring out a lot of the stuff. But the ecosystem is huge and if you know the quirks, it's still a heck of a productive language.
> Java inner classes suddenly appeared in 1997; it took more than a year to update the specification, and it became almost twice as big as a result. That’s a high price to pay for a non-essential feature.
I can't talk about the doubling of the specification but you can learn everything you need to about inner classes on 5 pages or so. It's over 20 years old now.
> generic arrays aren’t supported
So you can't use something with arrays that go doesn't even support ;-)
> type wildcards with upper and lower bounds are quite complicated.
Well, it's two words (extends and super) and some syntax. How could it be much easier?
> Enum [...] It’s certainly nice to have, but offers little that couldn’t be done with ordinary classes
Yes, and generics offer little that couldn't be done by implementing specialised classes. Enums are there to help you. You see an enum? You know what happens, you know what it offers, you know it's not a "normal" class but is there for a special reason.
Yes, Java does have its inconsistencies. Why is String Immutable when in a OO-language the opposite would be taken for granted? Why can't you use int as a datatype for an ArrayList but Integer? What's up with Stack and Deque and Vector? You have to know Java to work with it effectively. We are professionals after all. It can be expected that we know the language. Java gets the job done.
That's not to say that Go couldn't get ahead of Java and may be the better language (especially in the future). But the ecosystem is far from it for now and Go has to prove it won't suffer from the same problems Java is now.
Despite all critic comments here, I think the article is great because it clearly identifies what is minimalism in Go and how it is easy to learn and use compared to Java or Python. Still, there could be code snippets or more details on context but these are huge subjects and even if he tried it would be a hard read.
I don't like downvoting people but in this case your comment adds literally nothing to the conversation.
While I can wholeheartedly agree that golang has features, and in some cases absence of features, which make it less than ideal for some things. However, writing off a whole language, created by people who are no doubt smarter than you, is pretty silly.
Is it just me - but writing correct and clear code depends less on the language and more on the problem being solved and the domain - and ones understanding of both.
Someone with a detailed grasp of both can write clear code in C, Haskell, Python even BASIC
If you don't grasp either and have no time to be allowed to do it, no language will save you.
Given a certain level of understanding of the problem, certain languages can make it easier to write clear and correct code. For example, code that does physics or 3D graphics calculations is clearer in a language that can represent matrices and vectors as types providing the expected operations (e.g. C++ over C) and if the code involves physical quantities it can be easier to enforce correctness in a language that supports units in the type system.
This. I hate this relativism about the merits of languages. There's an entire field of study dedicated to that and just because you can write a web app in all your languages of choice doesn't actually invalidate the vast conceptual differences that exist and have real effects on how you program
Both C and C++ can represent matrices and vectors as types with related operations. In my view, the key difference between these two languages is RAII.
As a real world (-ish) example, I wrote some Python code to do some effects on DMX lights. OpenLighting did all the talking to the lights, so the Python code was really just focused on constructing a list of numbers that would eventually find their way into OpenLighting and onto a DMX cable.
Inside the Python code, each light has a Colour, and I built the Colour class with support for various math operators.
Want to make a Colour that's half as bright as another? Colour = OtherColour * 0.5. Want to blend two Colours? BlendedColour = OneColour + AnotherColour.
Doing this made all of the stuff that the code was doing incredibly obvious and straight forward. The alternative would have involved non-obvious helper functions and object methods or doing math directly on the rgb components encapsulated by each Colour object.
This was, for me, somewhat of a "light bulb" moment for operator overloading.
Color me skeptical :) The reason why unnamed operators work so well for math code is that their meaning is already well established.
OneColor + AnotherColor does not have any obvious meaning. If colors are RGBA vectors, it could be vector addition. But is that what your blend operation does?
The meaning makes sense to me which, for a one developer codebase, is OK, but it's not something I'd want to do in a codebase supported by a larger team. I expect that in a more complete product that even the term "blend" would have multiple meanings.
And it's also one of the easiest ways to write unmaintainable codes. I once inherited a code where the previous developer seemed to just have found out how cool operator overloading is. So there were a ton of overloads everywhere and you couldn't really tell what is just built-in operators and which is our own code. Total nightmare. After this I would be fine with operator overloading removed from languages.
Nothing wrong with writing:
Matrix_Multiply(m1,m2);
vs.
m1*m2
The first is not harder to read and not harder to write but I know exactly what's going on.
So this code is from Go's big.Int documentation (slightly adapted):
func main() {
a := big.NewInt(0)
b := big.NewInt(1)
var limit big.Int
limit.Exp(big.NewInt(10), big.NewInt(99), nil)
for a.Cmp(&limit) < 0 {
a.Add(a, b)
a, b = b, a
}
fmt.Println(a)
}
I can't say I'm particularly keen on having to read
a.Cmp(&limit) < 0
instead of
a < limit
The question is whether it's worth it to write code like this in order to prevent others from misusing operator overloading. I guess it may depend on how much math code you write and how disciplined your coworkers are.
> The first is not harder to read and not harder to write but I know exactly what's going on.
For your example, maybe so. But:
m = a * b * c + d * e + f
will be clearer than three Matrix_Multiply and two Matrix_Add calls. And one screen full of lines like mine will be far clearer than five screens full of Matrix_Add and Matrix_Multiply calls.
FWIW i prefer mul_x, even in languages such as C++ where operator overloading is available i put methods with names like mul, div, etc instead of operators as i am not fan of infix function calls.
Also specifically for vectors and matrices for 3D graphics, i prefer to use float mtx[16], vec[3], etc instead of specifying custom types (then i can use the vec_foo calls with matrices with the proper offsets or the normal part of a float plane[4] which is encoded as normal+distance or use the vec_foo calls directly in a vertex buffer, etc).
No language supports the commoditization of programming talent quite like Go. Java gave it a good shot, but Go is much closer to "perfection" in this regard. Using Go feels like mindless clerical work.
My industry (defense and govt contracting) is still big into Java, but we traditionally run at least 10 years behind the state of the art. At some point (maybe in another 10 years) I expect to see the cube farms full of Java code monkeys replaced by cube farms full of Golang code monkeys. Big body shops (which is what the majority of my industry is) love mediocrity and overstaffed programs.
By that time the cool kids will have moved on to something else. Maybe they'll rediscover OOP or some such.
I fail to see what's wrong with the commoditization of programming -- I assume you think there's something wrong because you use the term "code monkey".
Is there some language that's used by cool programmers that allows them to write complex code(equivalent of millions of lines of code written by "code monkeys" -- or 10 lines of code written by the gods) ?
Is there a conspiracy against said language that these god programmers write that prevents it from taking over the world (besides the "code monkeys" not being able to learn it?
Because in my experience once you get to decently complex projects, the (modern)language doesn't really matter that much.
My intuition is the above is applicable even for small projects, and that beyond hand picked examples, the programming language doesn't really matter as long as you're comparing apples to apples -- that is some business functionality, the choice is based just on the ecosystem.
"Is there some language that's used by cool programmers that allows them to write complex code(equivalent of millions of lines of code written by "code monkeys" -- or 10 lines of code written by the gods) ?"
WhatsApp famously built a company with a billion users, sold for $19 billion, with about 50 engineers, with a product built in Erlang. I think that's the best example of what you're asking about. It's not clear they could have accomplished the same success with any other language.
After being fully invested in Go for close to 6 years and working at primarily Go based shops...I want to disagree with this headline.
Folks new and old to Go still write data-race riddled code unless they put in the extreme mental effort and care into avoiding data-races. Also, if they write good unit tests that help them spin through the code they can often double-down on ensuring their code is correct by using the race-detector.
Even if you do all of this...sorry but there’s no guarantees.
In my opinion this is why we are seeing languages like Rust invest heavily in something like the borrow checker...to prove at compile time when you have racy code.
If Go had a compile time ability to do this it would be really amazing.
> Go is strongly and statically typed with no implicit conversions
This is one glaring exception: Concretely typed objects can be silently converted to interface objects. In particular, a `*MyErrorStruct` can be silently converted to an `error`. The giant footgun associated with this conversion is that because the resulting interface object "remembers" its concrete type, it won't compare equal to `nil`, even if the original value was in fact `nil`.
This is a pretty big exception, which I've seen cause bugs in production. It's common to have one function that returns a concrete type wrapped by another function that returns an interface. That means the silent type conversion is happening in the `return` statement, which is extremely easy to overlook. (Or which can be introduced after the fact by changing the signature of either function.)
209 comments
[ 4.2 ms ] story [ 226 ms ] threadI actually really like the error handling in Go and think it makes it easer to write good code. At least for simple programs.
So I clicked on this one. Started skimming through it, looking for code. Found none. Came back here to share my disappointment.
I'm always confused about articles that talk about code and have little code to show. Are there programmers who enjoy reading them? Or are these articles made for non-programmers?
Another thing that always throws me off is fluff. The article starts with "Choosing a programming language isn’t easy.". What does this add to the article? It immediately makes me think the author stuffed this article for no reason. So I have to skim even faster to not being cheated of my time. Is there anybody out there who thinks having the article prefixed with "Choosing a programming language isn’t easy." makes it more valuable?
Backward compatibility, compilation speed, circular dependencies, small vs big specs, etc. What code do you want to show about these aspects?
> The article starts with "Choosing a programming language isn’t easy.". What does this add to the article?
It's an introduction. It's a very common writing technique. You don't start with the core content of the article, you start with a paragraph or a sentence about why you wrote the article in the first place. You did the same with you "I love programming language" opening.
That is the point of my post. Asking who the target audience is. And how it is different from myself.
Well that bit is certainly not true; passing by certain communities and you’ll be let known theres only one good answer: {lisp,c#,haskell,rust,js,c/++}
If not code, how do you prove these assertions? How do you measure them?
> It's an introduction.
Inappropriate for an analytical argument. Common for infotainment (entertainment masquerading as information).
Read: It's a rant and/or PR.
- The article focuses on the properties of codebases, not individual code snippets. While it would be very valuable to do compare the properties of entire codebases, that is demanding on both the reader and the author.
- Opening the article with a statement like “Choosing a programming language is never easy...” is an acknowledgement of the fact that he is not claiming Go is unequivocally the best in all scenarios. The author is signaling that he is a reasonable person, and while that may seem like fluff it is a necessary component of communicating with a wide audience.
Probably there are languages that have a really expressive syntax that solve problems also in a scalable manner. For instance Python with its list comprehension that are a real code and time saver for software that does a lot with lists. (No coincidence it's so popular with ML.)
A list is much too slow because:
- Not contiguous - No SIMD vectorization - No parallel processing
Lists are mostly used for list of containers (for example storing the arrays inputs of a function)
All those arguments are fine and correct in small projects or libraries, there go is perfect.
But they really don't hold on big project like docker or containerd.
While it is clear what lines of code does, at a tactical level, it is extremely difficult to follow what happen in the big picture, the strategic level. Where the values comes from and where they go? When? How and why?
Honestly I keep seeing this in all big go project, despite the lack of generics.
Again if you need a small micorservice, use go! It is the best option right now. But I wouldn't bet any big codebase on it.
On teams of enough engineers, especially as those engineers rotate in and out of maintenance roles, richer abstractions tend to obfuscate intent and hurt maintainability more than help it.
The maintainability of the code is mostly determined not by how good the underlying language is but how good is that DSL. To take an example that's been widely discussed in HN, let's think about machine learning - the look, structure and maintainability of your code is highly influenced by the choice of your framework/"DSL" e.g. pytorch vs tensorflow1.x vs straight numpy vs Caffe will have major differences despite being in the "same language".
And in that regard, the features of the core language matter only in regard of whether they facilitate making a good DSL. Of course, some languages fail because their choices result in the "core DSL" i.e. standard libraries that everybody uses being fragmented and confusing; Go has it quite good IMHO for the standard library; but for every specific domain it also matters if the DSL for that domain is good, whether it's an ORM layer or a OpenGL interface or a deep learning framework or an physics simulation library - whether the language abstractions mean that these DSL's (often being interfaces/wrappers to some third-party code written in another language) are going to be good and convenient, or whether the language structures limit the DSL so it becomes unwieldy to use.
Every time I've seen this, it's been a disaster. Custom DSLs for business logic are, 100% of the time, a red flag of a development process gone off the rails.
(We may work in different industries.)
Go prevents you from doing this at a very low level. This is a huge advantage.
And it's worth noting that many (all?) of these particular systems were actually initially made as custom DSLs for internal use within a single company - Rails for 37signals/Basecamp, Pandas for AQR Capital Management, Django for Lawrence Journal-World, Tensorflow by Google Brain, etc. They were the exact thing that you describe - a custom DSL invented for the business logic that the particular company needed repeatedly. And they were not a disaster but the right thing to do in their case - mostly because they had the ability to make a somewhat good DSL though in all those cases it was an 'okayish' DSL initially and became good only through years of substantial changes. Yes, for every such case there are many bad internal DSLs. "Not Invented Here" is a common problem. You need to ensure that you're using good DSLs, and good DSLs generally gain wide adoption within that domain instead of every company building their own shoddy DSL.
I've seen a bunch of nightmarish financial systems with horrible DSLs. However, even in that case the DSL problem was that this DSL was horrible not that a DSL shouldn't have been used - any fix or rewrite would generally require replacing it with a better DSL/framework/whole-encompassing-structural-API/whatever, instead of sticking to the core programming language (which would just result in another emergent "DSL" of some core libraries/classes, which would be lousy compared to something you intentionally design to be usable).
The differences between programming languages are smaller than the differences between DLS's implemented in the same language. Code in Rails (Ruby) and Django (Python) has more in common than code for Django and Pandas. Writing Tensorflow code in Python is somewhat similar to writing TensorflowJS code in Javascript, but very dissimilar to writing Pytorch code that does the same thing.
They're not really independent libraries, they're frameworks that suggest, influence and sometimes even mandate most of the infrastructure around your code and the structure of your code in a much more stringent manner than the core language does. There's a fundamental difference between making a few calls to a black box library versus having the content and style of most of your code being dominated by the traits of that "library" - the large frameworks overwhelm the small(ish) core languages. So I believe it's justifiable to consider the frameworks I mentioned as DSLs.
Well, nothing I'm aware of beats the LISP family of languages as far as "making a good DSL" goes, but none of those are particularly popular :(
But that’s another topic.
Some tool that can point where in a 1M lines of code project those classes are being used, and if they would need to change.
I had convinced myself I was reading Clisp, instead of CLOS.
Common Lisp has an object system with classes since the early 90s. In fact it was the first standardized object-oriented language.
Common Lisp also has optional type declarations. Not very sophisticated, but they are there.
Check out the SBCL implementation, which has a compiler which does some static type checking/inferencing.
http://www.sbcl.org
But one usually can use types/classes at runtime, too.
That's a good thing. But then comes the git merge (or any other automatic code altering tool). How do you know something hasn't been messed up in the process ? (unless you wrote unit test for every argument of every function)
Lisp has compilers.
Some do also some type checking, here SBCL 1.5.2:
Then:So you're right, for programming in the large it's very important how well the language supports creating good reusable APIs and data structures. I think that's why so many people make such a big deal about generics in Go, though personally I think it's a poor example and the complaints are tiresome. I think I'd focus more on whether the language has things like macros and lambdas/continuations that let non-API idioms be expressed consistently and conveniently, as though they're part of the language (even though I think creating a true DSL is usually a bad idea). Not sure Go meets this standard TBH.
For me it's mainly due to the lack of generics and helper tools that naturally spawn from generics. Go's solution to having less LOC inside a function is _only_ to create helper methods/funcs. These helpers are bound to specific data structures and often are only applicable to that one use case in that one method that they came from. Your ability to make that helper function apply to more code depends heavily on what data structures are being used.
This form of non-generic bloat was far worse for me than the classic examples of `Fooi32()` and `FooString()`. The latter I don't mind at all.
Rust aids this process to me by, well, having generics. Converting one data structure into another becomes a breeze, and converting a Slice of those data structures is just as easy. I'll be really interested to see how Go feels once generics make it in - though I suspect I'll stick with more explicit memory management.
Speaking of memory management, it would be nice if Go had a way to intelligently indicate which structures are safe for concurrent use and which aren't. I've found in Rust that the explicit nature of the concurrent safety is not something I knew I wanted in Go. I know Go will not likely ever match Rust's safety, but if it at least indicated that something was an thread-unsafe implementation that would be golden.
[1] edit: Oh, and there is a disclaimer in this that the projects I'm involved in seem to heavily rely on managing several data structures in repeated ways. Aka, very generic friendly.. so not having generics made my projects extra painful. This is not likely the norm.
This has been my experience as well. People just parrot what the golang authors say about it being a language suites for "large scale development", whatever that means, without providing anything to back it up. Just because they said it was must be it's true? In practical experience, it's a verbose, clunky, and awkward language to use for any non-trivial code bases.
Rust is still too young to take advantage of this, but in due time it will.
I actually find Rust's standard library great. I think my only complaint so far is the lack of time handling. Luckily community packages (like Chrono, Diesel, Tokio, Hyper, Itertools) have implemented great solutions to a few missing details.
Hell, Iterators alone make my life so much better.
Then the team behind Rust after many research found that you can actually guaranty memory safety if you can track how data are used by each thread. To do this you need to track how each value is used and borrowed by each thread to insure memory safety. And so the borrow checker was born. It gives you the best of both world: speed And safety while adding only a little bit of time to the program to compile.
If Rust is slow to compile it’s not because of the borrow checker it only adds a little bit to the bottom line, but it’s because of Generics and other things. Which is also something that Go is lacking and doesn’t have yet.
... and yet experienced gophers will criticize newcomer's code for using channels too much and they will tell you you need to use traditional stupid shared memory approach instead of channels in many cases.
That's what some Go proponents say, not necessarily reality, and there's no study about this, just hearsay.
I find Go hurts readability for larger programs because it prevents many structural abstractions that make code more clear and concise (as opposed to mere fancy "clever" code, which it still allows), and because it forces too much boilerplace (error handling, no generics, etc).
Every large projects contains re-implementations of the same things (from data structures to algorithms, to parallel processing handling) that could have been done once and re-used forever with generics and a little more power.
Not maybe because of the lack of generics?
If I recall correctly one of the arguments for lack of generic was that they would make more obvious code, hence easier to follow and read. Actually it seems like the lack of generic just make more obvious code, hence more difficult to read.
The other argument was compilation speed, that honestly I cannot complain about.
I don't believe that is a reason ever articulated by the core team, but a post-hoc justification by apologists. The reason the core team gave was that they had not settled on a design that would fit well with their other goals for the language, and it simply didn't make the short list for 1.0.
Compared to python or Java? Well written python basically resembles Go. And most Java I've come across tries to spread logic across class boundaries so you never get to have all the code in front of you at one time.
I attribute it more to a static type system that lacks features I've grown use to in modern languages. Most of my experience in Java is 1.6 and prior so its possible code bases have improved since then.
You can build any abstraction you want in C.
This is disingenuous. Show me Pi types in C.
Interpreter, but close enough :)
https://github.com/codr7/g-fu/tree/master/v1
Embedding a higher level language that's controlled from a more primitive host language allows solving many otherwise tricky problems without the struggle.
I took a quick look at what these are. Couldn't they implemented with macros like (this builds a type "Utf8String" which "depends" on the value of the "ENCODING" parameter to figure out what set of functions to call for the guts of the functions that handle the type, e.g. RawStringUtf8Length):
Then in stringdef.ph (ph=parametric header, just came up with it): Then you can use the above simply like It isn't exactly pretty, but seems to do the work. The ENCODING parameter is only conceptually an Encoding enum, but this can be enforced with some dummy (unused) code that does use of the ENCODING value as Encoding (and if extra strength is needed, as enums are weakly typed, instead of native enums, you could define macros that create stronger "enums" out of structs).Edit: And the intrusive data structures commonly used in C are way more complicated than the equivalent generic code (in my opinion).
https://lwn.net/Articles/184495/
- Download one that uses interface{} / void pointers - okay, but not very efficient or type-safe.
- Download one that's intrusive - okay, but pretty convoluted.
- Write my own - a pain.
- Code generation - generics but worse.
(This isn't sarcastic.)
The overhead of interface{} isn't as high as people sometimes think. You have the overhead of boxing the value, but the actual type checking is just a single comparison and jump.
This is way too much in a tight loop. I want my structure to fit in the cache and I want to have as little tests as possible.
But yes, if you're writing performance critical code (which you generally won't be), then you'll need to use code generation to avoid boxing.
This kind of discussion is pointless without some kind of real world reference point. The vast majority of Go projects will never run into a situation where the overhead of using interface{} is a performance bottleneck. If you're working on a project where you consistently have to think about the cache performance of your code, then C++ or Rust would probably be better choices. Go isn't meant to be all things to all people.
I have different kinds of constraints, mainly propositional clauses (a or b or c) and pseudo-boolean constraints (2a + b + c >= 2). Long story short, a propositional clause is a special kind of pseudo-boolean constraint, so I can process my clauses as if they were regular pseudo-boolean constraints, but this is slower than using dedicated code.
Lots of problems only deal with propositional clauses, so they take longer to solve than they should, because I cannot say "this is a problem with only propositional clauses", vs "this is a problem with all kinds of clauses". I could use interfaces, and tried, but this is way slower, to the point it doesn't make sense to use them.
And I can't use C++ or rust for this problem: there are already tons of very good c++ SAT solvers, but my goal was to provide a SAT solver I can use in my go programs without resorting to cgo black magic.
[1]https://github.com/crillab/gophersat
Yes, fair enough, I think that is a good example of the kind of code that Go is not particularly suited to (if performance is critical).
If that is a hard requirement, then the answer is just that Go isn't a suitable language. AIUI the current generics proposal in Go, it won't fix that problem for you either.
Where I disagree with the programming community at large is how often that's actually a hard requirement. A lot of programmers grossly overestimate their performance requirements on the microscale [1], and so concerns about "premature optimization" remain alive and well decades later. However, there are undeniably still a non-trivial number of cases where that really is a hard requirement, in which case the answer is, don't use Go.
(Preferably, figure that out before you start. As a fairly heavy Go user, I actually kinda cringe when reading one of the several "We sped up Go by 10x on our particular big thing and got to barely-acceptable performance" articles, rather than consider it something to be celebrated. I mean, cool that you sped it up that much and all, but honestly the correct answer was to notice in the design phase that you were doing something you probably shouldn't have done in Go, modulo concerns about easy prototyping and such.)
[1]: I qualify with "microscale" because I also think a lot of programmers underestimate the performance requirements on the macro scale. But that's actually a very different issue. There's a lot of very sluggish and slow code out there in the wild that is architecturally wrong; at every individual step the code is reasonably optimized and fast, the problem is having the wrong steps.
I honestly wonder if go isn’t cursed to follow the same road.
But yeah, I share your concern. It would be nice to have a better way to write container types in Go, but generics always seem to end up getting abused.
Blaming just generics for that does not work imho; in Java people were writing really insanely over complex code close after the first release; I had an ex C++ guy before 2000 writing Java code that used every design pattern in The Book, everywhere he could and it ended up with 1000s of files and those strange long names that became the standard for Java a few years later.
The cognitive overhead certainly is. Computers exist to help us do things faster and with fewer mistakes. This is a prime example of using a computer to help us make more mistakes, worse.
There is no doubt that not having generics makes some code worse than it would be with generics. But I think the problem is often overstated.
But if you are working on a project where the details of the tree do not matter (so you can use a library), you will probably use C++, not C. I still have a hard time understanding what niche Go fills, it's too high level to compete with C or Rust, but here we are comparing its expressiveness with C.
A red-black tree implemented in the same simple style by an OpenBSD developer a couple of decades later was adopted almost overnight by all BSDs: https://man.openbsd.org/tree#EXAMPLES
Unlike the Linux version, the BSD version is as type-safe as a C++ template, permits a node to exist in as many collections as you want (one member field per collection), and like a template the BSD version can inline the comparison function. You even have the option of making all the routines extern or static, which you can't even do with C++ templates.
What the Linux version has going for it is that it doesn't need to rely on the preprocessor so the implementation looks a little neater and more like a C++ template--no columns of backslashes as required with preprocessor-based generation. But that's a horrible tradeoff. queue.h and tree.h have basically been frozen for decades.[1] After initial curiosity wanes (day #2 of many thousands), nobody reads the headers because the interface is simple and never changes.
[1] Various systems have tweaked things here and there (e.g. to add parameterized trace points), but the original APIs never changed and code has always remained portable at the source level. The only big change over all that time was most systems (BSDs, macOS) eventually dropped CIRCLEQ after someone (I think a FreeBSD developer) posted benchmarks showing it sucked relative to TAILQ. I actually preferred CIRCLEQ; the entire header is small enough that for awhile I just copied it around everywhere.
Could you give a couple of examples how this is made clearer in other languages?
> The Python code snippet del a[i] deletes the element at index i from a list a. This code certainly is quite readable, but not so transparent: it’s easy to miss that the time complexity is O(n), where n is the number of elements in the list. Go doesn’t have a similar utility function. This is definitely less convenient, but also more transparent. Your code needs to explicitly state if it copies a section of the list. See 2 ways to delete an element from a slice[0] for a Go code example.
But the examples given in [0] could also be implemented in Python as well with, I dare say, the same time complexity.
Sure Oracle may not be the wonderchild, but at least they don't store everything they can about me without me having any say in the matter.
Gmail, Search and Go (yes it's my fav language so far) runs or helps my business greatly and with a direct impact on my income so yes...I'll gladly share my fetishes with the big brother. I don;t want to but I have no better alternatives. Do you?
I think your frustration comes from the fact that you want to avoid using their products but can't find better alternatives. Also, no need to call people names and...use your own username; don't go green in fear of internet points.
> Why bother protesting you imperfect hypocrite?
As a thing that people are saying to the hypothetical Greenpeace protester, not a thing that GP is directing at you.
No. No. Yes. Yes. No.
But all departments is still controlled by the company itself. No matter how good hearted the Go devs are, they are still paid by Google and I don't want to contribute to their power any more than I have to.
Its still a better C in many ways, strings and character sets is a big one.
I certainly do not agree with them, and I see no clear evidence to support them.
There are different languages for different tasks. Tradeoffs abound. Pick the right tool, that’s all.
How is it more verbose than C++, Java, Rust or Swift?
You're not making much sense here, why do you feel so threatened?
All of these languages have generics.
The parent post was calling out Go for being overly verbose and explicit compared to alternatives. I was simply making an example to the contrary.
This article is no exception.
I'm not partial to Go, Java or Python, but I do wish this sort of article stopped making headlines, leaving room for finer analyses and experience reports. These are what we really need.
That's why I like Python over Go, C# and others. Python lets me fit more functionality (expressed as code) in my working memory, and I have to ignore less line noise and boilerplate.
ps: note that I'm not criticizing FP or static/math oriented programming. I'm just trying to pu both sides into a shared perspective
And that's what it comes down to, almost always. How hard is it to hire other people to work on your code, and how hard is it to bring people up to speed, who need to code, but aren't programmers. Scientists, mostly. Business people increasingly.
Jane Street, for example, went with OCaml in part because the business people found the code easier to review than other languages.
That said, the fact that OCaml is used by Bloomberg and Facebook (on multiple apps and products) alone make it not niche http://www.ocaml.org/learn/companies.html
That plus ReasonML's growing popularity, which is essentially just a syntax extension of OCaml, makes it even further not niche.
And that counts in all sorts of ways. You'll have much more trouble finding answers to any questions you have. You'll have less chances for community meetings or educational content. You'll definitely have more trouble finding new developers for your projects, both in business and in open source.
Erlang doesn't support mutation at all, for example, and uses immutable types for everything. But it does let you send messages to other processes, which is a side-effect, so Erlang is not purely functional. But that doesn't mean that JavaScript is somehow just as functional as Erlang. Because it's not.
Starting with Haskell is a bad idea, no doubt.
But there are easy to use functional languages that are very beginner friendly, like Elixir or Elm.
I not sure I agree. The university I went to used Haskell for its introduction to programming courses and I think it worked pretty well for most people, and especially for people who had never programmed before. The people that did struggled the most where actually those who already knew a bit of programming, but weren't very strong programmers.
The flip side is that all the students who had now had been introduced to programming via Haskell where completely confused when trying to understand OO and Java in the next course.
It's easier than teaching them imperative programming, because it's much easier to reason about.
The real problem is teaching functional programming to programmers whose brains have been damaged by years of writing imperative programs.
Preemptive strike against whitespace comment.
Especially C++ and C# are notorious for hiding what actually happens, and in a worse way. Their performance however is more predictable, which is important in some cases.
There's not a lot of difference between usage of such modules in Python "hiding" complexity and abstractions used in Java, C#, C++ or Rust except those languages don't actually need to use FFI to get acceptable speed. Computer Science is pretty much just layers and layers of abstractions and the level of abstraction that Go or Python chose to operate on isn't special in any way.
I only meant to say that in my opinion something like C++ "hiding" behavior in libraries, classes or through operator overloading is not different from Python where easy to use modules hide extreme amounts of complexity. In fact, C++ is better, because it uses FFI way less than Python and by being a lower level language it limits the maximum possible difference in abstraction level between visible and "hidden" code.
However, to argue that its easier to write "good" code, is hyperbole of the highest order.
A programming language is a brush with which to paint your logic. If your picture is murky its because you, the programmer has made it murky.
There are many tradeoffs with "good" code. Readability, speed, compactness, modularisation, use of libraries. As with any other practical subject, your skill comes from practice. Yes, new tools can make it _easier_ to be more efficient, or faster. They might even badger you into good habits.
But
They cannot make you "good". Its the expensive camera fallacy. Sure a Medium format SLR will take _spectacular_ pictures, but only if you use it properly. A good photographer will be able to take wonderful images using a camera phone from 2006.
Which underscores my point. When you are building a table in real life you make it all from one or two materials. You'd keep to one or two types of fasteners, because its simple quicker an easier.
You don't change halfway through from turned oak to carbon fibre, "because its higher performance." unless you have a bloody good reason to change.
Sure I've used screws as nails, it works pretty well, but we all know its a dirty hack.
I really appreciate taking a minimalist and opinionated approach to language design but all design choices done in Go comes at a cost and the above broad statement simply is not correct. And to me it just adds to the impression of the Go community of being on the immature and almost cultish side.
I have really tried to like Go, but the imperativeness is just something I struggled with. Even good Go code is littered with breaks, mid-function returns and imperative loops. For those who want cross-platform native code but find Rust too complicated, hopefully C# will fill the void once CoreRT ships.
Otherwise, a language is more than its feature set. A big part of what makes a language enjoyable is its culture as well. I think C# is a great language, but I prefer Go's culture much more.
To me, Go culture is very arrogant, so much in fact that it almost reminds me of some Lispers of old.
Unfortunately similar thing can be said about Rust. However, in Rust, most of the crap seems to be comming from zealous newcommers and a loud minority, while the leadership and people involved seem relatively humble (or at least not too arrogant). In Go, OTOH, the arrogance seems to be stemming all the way from the top, judging from some talks & blogs by Pike, Cheney et al. Pike in particular seems to me to be a very arrogant, unpleasant person. I respect him very much as an engineer, but I will never like the way he speaks, promotes Go and spreads FUD about other languages.
Java has AOT compilers available since around 2000, just not for free.
And there are plenty of other languages with AOT compilers to native code.
I tend not to care all that much about the language itself. What I care about is productivity and that I don't need to have a discussion about what language people would rather program in every 3-4 months. This is why I get suspicious when people say they are language enthusiasts. I leave it to the team to choose language, but it has to fit some minimum criteria and I don't want whining afterwards and I don't want a mishmash of languages "because it fit the domain better".
For instance, you have to add literally one line of code to terminate Java application at the end of main function. Why would such a minute feature of language make Java programs harder to reason about? Why would anyone even present this argument as a serious one?
> Java inner classes suddenly appeared in 1997; it took more than a year to update the specification, and it became almost twice as big as a result. That’s a high price to pay for a non-essential feature.
I can't talk about the doubling of the specification but you can learn everything you need to about inner classes on 5 pages or so. It's over 20 years old now.
> generic arrays aren’t supported
So you can't use something with arrays that go doesn't even support ;-)
> type wildcards with upper and lower bounds are quite complicated.
Well, it's two words (extends and super) and some syntax. How could it be much easier?
> Enum [...] It’s certainly nice to have, but offers little that couldn’t be done with ordinary classes
Yes, and generics offer little that couldn't be done by implementing specialised classes. Enums are there to help you. You see an enum? You know what happens, you know what it offers, you know it's not a "normal" class but is there for a special reason.
Yes, Java does have its inconsistencies. Why is String Immutable when in a OO-language the opposite would be taken for granted? Why can't you use int as a datatype for an ArrayList but Integer? What's up with Stack and Deque and Vector? You have to know Java to work with it effectively. We are professionals after all. It can be expected that we know the language. Java gets the job done.
That's not to say that Go couldn't get ahead of Java and may be the better language (especially in the future). But the ecosystem is far from it for now and Go has to prove it won't suffer from the same problems Java is now.
While I can wholeheartedly agree that golang has features, and in some cases absence of features, which make it less than ideal for some things. However, writing off a whole language, created by people who are no doubt smarter than you, is pretty silly.
Someone with a detailed grasp of both can write clear code in C, Haskell, Python even BASIC
If you don't grasp either and have no time to be allowed to do it, no language will save you.
That is certainly true.
But the language can have a big impact on the difficulty of getting the implementation correct.
* dead-simple array/vector operations (http://www.mathcs.emory.edu/~cheung/Courses/561/Syllabus/6-F...)
* many useful built-ins (e.g., DOT_PRODUCT, MAXVAL/MAXLOC, MATMUL, TRANSPOSE)
* standard math libraries (e.g., LAPACK) with lots of very fast functions for linear algebra
Inside the Python code, each light has a Colour, and I built the Colour class with support for various math operators.
Want to make a Colour that's half as bright as another? Colour = OtherColour * 0.5. Want to blend two Colours? BlendedColour = OneColour + AnotherColour.
Doing this made all of the stuff that the code was doing incredibly obvious and straight forward. The alternative would have involved non-obvious helper functions and object methods or doing math directly on the rgb components encapsulated by each Colour object.
This was, for me, somewhat of a "light bulb" moment for operator overloading.
OneColor + AnotherColor does not have any obvious meaning. If colors are RGBA vectors, it could be vector addition. But is that what your blend operation does?
What's wrong with:
The meaning makes sense to me which, for a one developer codebase, is OK, but it's not something I'd want to do in a codebase supported by a larger team. I expect that in a more complete product that even the term "blend" would have multiple meanings.
Nothing wrong with writing:
Matrix_Multiply(m1,m2);
vs.
m1*m2
The first is not harder to read and not harder to write but I know exactly what's going on.
For your example, maybe so. But:
will be clearer than three Matrix_Multiply and two Matrix_Add calls. And one screen full of lines like mine will be far clearer than five screens full of Matrix_Add and Matrix_Multiply calls.Also specifically for vectors and matrices for 3D graphics, i prefer to use float mtx[16], vec[3], etc instead of specifying custom types (then i can use the vec_foo calls with matrices with the proper offsets or the normal part of a float plane[4] which is encoded as normal+distance or use the vec_foo calls directly in a vertex buffer, etc).
My industry (defense and govt contracting) is still big into Java, but we traditionally run at least 10 years behind the state of the art. At some point (maybe in another 10 years) I expect to see the cube farms full of Java code monkeys replaced by cube farms full of Golang code monkeys. Big body shops (which is what the majority of my industry is) love mediocrity and overstaffed programs.
By that time the cool kids will have moved on to something else. Maybe they'll rediscover OOP or some such.
Is there some language that's used by cool programmers that allows them to write complex code(equivalent of millions of lines of code written by "code monkeys" -- or 10 lines of code written by the gods) ?
Is there a conspiracy against said language that these god programmers write that prevents it from taking over the world (besides the "code monkeys" not being able to learn it?
Because in my experience once you get to decently complex projects, the (modern)language doesn't really matter that much.
My intuition is the above is applicable even for small projects, and that beyond hand picked examples, the programming language doesn't really matter as long as you're comparing apples to apples -- that is some business functionality, the choice is based just on the ecosystem.
WhatsApp famously built a company with a billion users, sold for $19 billion, with about 50 engineers, with a product built in Erlang. I think that's the best example of what you're asking about. It's not clear they could have accomplished the same success with any other language.
I use it, but feel its design insults my intelligence.
I love ruby I would like to work with Ruby 100% of the time but yeah... too many downsides.
Folks new and old to Go still write data-race riddled code unless they put in the extreme mental effort and care into avoiding data-races. Also, if they write good unit tests that help them spin through the code they can often double-down on ensuring their code is correct by using the race-detector.
Even if you do all of this...sorry but there’s no guarantees.
In my opinion this is why we are seeing languages like Rust invest heavily in something like the borrow checker...to prove at compile time when you have racy code.
If Go had a compile time ability to do this it would be really amazing.
Yes, this all my experience and opinion.
Check out Pony.
I’d be happy to offload such mental hoops to the compiler.
This is one glaring exception: Concretely typed objects can be silently converted to interface objects. In particular, a `*MyErrorStruct` can be silently converted to an `error`. The giant footgun associated with this conversion is that because the resulting interface object "remembers" its concrete type, it won't compare equal to `nil`, even if the original value was in fact `nil`.
This is a pretty big exception, which I've seen cause bugs in production. It's common to have one function that returns a concrete type wrapped by another function that returns an interface. That means the silent type conversion is happening in the `return` statement, which is extremely easy to overlook. (Or which can be introduced after the fact by changing the signature of either function.)