Separate out query* logic (returning var mappings) from query macro

This commit is contained in:
Jeremy Dormitzer 2019-09-11 22:42:35 -04:00
parent 06a7bc3c60
commit 0bb77771b1
2 changed files with 76 additions and 17 deletions

View File

@ -1,5 +1,5 @@
(ns sicp-logic.core
(:require [sicp-logic.binding :refer [instantiate]]
(:require [sicp-logic.binding :refer [instantiate var?]]
[sicp-logic.db :as db]
[sicp-logic.evaluator :refer [qeval]]))
@ -25,14 +25,43 @@
(defn query-syntax-process [q]
(map-over-symbols #'expand-question-mark q))
(defmacro query [db q]
(defn sanitize-frame [q frame]
"Fully resolves all variables in q and returns a map
of the variable names to their bindings"
(letfn [(vars [acc node]
(cond
(var? node) (conj acc [(second node) node])
(and (sequential? node) (not (empty? node)))
(concat
(vars acc (first node))
(vars acc (rest node)))))]
(let [qvars (vars [] q)]
(into {} (map vec (instantiate qvars frame (fn [v f] v)))))))
(defn query-results [db q]
"Queries the database for assertions that match the query."
(let [processed-q (query-syntax-process q)]
`(map (fn [frame#]
(instantiate (quote ~processed-q)
frame#
(fn [v# f#] (contract-question-mark v#))))
(qeval ~db (quote ~processed-q) [{}]))))
(map (fn [frame]
(sanitize-frame processed-q frame))
(qeval db processed-q [{}]))))
(defn instantiate-query [q frames]
"Fills in the query with variables from frames"
(let [processed-q (query-syntax-process q)]
(map (fn [frame]
(instantiate processed-q
frame
(fn [v f] (contract-question-mark v))))
frames)))
(defn query* [db q]
(instantiate-query q
(query-results db q)))
(defmacro query [db q]
"Convenience macro to query the database for assertions
that match the query."
`(query* ~db (quote ~q)))
(defn assert! [db assertion]
"Adds a new assertion to the database."

View File

@ -1,5 +1,5 @@
(ns sicp-logic.tests
(:require [clojure.test :as test :refer [deftest is]]
(:require [clojure.test :as test :refer [deftest is testing]]
[sicp-logic.core :as logic]
[sicp-logic.db.memory :as memdb]))
@ -116,6 +116,7 @@
(lisp-value > 75000 30000))]))))
(deftest rules
(testing "Basic rules"
(let [db (memdb/new-db)]
(setup-sicp-dataset! db)
(is (= (logic/query db [lives-near ?x [Bitdiddle Ben]])
@ -125,6 +126,35 @@
[lives-near ?x [Hacker Alyssa P]]))
'[(and (job (Fect Cy D) (computer programmer))
(lives-near (Fect Cy D) (Hacker Alyssa P)))]))))
(testing "More complicated rules"
(let [db (memdb/new-db)]
(logic/defrule! db [append-to-form [] ?y ?y])
(logic/defrule! db [append-to-form [?u & ?v] ?y [?u & ?z]]
(append-to-form ?v ?y ?z))
(is (= (logic/query db [append-to-form [a b] [c d] ?z])
'[[append-to-form [a b] [c d] [a & [b & [c d]]]]]))
(is (= (logic/query db [append-to-form [a ?x] [c d] [a b c d]])
'[[append-to-form [a b] [c d] [a b c d]]])))))
(deftest raw-query
(testing "Raw queries"
(let [db (memdb/new-db)]
(setup-sicp-dataset! db)
(is (= (logic/query-results db '[job ?x [computer programmer]])
'[{x [Hacker Alyssa P]}
{x [Fect Cy D]}]))
(is (= (logic/query-results db '[job ?x [computer ?type]])
'[{x [Bitdiddle Ben]
type wizard}
{x [Hacker Alyssa P]
type programmer}
{x [Fect Cy D]
type programmer}
{x [Tweakit Lem E]
type technician}]))
(is (= (logic/query-results db '[lives-near ?x [Bitdiddle Ben]])
'[{x [Reasoner Louis]}
{x [Aull DeWitt]}])))))
(defn run-tests []
(test/run-tests 'sicp-logic.tests))