aboutsummaryrefslogtreecommitdiff
path: root/src/clojure/contrib/datalog/literals.clj
blob: 12605160343f657fae920adfa36bbef9bf707e6b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
;;  Copyright (c) Jeffrey Straszheim. All rights reserved.  The use and
;;  distribution terms for this software are covered by the Eclipse Public
;;  License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can
;;  be found in the file epl-v10.html at the root of this distribution.  By
;;  using this software in any fashion, you are agreeing to be bound by the
;;  terms of this license.  You must not remove this notice, or any other,
;;  from this software.
;;
;;  literals.clj
;;
;;  A Clojure implementation of Datalog -- Literals
;;
;;  straszheimjeffrey (gmail)
;;  Created 25 Feburary 2009


(ns clojure.contrib.datalog.literals
  (:use clojure.contrib.datalog.util)
  (:use clojure.contrib.datalog.database)
  (:use [clojure.set :only (intersection)])
  (:use [clojure.contrib.set :only (subset?)])
  (:use [clojure.contrib.seq-utils :only (flatten)]))


;;; Type Definitions

(defstruct atomic-literal
  :predicate              ; The predicate name
  :term-bindings          ; A map of column names to bindings
  :literal-type)          ; ::literal or ::negated

(derive ::negated ::literal)

(defstruct conditional-literal
  :fun                    ; The fun to call
  :symbol                 ; The fun symbol (for display)
  :terms                  ; The formal arguments
  :literal-type)          ; ::conditional


;;; Basics


(defmulti literal-predicate
  "Return the predicate/relation this conditional operates over"
  :literal-type)

(defmulti literal-columns
  "Return the column names this applies to"
  :literal-type)

(defmulti literal-vars
  "Returns the logic vars used by this literal"
  :literal-type)

(defmulti positive-vars
  "Returns the logic vars used in a positive position"
  :literal-type)

(defmulti negative-vars
  "Returns the logic vars used in a negative position"
  :literal-type)

(defmethod literal-predicate ::literal
  [l]
  (:predicate l))

(defmethod literal-predicate ::conditional
  [l]
  nil)

(defmethod literal-columns ::literal
  [l]
  (-> l :term-bindings keys set))

(defmethod literal-columns ::conditional
  [l]
  nil)

(defmethod literal-vars ::literal
  [l]
  (set (filter is-var? (-> l :term-bindings vals))))

(defmethod literal-vars ::conditional
  [l]
  (set (filter is-var? (:terms l))))

(defmethod positive-vars ::literal
  [l]
  (literal-vars l))

(defmethod positive-vars ::negated
  [l]
  nil)

(defmethod positive-vars ::conditional
  [l]
  nil)

(defmethod negative-vars ::literal
  [l]
  nil)

(defmethod negative-vars ::negated
  [l]
  (literal-vars l))

(defmethod negative-vars ::conditional
  [l]
  (literal-vars l))

(defn negated?
  "Is this literal a negated literal?"
  [l]
  (= (:literal-type l) ::negated))

(defn positive?
  "Is this a positive literal?"
  [l]
  (= (:literal-type l) ::literal))


;;; Building Literals

(def negation-symbol 'not!)
(def conditional-symbol 'if)

(defmulti build-literal
  "(Returns an unevaluated expression (to be used in macros) of a
   literal."
  first)

(defn build-atom
  "Returns an unevaluated expression (to be used in a macro) of an
   atom."
  [f type]
  (let [p (first f)
        ts (map #(if (is-var? %) `(quote ~%) %) (next f))
        b (if (seq ts) (apply assoc {} ts) nil)]
    `(struct atomic-literal ~p ~b ~type)))

(defmethod build-literal :default
  [f]
  (build-atom f ::literal))

(defmethod build-literal negation-symbol
  [f]
  (build-atom (rest f) ::negated))

(defmethod build-literal conditional-symbol
  [f]
  (let [symbol (fnext f)
        terms (nnext f)
        fun `(fn [binds#] (apply ~symbol binds#))]
    `(struct conditional-literal
             ~fun
             '~symbol
             '~terms
             ::conditional)))


;;; Display

(defmulti display-literal
  "Converts a struct representing a literal to a normal list"
  :literal-type)

(defn- display
  [l]
  (conj (-> l :term-bindings list* flatten) (literal-predicate l)))

(defmethod display-literal ::literal
  [l]
  (display l))

(defmethod display-literal ::negated
  [l]
  (conj (display l) negation-symbol))

(defmethod display-literal ::conditional
  [l]
  (list* conditional-symbol (:symbol l) (:terms l)))


;;; Sip computation

(defmulti get-vs-from-cs
  "From a set of columns, return the vars"
  :literal-type)

(defmethod get-vs-from-cs ::literal
  [l bound]
  (set (filter is-var?
               (vals (select-keys (:term-bindings l)
                                  bound)))))

(defmethod get-vs-from-cs ::conditional
  [l bound]
  nil)


(defmulti get-cs-from-vs
  "From a set of vars, get the columns"
  :literal-type)

(defmethod get-cs-from-vs ::literal
  [l bound]
  (reduce conj
          #{}
          (remove nil? 
                  (map (fn [[k v]] (if (bound v) k nil))
                       (:term-bindings l)))))

(defmethod get-cs-from-vs ::conditional
  [l bound]
  nil)


(defmulti get-self-bound-cs
  "Get the columns that are bound withing the literal."
  :literal-type)

(defmethod get-self-bound-cs ::literal
  [l]
  (reduce conj
          #{}
          (remove nil?
                  (map (fn [[k v]] (if (not (is-var? v)) k nil))
                       (:term-bindings l)))))

(defmethod get-self-bound-cs ::conditional
  [l]
  nil)


(defmulti literal-appropriate?
  "When passed a set of bound vars, determines if this literal can be
   used during this point of a SIP computation."
  (fn [b l] (:literal-type l)))

(defmethod literal-appropriate? ::literal
  [bound l]
  (not (empty? (intersection (literal-vars l) bound))))

(defmethod literal-appropriate? ::negated
  [bound l]
  (subset? (literal-vars l) bound))

(defmethod literal-appropriate? ::conditional
  [bound l]
  (subset? (literal-vars l) bound))


(defmulti adorned-literal
  "When passed a set of bound columns, returns the adorned literal"
  (fn [l b] (:literal-type l)))

(defmethod adorned-literal ::literal
  [l bound]
  (let [pred (literal-predicate l)
        bnds (intersection (literal-columns l) bound)]
    (if (empty? bound)
      l
      (assoc l :predicate {:pred pred :bound bnds}))))

(defmethod adorned-literal ::conditional
  [l bound]
  l)


(defn get-adorned-bindings
  "Get the bindings from this adorned literal."
  [pred]
  (:bound pred))

(defn get-base-predicate
  "Get the base predicate from this predicate."
  [pred]
  (if (map? pred)
    (:pred pred)
    pred))


;;; Magic Stuff

(defn magic-literal
  "Create a magic version of this adorned predicate."
  [l]
  (assert (-> l :literal-type (isa? ::literal)))
  (let [pred (literal-predicate l)
        pred-map (if (map? pred) pred {:pred pred})
        bound (get-adorned-bindings pred)
        ntb (select-keys (:term-bindings l) bound)]
    (assoc l :predicate (assoc pred-map :magic true) :term-bindings ntb :literal-type ::literal)))

(defn literal-magic?
  "Is this literal magic?"
  [lit]
  (let [pred (literal-predicate lit)]
    (when (map? pred)
      (:magic pred))))
      
(defn build-seed-bindings
  "Given a seed literal, already adorned and in magic form, convert
   its bound constants to new variables."
  [s]
  (assert (-> s :literal-type (isa? ::literal)))
  (let [ntbs (map-values (fn [_] (gensym '?_gen_)) (:term-bindings s))]
    (assoc s :term-bindings ntbs)))


;;; Semi-naive support

(defn negated-literal
  "Given a literal l, return a negated version"
  [l]
  (assert (-> l :literal-type (= ::literal)))
  (assoc l :literal-type ::negated))

(defn delta-literal
  "Given a literal l, return a delta version"
  [l]
  (let [pred* (:predicate l)
        pred (if (map? pred*) pred* {:pred pred*})]
    (assoc l :predicate (assoc pred :delta true))))

        
;;; Database operations

(defn- build-partial-tuple
  [lit binds]
  (let [tbs (:term-bindings lit)
        each (fn [[key val :as pair]]
               (if (is-var? val)
                 (if-let [n (binds val)]
                   [key n]
                   nil)
                 pair))]
    (into {} (remove nil? (map each tbs)))))

(defn- project-onto-literal
  "Given a literal, and a materialized tuple, return a set of variable
   bindings."
  [lit tuple]
  (let [step (fn [binds [key val]]
               (if (and (is-var? val)
                        (contains? tuple key))
                 (assoc binds val (tuple key))
                 binds))]
    (reduce step {} (:term-bindings lit))))
  

(defn- join-literal*
  [db lit bs fun]
  (let [each (fn [binds]
               (let [pt (build-partial-tuple lit binds)]
                 (fun binds pt)))]
    (when (contains? db (literal-predicate lit))
      (apply concat (map each bs)))))

(defmulti join-literal
  "Given a database (db), a literal (lit) and a seq of bindings (bs),
   return a new seq of bindings by joining this literal."
  (fn [db lit bs] (:literal-type lit)))

(defmethod join-literal ::literal
  [db lit bs]
  (join-literal* db lit bs (fn [binds pt]
                             (map #(merge binds %)
                                  (map (partial project-onto-literal lit)
                                       (select db (literal-predicate lit) pt))))))

(defmethod join-literal ::negated
  [db lit bs]
  (join-literal* db lit bs (fn [binds pt]
                             (if (any-match? db (literal-predicate lit) pt)
                               nil
                               [binds]))))

(defmethod join-literal ::conditional
  [db lit bs]
  (let [each (fn [binds]
               (let [resolve (fn [term]
                               (if (is-var? term)
                                 (binds term)
                                 term))
                     args (map resolve (:terms lit))]
                 (if ((:fun lit) args)
                   binds
                   nil)))]
    (remove nil? (map each bs))))
                 
(defn project-literal
  "Project a stream of bindings onto a literal/relation. Returns a new
   db."
  ([db lit bs] (project-literal db lit bs is-var?))
  ([db lit bs var?]
     (assert (= (:literal-type lit) ::literal))
     (let [rel-name (literal-predicate lit)
           columns (-> lit :term-bindings keys)
           idxs (vec (get-adorned-bindings (literal-predicate lit)))
           db1 (ensure-relation db rel-name columns idxs)
           rel (get-relation db1 rel-name)
           step (fn [rel bindings]
                  (let [step (fn [t [k v]]
                               (if (var? v)
                                 (assoc t k (bindings v))
                                 (assoc t k v)))
                        tuple (reduce step {} (:term-bindings lit))]
                    (add-tuple rel tuple)))]
       (replace-relation db rel-name (reduce step rel bs)))))


;; End of file