How to create items without mutating structures?

Hello guys!

If I want to write a simple program where, let’s say, you can add a user to a “database” (not really a DB, but in memory storage), what’s the best way of doing it?

In another languages, I could insert them all into an array, but I cannot do that with vectors in Clojure, right? At least, it doesn’t sound right to me if I’m trying to write a functional programming code, since I’ll be changing the array every time that function runs.

What do you guys suggest in this situation?

If you want in-memory storage that can be treated like a database, you might want to look at atom and have your data as a hash map, keyed by whatever primary key you had in mind. The value in an atom can be atomically swapped for a new value, so imagine you have users in your “database” identified by their (unique) email address, then you might do:

(swap! database assoc (:email user) user)

You would be able to look up a user’s record like this:

(get @database "person@acme.com")

But you might want to think about whether you really want something like a “database” in memory as opposed to threading an immutable data structure through your functions (and being able to pass it along with additions or subtractions).

2 Likes

Wow, that’s really nice!

I know that this is not the best way of doing things in real world, but I’m doing an exercise where I have to write an API with multiple endpoints, and one of them is exclusively for adding users. Each time I hit this endpoint, It should create a new user and I cannot lose those already created before. Also, they should be available (as long as the server is running) for use when I hit other endpoints, to create connections between users as “friends”, for instance.

Do you think the atom really is the way-to-go in this scenario?

You need to learn about atoms first. Clojure uses immutable data, values can not be modified by default. To simulate states in Clojure, you need atoms, which wraps a value inside a reference. While you can not mutate the value, you are able to modify the reference and always read the latest value by (deref your-atom). Also to mutate the reference, you will need swap! and reset!.

https://clojuredocs.org/clojure.core/atom

1 Like

Atoms are definitely way to go in this scenario (a straightforward quick in-memory db). Review the clojuredocs link @jiyinyiyong posted up above if you haven’t already.

If you really only need the values to stay in place during an individual run of the server, an atom will be fine.

But if you’re planning to go down the database path at some point, you could also look at doing this will a real, in-memory SQL database such as H2, or even an on-disk local database (also H2, or Apache Derby, or HSQLDB, or SQLite).

The nice thing about doing that – starting with a real, in-memory DB – is that you could evolve the API services to use regular SQL databases very easily later on.

Yes, as others have said, the use of atom in this case is quite idiomatic. Atoms allow you to have shared mutable state in a thread and memory safe manner.

I just wanted to say that if you ended up in a place where you needed to coordinate read/writes to multiple shared states, and had concurrent execution in your application, you can also make use of Refs in that case. They allow you to define transactions, to allow to coordinate changes between them. They are a lot slower then atoms though, so only use them if you need them.

Finally, I also wanted to mention there is also: https://github.com/tonsky/datascript which is an in-memory Datomic-like database.

Oh, also, while @seancorfield mentioned how you could model the atom as a map where the key is your index. That usage makes it act a bit like a noSql document store. You can also model it where your atom is a relational DB. There’s a few ways to do this, but one simple one is to have the atom be a map of tables to list of rows where the rows are maps as well. Such as:

(def db (atom {}))

(defn insert
  [table row]
  (swap! db update-in [table] conj row))

(defn delete
  [table where]
  (swap! db update-in [table] #(remove where %)))

(defn update
  [table where f & args]
  (swap! db update-in [table] #(doall (map (fn[e] (if (where e) (apply f e args) e)) %))))

(defn select
  [table where]
  (->> (get-in @db [table]) (filter where)))

(defn join
  [table1 table2 on where]
  (->> (clojure.set/join (get-in @db [table1])
                         (get-in @db [table2])
                         on)
       (filter where)))

This approximately makes insert O(1), delete O(n), update O(n), select O(n) and join O(n). So it is slower then with an indexed structure, but can be quite convenient if the performance fits your needs.

1 Like

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.