Hacker News new | ask | show | jobs
by dkersten 1694 days ago
I sorely miss Clojure's immutable data structures when I use other languages (Python, Javascript, C++). They give me a guarantee that nobody else is going to be changing the data, somewhere deep down the call stack. I've had cases in production Python code where some seemingly unrelated code module somewhere was mutating data without my knowledge, breaking whatever I was working on. This is much harder to have happen in Clojure (you have to go out of your way to store things in mutable types, or use Java interop hacks to do this, which shouldn't pass code review without good reason).

Clojure's sequence abstraction also leads to a very rich and powerful set of core functions that make transforming your data a breeze. Other languages can do this too, but in Clojure I find its just really easy and natural since its part of the language's core library.

It also helps emphasize pure functions: data in, data out. Which makes my code much nicer.

Besides that, I use clojure because I like reitit and spec/malli for writing my HTTP routes, and hugsql for writing my SQL, both much better than the equivalent libraries in other languages I use.

I also like its emphasis on data first, code second. Sure you can do this in other languages too, but Clojure encourages it since its something the community has rallied behind.

1 comments

They give me a guarantee that nobody else is going to be changing the data, somewhere deep down the call stack. I've had cases in production Python code where some seemingly unrelated code module somewhere was mutating data without my knowledge, breaking whatever I was working on.

Since we are talking about single-threaded single stack context, immutability wouldn't have saved you here, even with immutability you could have receive the wrong value/data from some function in another module down the call stack. The problem was not mutation but a function returning bad output.

You are implying some code modified data underneath you, which can't happen in a single thread.

This was in a single-threaded application. Immutability would have saved me because it guarantees that no nested function call can mutate data unless its passed in or stored in a mutable way (in a Clojure atom, for example). If the data structure does not contain any mutable constructs, then it cannot be mutated in a way that is visible.

> You are implying some code modified data underneath you, which can't happen in a single thread.

Sure it can. I mean, yes, the code is ultimately called by my code, through some deep transitive dependency, but the point is that if you have a complex architecture where dependencies interact in complex ways, things can be mutated by a nested function somewhere deep in the call stack. Immutable data would prevent this, unless the data is stored in a mutable object, but in that case, its easy to audit your mutable objects to see where they are used, accessed and mutated. In a language without immutable types, any assignment operator could potentially be mutating something that you don't want it to mutate.

Besides, many otherwise-single-threaded applications we develop these days do have ways that things can mutate asynchronously: request handlers, timeouts, animation frames, network response callbacks etc. It doesn't literally have to mutate them in parallel with other code I'm executing for it to become a problem, just when you flatten a particular execution chain the data may have changed without your knowledge between two steps. For example: I do a network request to an API and wait for the response, has the data been mutated when the response callback is run?

It may even be perfectly valid to do so, my main concern is that it should be explicit and carefully managed. If data defaults to immutable and mutable data requires some extra ceremony (ie deliberate intent by choosing to use an atom or other mutable type), then this signals where care must be taken and makes it clear where integrity may need to be checked, data may need to be reloaded or recalculated etc. Everything else is guaranteed to have the same value you left it with, no matter what, even if it was passed by reference to a callback or deeply nested function call.

Anyway, I was just pointing out why I miss Clojure's data structures when I use other languages and in what cases Clojure would have saved me from headaches. YMMV and all that.

An aspect of immutability I really appreciate is that I know no variable has been changed after it's been assigned to, including by me. If I want to know where the value was assigned, I only have to look for the only place it could have been assigned. I don't have to search anywhere else.
99% of variables are local to your function, is not like program a thousand of global variables.
Sounds like you code pretty tight. I've seen plenty of functions reference variables out of function scope, or be reassigned within long function bodies. I personally don't code that way, but not everyone does. And as you've seen in this thread, there are people that do like immutability. To each his own.
I do prefer immutability too and think it should be the default for most programming, my original point was that for me, the trade-off of using clojure (small ecosystem) instead javascript for single server CRUD apps is not worth it but as a language I'll take clojure over javascript any day.