Skip to content

Instantly share code, notes, and snippets.

@ustun
Created March 1, 2017 22:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ustun/648af2ed6053e2edd1611b8308e07ff8 to your computer and use it in GitHub Desktop.
Save ustun/648af2ed6053e2edd1611b8308e07ff8 to your computer and use it in GitHub Desktop.
Let's say we have a feed of questions and each question has an answer, and each answer has an id.
We want to find the answer with id=5 and update its like count.
Assume the data structure is nested, so
(def feed (atom {:questions [{:id 1 :answers [{:id 5, text: 'foo', :like-count 4}]}]})
If I know that the answer I'm targeting is at 0th position of 0th question, I can do:
(swap! feed update-in [:questions 0 :answers 0 :like-count] inc)
But I don't actually know that it is at 0th position. I only know the answer id. What do I do then?
If it were mutable, it would be easy, since my "answer view" would actually hold on to the answer map. So,
I could just do (swap! my-answer update :like-count inc)
But it is not. One could use cursors for that, but will I create new cursors for each answer?
So, imagine the following:
(swap! feed update-in [:questions ALL :answers #(= :id 5) :like-count] inc)
Here, update took a path where we checked an arbitary predicate.
I run into this all the time in UI. Of course, if the data was modelled "normalized" or as in Datomic, so that my structure was like the following, I could just easily find and swap the answer.
(def feed (atom
{:question-ids ["q1" "q2"]
:q1 {:text "Question 1" :answer-ids ["a1" "a5"]}
:a5 {:text "Answer 5" :like-count 5}}))
Here, I can directly find a5.
(swap! feed update-in [:a5 :like-count] inc)
But now, my views need to do the denormalization. So whenever I display question 1 and its answers, I need to do a "db" lookup.
@borkdude
Copy link

borkdude commented Mar 1, 2017

;; sorry for missing indentation, I typed this directly in a REPL
(def feeds {:questions [{:id 1, :answers [{:id 5, :text "foo", :like-count 4}]}]})
(defn update-when [coll id-key v update-key f & args] (map (fn [e] (if (= v (get e id-key)) (apply update e update-key f args) e)) coll))
(update feeds :questions update-when :id 1 :answers update-when :id 5 :like-count inc)
;;=> {:questions ({:id 1, :answers ({:id 5, :text "foo", :like-count 5})})}

I think the last line reads pretty nice. update-when could be more general by replacing equality with a more general predicate.

@ustun
Copy link
Author

ustun commented Mar 1, 2017

Yes, at the very least, we need something in core like update-when that takes a list of predicates.

For reference, here is the version with specter:

(transform [:questions ALL #(= (:id %) 1) :answers ALL #(= (:id %) 2) :like-count] inc feed)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment