logoalt Hacker News

gleennlast Wednesday at 4:46 AM2 repliesview on HN

IMHO, this is one of the coolest aspects of Clojure: the data structures were designed to be immutable as efficiently as possible. This allows for some of the most critical aspects of the language to function. If you're whole program passes around values that are immutable, you don't ever have to worry about which part of the code owns what. Rust is such a different language but the borrow-checker is solving a nearly identical problem: who owns this memory and who is allowed to change it? In Rust, one owner ever gets to write to a piece of memory and you can move that around. It catches lots of bugs and is extremely efficient, but it is definitely a difficult aspect of the language to master. Clojure just says everyone gets to read and share everything, because every mutation is a copy. One piece of your codebase can never stomp on or free some memory that another part is using. Big tradeoffs with garbage collection but geeze does it make reasoning about Clojure programs SO much easier. And the cornerstone of that idea is fast, immutable data structures by default. Maps are implemented similarly in that they are fast to make mutated copies with structural sharing to save memory.


Replies

j-pblast Wednesday at 9:44 AM

It's surprising to me how well these go together, the transient concept in clojure is essentially a &mut, couple that with a reference counter check and you get fast transient mutations with cheap persistent clone.

All of rusts persistent immutable datastructure libraries like im make use of this for drastically more efficient operations without loss in capability or programming style.

I used the same principle for my rust re-imagination of datascript [1], and the borrow checker together with some merkle dag magic allows for some really interesting optimisations, like set operations with almost no additional overhead.

Which allows me to do stuff like not have insert be the primary way you get data into a database, you simply create database fragments and union them:

  let herbert = ufoid();
  let dune = ufoid();
  let mut library = TribleSet::new();

  library += entity! { &herbert @
        literature::firstname: "Frank",
        literature::lastname: "Herbert",
    };

  library += entity! { &dune @
        literature::title: "Dune",
        literature::author: &herbert,
        literature::quote: ws.put(
            "I must not fear. Fear is the mind-killer."
        ),
    };

  ws.commit(library, "import dune");
The entity! macro itself just creates a TribleSet and += is just union.

In Clojure this would have been too expensive because you would have to make a copy of the tries every time, and not be able to reuse the trie nodes due to borrow checking their unique ownership.

1: https://github.com/triblespace/triblespace-rs

rienbdjlast Wednesday at 6:30 AM

There are also atomic references when mutability is the best approach. This is forcing single writer but in the API rather than as a proof.