How to write touch-all (touch all reachable entities from an entity) in Datomic?

319 Views Asked by At

I wanted to write a function that would expand everything that is reachable from a given Datomic Entity. I am aware that this might be problematic if there are cycles, assume that entity is not cyclic.

(defn touch-all 
  "Touches `entity and all other reachable Entities of `entity"
  [entity]
  (let [entity (d/touch entity)]
    (doseq [[k v] entity]
      (when (instance? datomic.query.EntityMap v)
        (touch-all v)))
    entity))

d/touch expands out all "keys" in the entity and displays their values as either their literal value or as another (untouched) entity.

The code above does walk through all children (recursive) of entity. Therefore all entities reachable from the entity have been touched. Why am I not seeing the entity in its fully child expanded form when I type (touch-all entity)?

A large part of my confusion has to do with what I see in the repl:

sample.datomic> (def root (d/entity db 44))
#'sample.datomic/root
sample.datomic> root
{:db/id 17592186045421}
sample.datomic> (:answer-2-card root)
{:db/id 17592186045423}
sample.datomic> root
{:answer-2-card {:db/id 17592186045423}, :db/id 17592186045421}
sample.datomic> (-> root :answer-2-card :text)
"Card 2"
sample.datomic> (-> root :answer-2-card)
{:text "Card 2", :db/id 17592186045423}
sample.datomic> root
{:answer-2-card {:text "Card 2", :db/id 17592186045423}, :db/id 17592186045421}

So, it really looks like the Entities are mutable and sticky. When I access entities within containing entities, it appears to effect the string representation of the containing attribute as well. If this works manually "recursively" in the repl, why does it not work when I do it in a function? I am just confused because I don't seem to be able to repeat what I can manually do in the repl within a function. Seems odd.

1

There are 1 best solutions below

1
On

The answer to your question is immutability.

entity is bound to the first touched entity passed by parameter, but that value doesn't change when the recursion touches other entities.

If you want a single value containing a tree of touched entities you should do something like:

(defn touch-all
  "Touches `entity and all other reachable Entities of `entity"
  [e]
  (if (instance? datomic.query.EntityMap e)
     (reduce (fn [m [k v]]
              (assoc m k (touch-all v)))
             {}
             (d/touch e))
      e))

With the following considerations:

  • Now a hash-map is returned not a datomic entity since assoc fails on entities.
  • This solution is incomplete for attributes with type ref and cardinality many (in that case, when e is a set it should be considered it could be a set of entities)