rlm@10
|
1 ;; Copyright (c) Jeffrey Straszheim. All rights reserved. The use and
|
rlm@10
|
2 ;; distribution terms for this software are covered by the Eclipse Public
|
rlm@10
|
3 ;; License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can
|
rlm@10
|
4 ;; be found in the file epl-v10.html at the root of this distribution. By
|
rlm@10
|
5 ;; using this software in any fashion, you are agreeing to be bound by the
|
rlm@10
|
6 ;; terms of this license. You must not remove this notice, or any other,
|
rlm@10
|
7 ;; from this software.
|
rlm@10
|
8 ;;
|
rlm@10
|
9 ;; literals.clj
|
rlm@10
|
10 ;;
|
rlm@10
|
11 ;; A Clojure implementation of Datalog -- Literals
|
rlm@10
|
12 ;;
|
rlm@10
|
13 ;; straszheimjeffrey (gmail)
|
rlm@10
|
14 ;; Created 25 Feburary 2009
|
rlm@10
|
15
|
rlm@10
|
16
|
rlm@10
|
17 (ns clojure.contrib.datalog.literals
|
rlm@10
|
18 (:use clojure.contrib.datalog.util)
|
rlm@10
|
19 (:use clojure.contrib.datalog.database)
|
rlm@10
|
20 (:use [clojure.set :only (intersection)])
|
rlm@10
|
21 (:use [clojure.contrib.set :only (subset?)]))
|
rlm@10
|
22
|
rlm@10
|
23
|
rlm@10
|
24 ;;; Type Definitions
|
rlm@10
|
25
|
rlm@10
|
26 (defstruct atomic-literal
|
rlm@10
|
27 :predicate ; The predicate name
|
rlm@10
|
28 :term-bindings ; A map of column names to bindings
|
rlm@10
|
29 :literal-type) ; ::literal or ::negated
|
rlm@10
|
30
|
rlm@10
|
31 (derive ::negated ::literal)
|
rlm@10
|
32
|
rlm@10
|
33 (defstruct conditional-literal
|
rlm@10
|
34 :fun ; The fun to call
|
rlm@10
|
35 :symbol ; The fun symbol (for display)
|
rlm@10
|
36 :terms ; The formal arguments
|
rlm@10
|
37 :literal-type) ; ::conditional
|
rlm@10
|
38
|
rlm@10
|
39
|
rlm@10
|
40 ;;; Basics
|
rlm@10
|
41
|
rlm@10
|
42
|
rlm@10
|
43 (defmulti literal-predicate
|
rlm@10
|
44 "Return the predicate/relation this conditional operates over"
|
rlm@10
|
45 :literal-type)
|
rlm@10
|
46
|
rlm@10
|
47 (defmulti literal-columns
|
rlm@10
|
48 "Return the column names this applies to"
|
rlm@10
|
49 :literal-type)
|
rlm@10
|
50
|
rlm@10
|
51 (defmulti literal-vars
|
rlm@10
|
52 "Returns the logic vars used by this literal"
|
rlm@10
|
53 :literal-type)
|
rlm@10
|
54
|
rlm@10
|
55 (defmulti positive-vars
|
rlm@10
|
56 "Returns the logic vars used in a positive position"
|
rlm@10
|
57 :literal-type)
|
rlm@10
|
58
|
rlm@10
|
59 (defmulti negative-vars
|
rlm@10
|
60 "Returns the logic vars used in a negative position"
|
rlm@10
|
61 :literal-type)
|
rlm@10
|
62
|
rlm@10
|
63 (defmethod literal-predicate ::literal
|
rlm@10
|
64 [l]
|
rlm@10
|
65 (:predicate l))
|
rlm@10
|
66
|
rlm@10
|
67 (defmethod literal-predicate ::conditional
|
rlm@10
|
68 [l]
|
rlm@10
|
69 nil)
|
rlm@10
|
70
|
rlm@10
|
71 (defmethod literal-columns ::literal
|
rlm@10
|
72 [l]
|
rlm@10
|
73 (-> l :term-bindings keys set))
|
rlm@10
|
74
|
rlm@10
|
75 (defmethod literal-columns ::conditional
|
rlm@10
|
76 [l]
|
rlm@10
|
77 nil)
|
rlm@10
|
78
|
rlm@10
|
79 (defmethod literal-vars ::literal
|
rlm@10
|
80 [l]
|
rlm@10
|
81 (set (filter is-var? (-> l :term-bindings vals))))
|
rlm@10
|
82
|
rlm@10
|
83 (defmethod literal-vars ::conditional
|
rlm@10
|
84 [l]
|
rlm@10
|
85 (set (filter is-var? (:terms l))))
|
rlm@10
|
86
|
rlm@10
|
87 (defmethod positive-vars ::literal
|
rlm@10
|
88 [l]
|
rlm@10
|
89 (literal-vars l))
|
rlm@10
|
90
|
rlm@10
|
91 (defmethod positive-vars ::negated
|
rlm@10
|
92 [l]
|
rlm@10
|
93 nil)
|
rlm@10
|
94
|
rlm@10
|
95 (defmethod positive-vars ::conditional
|
rlm@10
|
96 [l]
|
rlm@10
|
97 nil)
|
rlm@10
|
98
|
rlm@10
|
99 (defmethod negative-vars ::literal
|
rlm@10
|
100 [l]
|
rlm@10
|
101 nil)
|
rlm@10
|
102
|
rlm@10
|
103 (defmethod negative-vars ::negated
|
rlm@10
|
104 [l]
|
rlm@10
|
105 (literal-vars l))
|
rlm@10
|
106
|
rlm@10
|
107 (defmethod negative-vars ::conditional
|
rlm@10
|
108 [l]
|
rlm@10
|
109 (literal-vars l))
|
rlm@10
|
110
|
rlm@10
|
111 (defn negated?
|
rlm@10
|
112 "Is this literal a negated literal?"
|
rlm@10
|
113 [l]
|
rlm@10
|
114 (= (:literal-type l) ::negated))
|
rlm@10
|
115
|
rlm@10
|
116 (defn positive?
|
rlm@10
|
117 "Is this a positive literal?"
|
rlm@10
|
118 [l]
|
rlm@10
|
119 (= (:literal-type l) ::literal))
|
rlm@10
|
120
|
rlm@10
|
121
|
rlm@10
|
122 ;;; Building Literals
|
rlm@10
|
123
|
rlm@10
|
124 (def negation-symbol 'not!)
|
rlm@10
|
125 (def conditional-symbol 'if)
|
rlm@10
|
126
|
rlm@10
|
127 (defmulti build-literal
|
rlm@10
|
128 "(Returns an unevaluated expression (to be used in macros) of a
|
rlm@10
|
129 literal."
|
rlm@10
|
130 first)
|
rlm@10
|
131
|
rlm@10
|
132 (defn build-atom
|
rlm@10
|
133 "Returns an unevaluated expression (to be used in a macro) of an
|
rlm@10
|
134 atom."
|
rlm@10
|
135 [f type]
|
rlm@10
|
136 (let [p (first f)
|
rlm@10
|
137 ts (map #(if (is-var? %) `(quote ~%) %) (next f))
|
rlm@10
|
138 b (if (seq ts) (apply assoc {} ts) nil)]
|
rlm@10
|
139 `(struct atomic-literal ~p ~b ~type)))
|
rlm@10
|
140
|
rlm@10
|
141 (defmethod build-literal :default
|
rlm@10
|
142 [f]
|
rlm@10
|
143 (build-atom f ::literal))
|
rlm@10
|
144
|
rlm@10
|
145 (defmethod build-literal negation-symbol
|
rlm@10
|
146 [f]
|
rlm@10
|
147 (build-atom (rest f) ::negated))
|
rlm@10
|
148
|
rlm@10
|
149 (defmethod build-literal conditional-symbol
|
rlm@10
|
150 [f]
|
rlm@10
|
151 (let [symbol (fnext f)
|
rlm@10
|
152 terms (nnext f)
|
rlm@10
|
153 fun `(fn [binds#] (apply ~symbol binds#))]
|
rlm@10
|
154 `(struct conditional-literal
|
rlm@10
|
155 ~fun
|
rlm@10
|
156 '~symbol
|
rlm@10
|
157 '~terms
|
rlm@10
|
158 ::conditional)))
|
rlm@10
|
159
|
rlm@10
|
160
|
rlm@10
|
161 ;;; Display
|
rlm@10
|
162
|
rlm@10
|
163 (defmulti display-literal
|
rlm@10
|
164 "Converts a struct representing a literal to a normal list"
|
rlm@10
|
165 :literal-type)
|
rlm@10
|
166
|
rlm@10
|
167 (defn- display
|
rlm@10
|
168 [l]
|
rlm@10
|
169 (conj (-> l :term-bindings list* flatten) (literal-predicate l)))
|
rlm@10
|
170
|
rlm@10
|
171 (defmethod display-literal ::literal
|
rlm@10
|
172 [l]
|
rlm@10
|
173 (display l))
|
rlm@10
|
174
|
rlm@10
|
175 (defmethod display-literal ::negated
|
rlm@10
|
176 [l]
|
rlm@10
|
177 (conj (display l) negation-symbol))
|
rlm@10
|
178
|
rlm@10
|
179 (defmethod display-literal ::conditional
|
rlm@10
|
180 [l]
|
rlm@10
|
181 (list* conditional-symbol (:symbol l) (:terms l)))
|
rlm@10
|
182
|
rlm@10
|
183
|
rlm@10
|
184 ;;; Sip computation
|
rlm@10
|
185
|
rlm@10
|
186 (defmulti get-vs-from-cs
|
rlm@10
|
187 "From a set of columns, return the vars"
|
rlm@10
|
188 :literal-type)
|
rlm@10
|
189
|
rlm@10
|
190 (defmethod get-vs-from-cs ::literal
|
rlm@10
|
191 [l bound]
|
rlm@10
|
192 (set (filter is-var?
|
rlm@10
|
193 (vals (select-keys (:term-bindings l)
|
rlm@10
|
194 bound)))))
|
rlm@10
|
195
|
rlm@10
|
196 (defmethod get-vs-from-cs ::conditional
|
rlm@10
|
197 [l bound]
|
rlm@10
|
198 nil)
|
rlm@10
|
199
|
rlm@10
|
200
|
rlm@10
|
201 (defmulti get-cs-from-vs
|
rlm@10
|
202 "From a set of vars, get the columns"
|
rlm@10
|
203 :literal-type)
|
rlm@10
|
204
|
rlm@10
|
205 (defmethod get-cs-from-vs ::literal
|
rlm@10
|
206 [l bound]
|
rlm@10
|
207 (reduce conj
|
rlm@10
|
208 #{}
|
rlm@10
|
209 (remove nil?
|
rlm@10
|
210 (map (fn [[k v]] (if (bound v) k nil))
|
rlm@10
|
211 (:term-bindings l)))))
|
rlm@10
|
212
|
rlm@10
|
213 (defmethod get-cs-from-vs ::conditional
|
rlm@10
|
214 [l bound]
|
rlm@10
|
215 nil)
|
rlm@10
|
216
|
rlm@10
|
217
|
rlm@10
|
218 (defmulti get-self-bound-cs
|
rlm@10
|
219 "Get the columns that are bound withing the literal."
|
rlm@10
|
220 :literal-type)
|
rlm@10
|
221
|
rlm@10
|
222 (defmethod get-self-bound-cs ::literal
|
rlm@10
|
223 [l]
|
rlm@10
|
224 (reduce conj
|
rlm@10
|
225 #{}
|
rlm@10
|
226 (remove nil?
|
rlm@10
|
227 (map (fn [[k v]] (if (not (is-var? v)) k nil))
|
rlm@10
|
228 (:term-bindings l)))))
|
rlm@10
|
229
|
rlm@10
|
230 (defmethod get-self-bound-cs ::conditional
|
rlm@10
|
231 [l]
|
rlm@10
|
232 nil)
|
rlm@10
|
233
|
rlm@10
|
234
|
rlm@10
|
235 (defmulti literal-appropriate?
|
rlm@10
|
236 "When passed a set of bound vars, determines if this literal can be
|
rlm@10
|
237 used during this point of a SIP computation."
|
rlm@10
|
238 (fn [b l] (:literal-type l)))
|
rlm@10
|
239
|
rlm@10
|
240 (defmethod literal-appropriate? ::literal
|
rlm@10
|
241 [bound l]
|
rlm@10
|
242 (not (empty? (intersection (literal-vars l) bound))))
|
rlm@10
|
243
|
rlm@10
|
244 (defmethod literal-appropriate? ::negated
|
rlm@10
|
245 [bound l]
|
rlm@10
|
246 (subset? (literal-vars l) bound))
|
rlm@10
|
247
|
rlm@10
|
248 (defmethod literal-appropriate? ::conditional
|
rlm@10
|
249 [bound l]
|
rlm@10
|
250 (subset? (literal-vars l) bound))
|
rlm@10
|
251
|
rlm@10
|
252
|
rlm@10
|
253 (defmulti adorned-literal
|
rlm@10
|
254 "When passed a set of bound columns, returns the adorned literal"
|
rlm@10
|
255 (fn [l b] (:literal-type l)))
|
rlm@10
|
256
|
rlm@10
|
257 (defmethod adorned-literal ::literal
|
rlm@10
|
258 [l bound]
|
rlm@10
|
259 (let [pred (literal-predicate l)
|
rlm@10
|
260 bnds (intersection (literal-columns l) bound)]
|
rlm@10
|
261 (if (empty? bound)
|
rlm@10
|
262 l
|
rlm@10
|
263 (assoc l :predicate {:pred pred :bound bnds}))))
|
rlm@10
|
264
|
rlm@10
|
265 (defmethod adorned-literal ::conditional
|
rlm@10
|
266 [l bound]
|
rlm@10
|
267 l)
|
rlm@10
|
268
|
rlm@10
|
269
|
rlm@10
|
270 (defn get-adorned-bindings
|
rlm@10
|
271 "Get the bindings from this adorned literal."
|
rlm@10
|
272 [pred]
|
rlm@10
|
273 (:bound pred))
|
rlm@10
|
274
|
rlm@10
|
275 (defn get-base-predicate
|
rlm@10
|
276 "Get the base predicate from this predicate."
|
rlm@10
|
277 [pred]
|
rlm@10
|
278 (if (map? pred)
|
rlm@10
|
279 (:pred pred)
|
rlm@10
|
280 pred))
|
rlm@10
|
281
|
rlm@10
|
282
|
rlm@10
|
283 ;;; Magic Stuff
|
rlm@10
|
284
|
rlm@10
|
285 (defn magic-literal
|
rlm@10
|
286 "Create a magic version of this adorned predicate."
|
rlm@10
|
287 [l]
|
rlm@10
|
288 (assert (-> l :literal-type (isa? ::literal)))
|
rlm@10
|
289 (let [pred (literal-predicate l)
|
rlm@10
|
290 pred-map (if (map? pred) pred {:pred pred})
|
rlm@10
|
291 bound (get-adorned-bindings pred)
|
rlm@10
|
292 ntb (select-keys (:term-bindings l) bound)]
|
rlm@10
|
293 (assoc l :predicate (assoc pred-map :magic true) :term-bindings ntb :literal-type ::literal)))
|
rlm@10
|
294
|
rlm@10
|
295 (defn literal-magic?
|
rlm@10
|
296 "Is this literal magic?"
|
rlm@10
|
297 [lit]
|
rlm@10
|
298 (let [pred (literal-predicate lit)]
|
rlm@10
|
299 (when (map? pred)
|
rlm@10
|
300 (:magic pred))))
|
rlm@10
|
301
|
rlm@10
|
302 (defn build-seed-bindings
|
rlm@10
|
303 "Given a seed literal, already adorned and in magic form, convert
|
rlm@10
|
304 its bound constants to new variables."
|
rlm@10
|
305 [s]
|
rlm@10
|
306 (assert (-> s :literal-type (isa? ::literal)))
|
rlm@10
|
307 (let [ntbs (map-values (fn [_] (gensym '?_gen_)) (:term-bindings s))]
|
rlm@10
|
308 (assoc s :term-bindings ntbs)))
|
rlm@10
|
309
|
rlm@10
|
310
|
rlm@10
|
311 ;;; Semi-naive support
|
rlm@10
|
312
|
rlm@10
|
313 (defn negated-literal
|
rlm@10
|
314 "Given a literal l, return a negated version"
|
rlm@10
|
315 [l]
|
rlm@10
|
316 (assert (-> l :literal-type (= ::literal)))
|
rlm@10
|
317 (assoc l :literal-type ::negated))
|
rlm@10
|
318
|
rlm@10
|
319 (defn delta-literal
|
rlm@10
|
320 "Given a literal l, return a delta version"
|
rlm@10
|
321 [l]
|
rlm@10
|
322 (let [pred* (:predicate l)
|
rlm@10
|
323 pred (if (map? pred*) pred* {:pred pred*})]
|
rlm@10
|
324 (assoc l :predicate (assoc pred :delta true))))
|
rlm@10
|
325
|
rlm@10
|
326
|
rlm@10
|
327 ;;; Database operations
|
rlm@10
|
328
|
rlm@10
|
329 (defn- build-partial-tuple
|
rlm@10
|
330 [lit binds]
|
rlm@10
|
331 (let [tbs (:term-bindings lit)
|
rlm@10
|
332 each (fn [[key val :as pair]]
|
rlm@10
|
333 (if (is-var? val)
|
rlm@10
|
334 (if-let [n (binds val)]
|
rlm@10
|
335 [key n]
|
rlm@10
|
336 nil)
|
rlm@10
|
337 pair))]
|
rlm@10
|
338 (into {} (remove nil? (map each tbs)))))
|
rlm@10
|
339
|
rlm@10
|
340 (defn- project-onto-literal
|
rlm@10
|
341 "Given a literal, and a materialized tuple, return a set of variable
|
rlm@10
|
342 bindings."
|
rlm@10
|
343 [lit tuple]
|
rlm@10
|
344 (let [step (fn [binds [key val]]
|
rlm@10
|
345 (if (and (is-var? val)
|
rlm@10
|
346 (contains? tuple key))
|
rlm@10
|
347 (assoc binds val (tuple key))
|
rlm@10
|
348 binds))]
|
rlm@10
|
349 (reduce step {} (:term-bindings lit))))
|
rlm@10
|
350
|
rlm@10
|
351
|
rlm@10
|
352 (defn- join-literal*
|
rlm@10
|
353 [db lit bs fun]
|
rlm@10
|
354 (let [each (fn [binds]
|
rlm@10
|
355 (let [pt (build-partial-tuple lit binds)]
|
rlm@10
|
356 (fun binds pt)))]
|
rlm@10
|
357 (when (contains? db (literal-predicate lit))
|
rlm@10
|
358 (apply concat (map each bs)))))
|
rlm@10
|
359
|
rlm@10
|
360 (defmulti join-literal
|
rlm@10
|
361 "Given a database (db), a literal (lit) and a seq of bindings (bs),
|
rlm@10
|
362 return a new seq of bindings by joining this literal."
|
rlm@10
|
363 (fn [db lit bs] (:literal-type lit)))
|
rlm@10
|
364
|
rlm@10
|
365 (defmethod join-literal ::literal
|
rlm@10
|
366 [db lit bs]
|
rlm@10
|
367 (join-literal* db lit bs (fn [binds pt]
|
rlm@10
|
368 (map #(merge binds %)
|
rlm@10
|
369 (map (partial project-onto-literal lit)
|
rlm@10
|
370 (select db (literal-predicate lit) pt))))))
|
rlm@10
|
371
|
rlm@10
|
372 (defmethod join-literal ::negated
|
rlm@10
|
373 [db lit bs]
|
rlm@10
|
374 (join-literal* db lit bs (fn [binds pt]
|
rlm@10
|
375 (if (any-match? db (literal-predicate lit) pt)
|
rlm@10
|
376 nil
|
rlm@10
|
377 [binds]))))
|
rlm@10
|
378
|
rlm@10
|
379 (defmethod join-literal ::conditional
|
rlm@10
|
380 [db lit bs]
|
rlm@10
|
381 (let [each (fn [binds]
|
rlm@10
|
382 (let [resolve (fn [term]
|
rlm@10
|
383 (if (is-var? term)
|
rlm@10
|
384 (binds term)
|
rlm@10
|
385 term))
|
rlm@10
|
386 args (map resolve (:terms lit))]
|
rlm@10
|
387 (if ((:fun lit) args)
|
rlm@10
|
388 binds
|
rlm@10
|
389 nil)))]
|
rlm@10
|
390 (remove nil? (map each bs))))
|
rlm@10
|
391
|
rlm@10
|
392 (defn project-literal
|
rlm@10
|
393 "Project a stream of bindings onto a literal/relation. Returns a new
|
rlm@10
|
394 db."
|
rlm@10
|
395 ([db lit bs] (project-literal db lit bs is-var?))
|
rlm@10
|
396 ([db lit bs var?]
|
rlm@10
|
397 (assert (= (:literal-type lit) ::literal))
|
rlm@10
|
398 (let [rel-name (literal-predicate lit)
|
rlm@10
|
399 columns (-> lit :term-bindings keys)
|
rlm@10
|
400 idxs (vec (get-adorned-bindings (literal-predicate lit)))
|
rlm@10
|
401 db1 (ensure-relation db rel-name columns idxs)
|
rlm@10
|
402 rel (get-relation db1 rel-name)
|
rlm@10
|
403 step (fn [rel bindings]
|
rlm@10
|
404 (let [step (fn [t [k v]]
|
rlm@10
|
405 (if (var? v)
|
rlm@10
|
406 (assoc t k (bindings v))
|
rlm@10
|
407 (assoc t k v)))
|
rlm@10
|
408 tuple (reduce step {} (:term-bindings lit))]
|
rlm@10
|
409 (add-tuple rel tuple)))]
|
rlm@10
|
410 (replace-relation db rel-name (reduce step rel bs)))))
|
rlm@10
|
411
|
rlm@10
|
412
|
rlm@10
|
413 ;; End of file
|