I'm glad this is back. A version of this existed in the ancient past and helped encourage me to try Clojure which ended up being by far the most impactful decision in my professional life. It went away for a while for reasons I'm unclear on.
I use Clojure nearly daily at my job and at home. Sometimes it's standard Clojure, sometimes it's the excellent Babashka flavor which I use as a make-like task runner and Zsh-like script replacement. It's not the only language I use of course, and Go is a strong second place most of the time especially if I need something compiled to a single binary. But Clojure is where I generally feel most at home thanks to the irreplaceable REPL based development flow which is more like a dialogue with my program than your typical write compile run loop.
Combine that with it running on the JVM and you have a wonderful set of tools to get things done in a pleasant way.
I strongly encourage anyone with even a passing interest in Lisp and functional programming to give it a try. If you're using VSCode there is the excellent Calva plugin to help you out.
As a side note, I'm always amazed by people who can use a highly expressive language (Clojure, Rust, even TS) but switch to Go when they feel like it (especially pre-1.18).
To me, switching to a less expressive language is painful and infuriating. I remember having to switch from Python to Java 5, and how everything started to take 3 to 5 times longer code to express.
Maybe the key thing is to only write small things in less expressive languages, like shell scripts, or small C functions to help performance or FFI, or, well, tiny Go utilities.
Development in Go will always be more painful for some people than in Clojure or TS, because the language is intentionally hostile to abstraction, so you're thinking in higher level concepts, but you implement them in more steps than necessary. The program can never reflect the shape of the problem, because it will be riddled with "glue code" that implements the obvious dull steps that you abstract away when reasoning.
There's a population of people that don't like this kind of abstraction, because to understand the code you first need to understand the problem, the data flow and how it's expressed. They prefer to look at the issue "bottom up" than "top down", and for them Go is perfect, because instead of reasoning about what the module does, how the data flows, what the algorithm does, they can focus on the small: this is a loop that iterates through a slice, this is an if condition that depends on this variable.
There's no better or worse here, just two types of people. Some like to look at the whole bridge, some like to examine the individual nuts and bolts and joints.
No, two types of solving a problem. I like writing both (Haskell and Go) - for _myself_ - but the tooling and standard library and ... of Go is orders of magnitudes better.
I would refrain from comparing Haskell - a research language intended for academia and created in universities, to Go - a pragmatic industry language intended for production and created in a megacorp. Why would Haskell's tooling be better? It's not reasonable to expect that.
I'm actually surprised Go's tooling as as good as it is _although_ it is made by Google. But to be fair, GoogleTest is OK too.
Google has produced a number of quite good tools. See also Bazel, for instance.
During my time there, I was genuinely impressed by the quality of many of the internal tools. E.g. it took GitHub code review tools a long time to get on par with Google's internal code review tools from 2015.
"Quite good" is exactly the wording that I would have used too. But Go's tooling is _really_ good; the language itself is, well, "quite good". And is still supported and actively developed!
Well, you are comparing the tooling of haskell and go. Of course in this comparison the difference will be stark, but there are plenty of languages that are sanely expressive, unlike go, and has decent tooling around it, like java, c#, perhaps scala if you want even more expressivity.
All of these are perfectly mediocre and usable languages (in which I would write in if somebody pays me), but surprisingly even for myself I ended up really liking Go.
How does this affect readability when one tries to dive into an existing Go project? Is Go code harder to navigate than code in more expressive languages?
We shouldn't overestimtae the complexity of expressive language like Clojrue vs Go. Go is perfectly simple and easy to follow, even compared with Clojure. If you come from background of Computer Science or programming first, Go is easier to follow.
I always feel this comparison is a fallacy. Assembly is also perfectly simple and easy to follow, like each instruction is trivial. Yet you will fail to grasp the whole, as “you are zoomed in too close”. I feel go has a good scale for many kind of tasks, but at the same time, it lacks the expressivity to change the zoom level, which I feel is needlessly limiting and makes the language a bad language for problems that require a slightly higher level of abstraction.
I tried Go for a while after coming from JavaScript and didn't feel productive enough. I think this explains why exactly.
This would depend on the size of the project, and the difficulty of the solved problem.
With a simple problem, and a small codebase, Go is easier, because you need to know less language constructs.
With a complex problem, and a large codebase, a more expressive language is easier if used properly, because it can highlight what's important in the solution. The ratio of signal to noise when reading code is higher.
Go really is the most "readable" code for anybody who has ever read something C (or Algol ;) like - so just about anybody. The only "strange" stuff is its `iota`, the method syntax and of course channels and goroutines. But that's nowhere comparable to some C++ or Haskell, "type heavy" Typescript or some Lisp macro DSL.
While I share your perspective on go, I do prefer developing in a bottom-up way myself: I just do so with the intent of iterating my way to a higher level abstraction. Sometimes I see the abstraction right away and start with it, but I don’t want a language that gets in my way in either direction.
A Lisp (like Clojure), or Haskell are great for playing around with things and developing bottom-up. I did just that for a day job sometimes. Of course, I then had to translate that to Python or TS, but once an idea is understood and tried, it's not such a big deal.
That's interesting, for me Lisps, Python or TS (or Go) are great for playing around with things and developing bottom-up. And then I translate that to Haskell (or Rust or C++ or Go).
The thing is, in Go you can develop bottom-up, but you can't always end up with a an appropriate abstraction, because of the lack of tools to achieve that.
I do switch between Clojure and Rust depends on the problems I solve at hands ( prototypes vs building production ). And yes I need go through my checklist of each to switch from connecting data flows at high level to examine nuts and bolts at low level.
Then a week later, the squinty mole discovers the code is dead; not called at all. There is no slice and no variable.
Java has had lambdas, map/filter/reduce and other stuff for like ten years or so I think. It's a bit chatty for sure, but that's what you want sometimes, the verbosity can make it easier for newcomers to make changes and additions.
To me it isn't more infuriating than learning a new language or practicing one I'm not very good at. There's a bit of friction, but that's common when I'm developing in the languages I'm most fluent in too. Either that or the problem is trivial and likely to be in most common programming languages.
These Java features are ruined by not allowing mutable variables to be captured. Having to do the 1-element array trick when the compiler could just do it for me is insane.
If you are making JavaScript look good you are failing as a language.
You mean not having the error that go just fixed by changing the language is a problem?
JS has this error too, but nothing forces Java to have this issue if they fix the no-mutable-captures problem. Dart got this right from the start.
Here's JS showing the same issue.
The correct fix is that each loop iteration gets a fresh i instead of i just being mutated. Currently you can't see the difference in Java between there-is-only-one-i and there-is-a-fresh-i-every-time, so Java is free to do this right.Here's what I'd do:
This outputs 0-9 instead of 10, though I'm just a silly self-taught person that never got schooled at these things, maybe there is some reason not to use closures that I don't know about.These days in JS you can just use let instead of var and it will work, but in itself that inconsistency is a bit weird
Yeah, let/const is what I tend to use. Here I wanted to change someone else's code as little as possible.
Edit: Oh, right, it also solves the 10-issue. Thanks, didn't realise at first.
Typically I can use a forEach in such cases, and when I can't it's probably an incrementer and the IDE just switches out my int as an automatic solution to its complaint.
I think its OK to be reminded that the context is mainly immutable when chaining on a stream.
Method polymorphism to achieve default parameter values is a much worse nuisance, but not particularly hard to get used to when one decides to focus on the problem rather than the inevitable warts of a broadly useful programming language.
Edit: Also a pretty weird complaint in a thread about a programming language where pretty much all the basic data structures are immutable. Maybe take it to the top and make the quip about worse than JavaScript with regards to Clojure?
Doesn't the forEach method on arrayList have the exact same issue?
Java 7 is already pretty pleasant, and it has been improving ever since then.
(Say what you want about Oracle Corporation, but as the wardens of Java they're doing a pretty good job.)
The funny thing is, go is significantly more verbose than even decade-old java.
I’ve been writing a lot of Java 11 recently, and I find it to just be a very pleasant environment to work in: the IDEs still are best in class, afaict; the newer APIs avoid a lot of the boilerplate I used to have to write out and there are generally mature libraries to interact with just about any system you might need. Where Clojure really shines is that it’s a really well thought-out layer that can leverage this ecosystem and adds its own well-designed tools on top for my preferred more-interactive development style.
I must use PyTorch for ML, but badly wish I could use TypeScript instead. Its type system demolishes anything else I've used. Writing anything from SPI communications, to server logic, to rich GUIs is a breeze. Then I work in Python and have to fight `Any`s, ambiguous arguments, more verbose code, and bizarrely slow run times. It makes me sad.
While I agree with you, Python is getting better in this regard, as is mypy.
Making historical interfaces well-typed is another kettle of fish. For instance, Python's range() is actually two overrides with incompatible signatures. I bet PyTorch has more of such examples.
If it only had a V8 class JIT in the package as well.
I write less boilerplate in Go than in Rust, about as much in Go (but of course different ;) as in Haskell).
That's cool! How do you manage? E.g. the `if err != nil { return err; }` stuff?
Well, in the context of Lisp, `err != nil` is equivalent to Lisp's parens: if somebody complains about that, chances are their critique is superficial (and there are enough "real" problems when using Go, especially when using goroutines and channels). The amount of low abstraction Go code I write is compensated by the amount of "abstraction boilerplate" like implementing type classes by myself or letting the compiler derive, implementing newtypes, adding GADTs, adding Pattern Synonyms and Type Families to be able to comfortably use the GADTs without much boilerplate and so on. Rust adds for example the need for associated types and is generally more verbose than Haskell.
I do not see a significant difference in the amount of boilerplate needed for catching exceptions, pattern matching or branching on the existence of an error value. Nobody can stop you from implementing `bind` in Go for your error (which I did in some places), but of course Rust "wins" with its `?`.
i find i flip between different languages when i'm thinking about the problem in a different way
an expressive language (clojure, python, C#, etc.) is good when you're iterating "what problem am i even trying to solve?"
a less expressive language is typically better when you more or less know a high level solution to your business problem, and you're iterating on "how do i make the machine actually do it". like "how do i reduce my AWS bill" or "how do i render it in 16ms?"
for something like gamedev, enough of the problem is in the second category that you might not even reach for the expressive language. (many do, though. with lua, or artisanal lisp dialects)
If you're counting C# as an expressive language then a lot of gamedevs will reach for one, judging by the market share of unity, me included :P
Python is the new Java 5 here. The lack of multiline lambdas is incredibly awkward, leading to code that bounces you around the file.
Lack of multiline lambdas is infuriating, but nested named functions partially make up for it.
We get used to being yet another work related chore.
I won't tell you there aren't stumbling blocks and impedence mismatches but most of them are relatively minor syntactical things. The faux-AST in my head gets typed out in the wrong format and I have to backspace and correct it (think function declaration differences). The biggest workflow difference is undoubtedly the lack of a REPL in Go, but that is somewhat made up for by Go's lightning fast compilation and testing tooling (probably its second strongest attribute after the excellent standard library). After a day or two of writing in one or the other my neurons seem to get used to it only to cause the same problem in reverse when I switch back. Overall, it's not too bad and a case of just accepting the tools available for what they are. I don't try to force Go to be Clojure or Clojure to be Go.
As a C++ programmer, I definitely like my languages with extra legs nailed on.
But sometimes limitations can spur creativity.
Expressiveness has many dimensions. It depends on what you are trying to express in the first place.
C++ or Rust are expressive in both directions, towards the metal and upwards in terms of abstractions. But those languages are not easy to reason about. They come with lots of stuff, hard to grok abstractions, ceremony and so on.
Small languages like Scheme or Lua are expressive in that they don't force you to write a lot of stuff to get the job done. But those languages don't typically let you express what the computer should actually do at a lower level.
C or Zigs let you do those things and are also relatively small languages. But their facilities for abstraction are limited and they force you to express more computational details.
Go frees memory and schedules goroutines for you, but lets you express a lot more about how you lay out memory than other managed languages, so you can actually think in terms of bits and bytes. It has a great std lib so you can get stuff going quickly and reliably.
Writing Go is less fun but the language has far better tooling, dead simple deployment, lower memory usage, generally higher performance, and is easier to onboard new developers. Also I find it way easier to read old projects after time away.
You can get incredible mileage out of Go using just the stdlib, built in tools, and the pure go port of SQLite. With Clojure it feels like I have to stitch a million random libraries together to get anywhere. Writing Clojure is delightful but the ecosystem and tooling pale in comparison, and at the end of the day I think that’s what matters.
If Clojure had great built-in tooling, better error messages (and maybe some SBCL-esque type checking features), and if Datomic was open source and less rough around the edges, I think the language would have been far more successful.
The "j" is always what put me off from learning Clojure. I've had too many bad experiences with JDKs and JVMs that I stay a thousand kilometers away from any thing that has "J" in it unless it's referring to JavaScript.
Check out Babashka. It's a single binary that runs Clojure without a JVM. It also starts up really fast and has a much lower base memory requirement: https://github.com/babashka/babashka
It's pretty easy to switch to writing JVM Clojure if you're familiar with Babashka. Most of the libraries written for Babashka are designed to work in either environment.
That said, there are reasons you may want to use the Clojure on the JVM later on. It might be interesting to read the replies to another poster with similar concerns about the JVM: https://news.ycombinator.com/item?id=40445415
Well, there aren’t too many more stable platforms out there, so your experience elsewhere couldn’t be all that cloudless either.
You can also try ClojureScript, which runs on JavaScript. If you're developing frontends.
Interesting. Usually the reluctance comes from people confusing Java the language with JVM the platform.
The creator, Anthony Grimes (https://github.com/Raynes), sadly passed away seven years ago: https://www.reddit.com/r/Clojure/comments/5gyyxw/clojure_ope...