datalevin 2026-07-13

A question from the book, in the VIP-status example section on rules:

(def reasoning-rules
  '[;; Rule 1: High Spender
    [(is-high-spender? ?u)
     [?u :user/total-spent ?amount]
     [(> ?amount 5000)]]
    ;; Rule 2: Frequent Shopper
    [(is-frequent-shopper? ?u)
     [?u :user/order-count ?count]
     [(> ?count 20)]]
    ;; Rule 3: Reasoner for VIP Status
    [(vip-status ?u ?tier)
     (is-high-spender? ?u)
     (is-frequent-shopper? ?u)
     [(ground :gold) ?tier]]
    [(vip-status ?u ?tier)
     (or (is-high-spender? ?u)
         (is-frequent-shopper? ?u))
     [(ground :silver) ?tier]]])
The two clauses for vip-status implies that the order of rules is important, correct? Otherwise, if I understand correctly, the :silver status would be liable to be assigned to vips that would also qualify for :gold. In a thread from 2024 I see this https://clojurians.slack.com/archives/C01RD3AF336/p1725641324831899?thread_ts=1725634881.640399&cid=C01RD3AF336: > The current rule engine processes rules in the order as you write them, so rearranging them makes a big difference. > The rewritten engine will be order independent. Unsure if this is still the case, I see the rules list referred to as a set/rule set--which to me implies un-ordered by default, but of course there is sorted-set in clojure.core, so I shouldn't read that word-choice as a be-all end-all

Rules order does not matter. So yes, a user qualifying for silver would also qualify for gold. So that example needs to add rule to exclude those satisfy both conditions.

I will correct the web version of the book, but the paper book has already been submitted, so it will need to be in the errata.

Thanks for pointing this out.

I appreciate the clarification, wish I had gotten to this section sooner during the review window- jumped around too much 😓

Thank you for catching this problem. 🙏

Another basic rule-based misunderstanding I might be having, which is causing me some confusion: Given the following database:

(def tconn
    (d/get-conn "/tmp/datalevin/tst"
      {:a/name {:db/valueType :db.type/string}
       :a/b {:db/valueType :db.type/ref}
       :b/name {:db/valueType :db.type/string}
       :b/c {:db/valueType :db.type/ref}
       :c/name {:db/valueType :db.type/string}
       :c/d {:db/valueType :db.type/ref}
       :d/name {:db/valueType :db.type/string}
       :d/e {:db/valueType :db.type/ref}
       :e/name {:db/valueType :db.type/string}}))
  
  (d/transact! tconn
    [{:db/id 1
      :a/name "A"
      :a/b {:db/id 2
            :b/name "B"
            :b/c {:db/id 3
                  :c/name "C"
                  :c/d {:db/id 4
                        :d/name "D"
                        :d/e {:db/id 5
                              :e/name "E"}}}}}])
The following query/ruleset evaluates as I expect:
(d/q
    '[:find (pull ?a [:*])
      :in $ % ?subject
      :where
      [?e :e/name ?subject]
      (e->a ?e ?a)
      [?a :a/b ?b]]
    (d/db tconn)
    '[[(b->d ?b ?d), [?b :b/c ?c], [?c :c/d ?d]]
      [(a->d ?a ?d), [?a :a/b ?b], (b->d ?b ?d)]
      [(e->a ?e ?a), [?d :d/e ?e], (a->d ?a ?d)]]
    "E")
  ;;=> ([{:db/id 1, :a/name "A", :a/b #:db{:id 2}}])
But if I alter the :find to pull ?b instead, I get an empty list (), even though (to me, at least) the previous result implies ?b should be populated If I elide some rule usage however, ?b ends up properly bound
(d/q
    '[:find (pull ?b [:*])
      :in $ % ?subject
      :where
      [?e :e/name ?subject]
      ;; (e->a ?e ?a) | supplanted by the following 2 clauses
      [?d :d/e ?e]
      (b->d ?b ?d)
      [?a :a/b ?b]]
    (d/db tconn)
    '[[(b->d ?b ?d), [?b :b/c ?c], [?c :c/d ?d]]
      [(a->d ?a ?d), [?a :a/b ?b], (b->d ?b ?d)]
      [(e->a ?e ?a), [?d :d/e ?e], (a->d ?a ?d)]]
    "E")
  ;;=> ([{:db/id 2, :b/name "B", :b/c #:db{:id 3}}])
Why does pulling ?b in the first query fail, but not in the second one?

This is a bug that has already been fixed in current master.

Ah I see; thanks, will try and run on that for the time being