Rust for Clojurists (2015)
gist.github.com
gist.github.com
The dynamism of Clojure and the memory safety of Rust are a strong combination, especially for streaming EDN data over websockets to Clojure or ClojureScript frontend.
However, neither Rust nor Clojure is the end-game. Eventually all languages will IMO adopt memory lifecycle management (lookahead guarantees) and borrow-checking as language features to do runtime optimization.
An interesting language in this regard is Carp, a high-performance statically-typed Lisp implemented in Haskell with borrow-checking: https://github.com/carp-lang/Carp
It's a shame they did not opt to follow Clojure syntax more closely, or it would make the transition much easier for the growing Clojure community. Clojure seems to be winning the Lisp language wars.
Rust could have be so much more if it used S-expressions or M-expressions. The macro language is an abomination compared to Lisp, but I understand they had to lure the embedded C crowd.
Personally I'd be more interested in CPUs adding dedicated GC chips such as this paper: https://ieeexplore.ieee.org/document/8695831
> It's a shame they did not opt to follow Clojure syntax more closely, or it would make the transition much easier for the growing Clojure community
I've been keeping an eye on Carp, but never tried it, at first glance it looked like they used the Clojure syntax, what part do they deviate from?
It's very pre-v1 though so expect breakage and API change.
[1] https://github.com/TimDeve/dino/blob/master/main.carp
[2] https://github.com/TimDeve/carpgamer/blob/master/main.carp
[3] https://github.com/TimDeve/carp-lang-arduino/blob/master/mai...
[4] https://github.com/TimDeve/koi/blob/master/examples/notes-js...
As long as Rust doesn't offer something similar as the REPL driven development Clojure offers, there is zero reason of preferring Rust (for me) over Clojure for the same tasks. Which, because of the last point ("Rust is not homoiconic") won't happen any time soon.
Now, if you need to do embedded system development, Rust might make more sense than Clojure, but it won't replace Clojure for the same tasks today.
It's ideal for getting into "flow", iterating on ideas, there's no waiting or downtime that you can get in other languages.
I don't know why REPL driven development and Clojure type REPL integration isn't how we all want to do development (assuming as you say it's not embedded or some other niche area). It's just so powerful as a tool for getting immediate feedback and allowing a tight, painless iteration on ideas.
Coming from more of test-driven development background, I found that tinkering in the REPL was similar to what I would usually do with unit tests, but without the ability to easily build up a test suite that would prevent regressions in future.
I'll start by writing a bunch of tests and have a test runner that runs all my tests everytime I change my source code and displays the results. It's fast, I have one project that runs ~1400 asserts and on each save it shows the results less than 2 seconds later.
THen I write the code in my IDE source editor and use a keyboard shortcut to send it to the REPL as I'm working. I wouldn't ever write code directly in the REPL. By the time I've got it working in the REPL I normally then see that all my tests are passing.
So usually I end up mostly living within (comment) blocks around the functions I'm interacting with, and when I'm happy with it, move the code into the correct test namespace, which is a matter of just moving the code around and changing references to namespaces.
I missed a trick in life, i started getting into Clojure in 2020 but about 5 years ago i used to sit 1 row of desks away from Stuart Sierra in Stirling and I don’t think i ever properly spoke to him save for hello or so. It was a funny setup in that operation, i had to fight for my small team to even have office space at the time!
Yes, it's called discipline ;)
In Clojure(or JS for example) you have to rely more on being disciplined around tests and documentation than with a statically typed language.
They begin their sentence with "obviously not the same thing as a LISP" so I'm sure they are aware that it can't get better than it is with lisps :)
Yeah, I agree with this, which is why I spend all my time in the editor, which is connected to the REPL. I never leave my editor to write stuff manually in the REPL, I simply send the forms from my editor to the REPL to be evaluated. So the best of both worlds :)
Recent example of how that workflow looks like: https://vlaaad.github.io/reveal/
What's on the left is vlaaad's editor, and the REPL is on the right. Usually I have a similar setup, but the output of the REPL goes straight into my editor instead of in a separate window.
One question for you, if you rarely used the REPL, how is your workflow with Clojure unless with a REPL? If you're working on a service, do you restart the process after each change or how to you avoid the REPL when working with Clojure?
I very rarely stray far from real TDD (red -> green -> refactor), and I very very rarely run whatever service I’m working on. The big exception is when I do (web) frontend work, which used to be my bread and butter but has been fairly limited for a few years.
That was the problem right there. Not getting used to a REPL driven workflow in Clojure is like using TypeScript without an IDE. It'll negate all benefits.
1. PHP
2. JavaScript (browser)
3. JavaScript (Node)
The introduction to functional programming was eye opening to me, and made me a significantly better programmer. Working with ClojureScript, on the other hand, was painful. Numerous hours-long debugging sessions where I discovered I was passing a different type to core functions than I expected, causing wildly unexpected and baffling errors. Reporting issues and having them closed with "wontfix" because "don't do that".
That's what led me to TypeScript. And then of course the IDE. But I carried what I learned from FP with me. And now, having learned a powerful type system, it's made me a much better programmer still.
If you needed to run the TypeScript compiler as a CLI and it spiled errors on the command line, that also wouldn't be a very pleasant experience.
I've known a few people coming from Java or C# to Clojure, never really getting into the REPL, would mostly program the same way they used too, and they went back. Which frankly doesn't surprise me, I'd go back as well if that was the case. The dynamism of Clojure isn't there out of lazyness to implement a static checker, but to be leveraged both at run-time and development time.
Clojure is still a lovely language without it, but if you don't leverage the dynamism, then it'll seem like the language is lacking staticness, obviously, since you're using it statically except it has no features built around staticness.
That said, I don't blame anyone for not leaning into the REPL, Clojure and especially ClojureScript doesn't make it easy, the tooling is rough around the edges.
As an aside, I think everyone should explore multiple paradigms and languages, and having good experience in a statically typed language will make you a better programmer, even if you were to go back to Clojure one day. The act of thinking about the types behind your variables actually makes you a better programmer in dynamic languages, because you start to have a static checker inside your head, and over time make less and less type related mistakes or can catch them very quickly.
Skip to 15:53 and 20:30 for examples: https://videos.confluent.io/watch/yHoHM4Gxo7Bu1MCdo8vVh6?
(defun cargo-process-check-sentinel (process event)
(when (string= "finished\n" event)
(rust-format-buffer)))
(defun my-after-save-hook ()
(when (eq major-mode 'rust-mode)
(set-process-sentinel (cargo-process-check) 'cargo-process-check-sentinel)))
(add-hook 'after-save-hook #'my-after-save-hook)This cracked me up so hard I can no longer deny being a straight up nerd.
Racket is, almost like Clojure, highly interactive (though not completely so, it has a conceptual separation of compile time and run time), and on top of that, it can call easily into a C ABI, which Rust provides. Both languages (Rust and Racket) are well-suited for writing pure functions returning immutable results which allows for a nice match in style. Calling across the language boundary is a bit slower than in the Python/C case but it can achieve a decent speed-up. Racket's places allow for parallel execution with almost separate memory images, and it also has green threads. And Racket has batteries such as a GUI toolkit and image data types included which is really nice.
So you can say it appeals to functional programmers in the sense that they tend to be wary of mutation, so does Rust in a way. Except where a functional language will turn to immutability, Rust turns to an advanced compiler and linear types to safeguard you against the dangers of mutation.
The real benefits are performance, which has always been the achilles heel of functional programmers.
As for performance, I don’t think it’s nearly as large a problem for FP in general as some make it out to be. OCaml and Haskell are typically quite fast, but of course they rely on GC, so they aren’t suitable for environments that are heavily resource constrained or where pauses are unacceptable.
Type inference also helps a lot but is standard in most imperative languages nowadays, so I wouldn't count it as a terseness advantage anymore. Also, Rust doesn't go full Hindley-Milner as types still have to be declared in function signatures, by design, for readability.
Generics are interesting and can save some typing when doing "blanket implementation" of traits, but overall still will count as stuff you wouldn't have to deal with in a more dynamic language.
The remaining sore point are the lifetimes annotations. Although most are elided by the compiler by now, if you fall into a pattern where you have to explicitly carry lifetimes around, things get tiresome real quick. At that point, I usually take it as a sign that my code is not as optimal as it could be, and try to rewrite in a way that's less verbose. This is very much compiler-centric approach and actually reflects the whole Rust experience where programming is akin to having a dialectic conversation with rustc, which although quite smart can also be very pragmatic in its ways.