Hi there Joe,
Thanks for reading, and thanks for the compliments!
Your observation that the ordering is the same between (db/get-messages)
and (vec (db/get-messages))
is correct, and on the right track to figuring this out. If we were to update [1] to use (vec (db/get-messages))
you’d notice no change at all. In fact, if you write a few messages on [3] with the vec
call removed (thus seeing them in the wrong order) and then refreshed the page, you’d see that they’d shuffle into the correct order. The fact that the issue only appears when we update our message-list
is the key here.
We use vec
here because the conj
function prepends to lists (the default type from db/get-messages
) but appends to vectors. Clojure does this because conj
uses the most efficient method of inserting an element into a collection. Lists are implemented with cons
cells, and so prepending is O(1) and appending is O(n), so conj
prefers prepending. On the other hand, vectors are quite complex, but are optimized for append and lookup, so conj
prefers appending. This is a subtle behaviour, but is an important one to know when working with Clojure.
Having our messages as a list
was okay in [1], since we were never updating our list. We were always getting it fresh from the database. However, in [2] and [3], we are optimistically updating our message-list
on the client. Since we’d like to append messages, we want a vector. We could convert it on the client side, but in practice it is preferable to have one consistent data model across the application. There is no need for us to keep messages as a list
, so the most straightforward solution is to convert it as soon as we get it from our database.
Hope that helps. Enjoy the rest of the book!