Clojure backward chaining for facts

212 Views Asked by At

We are using a forward-chaining rule system where we have to spoon-feed the system the data it needs to make a decision. I'd prefer the rule system can ask the questions to obtain the data needed for decisioning. In Jess, this seems possible with the magical "need-" prefix:

(defrule create-member
    (need-member $?)
    =>
    (assert (member A B))) ; eg. DB call to check membership, if needed for goal finding.

(defrule rule-1
    (member ?A ?B)
    =>
    (printout t member crlf))

With this solution, unneeded facts are not fetched. Also, salience could be used to help avoid more costly fact look-up (eg. remote calls), also would keep code DRY

Although Jess appears to solve this, a license-free Clojure solution is preferred. However, it's obscure how to pull this off with say clojure.logic for example. Clara uses forward-chaining, which seems like a no-go, but possibly with rule generation a Jess-like hack is possible?

Really looking for an example of doing similar in Clojure.

1

There are 1 best solutions below

0
AticusFinch On

Just so it is linked from here, someone answered to Joel this very question on Ask Clojure: Ask Clojure: Can core.logic ask questions?

The answer is as follows:

Not being a great minikanren or core.logic user, I was able to coax out a little solution this way:

(ns logos.demo
  (:require [clojure.core.logic :as l]
            [clojure.core.logic.pldb :as pldb]))


;; guilty(X) :-
;; commits(X,Y),
;; crime(Y).
;; crime(murder).
;; crime(theft)

(pldb/db-rel person x)
(pldb/db-rel crime x)
(pldb/db-rel commits x y)

(def facts
  (pldb/db-facts pldb/empty-db
                 [person "bill"]
                 [crime :murder]
                 [crime :theft]
                 [commits "bill" :theft]))

(defn ask! [person act]
  (println  (str "Does " person " commit " act "?"))
  (-> (read-line)
      clojure.string/trim
      clojure.string/lower-case
      #{"y" "yes"}
      some? ))

(defn commitso
  [p act]
  (l/conda
   [(person p) (commits p act)]
   [(l/project [p act]
             (l/==  (ask! p act) true))]))

(defn crimes [name]
  (->> (l/run-db* facts [p c]
                (l/== p name)
                (crime c)
                (commitso p c))
       (map second)))

If you evaluate it at the repl, you should get an interactive prompt if the name is not related to a known person:

logos.demo=> (crimes "bill")
(:theft)
logos.demo=> (crimes "tom")
Does tom commit :theft?  
y
Does tom commit :murder?
y
(:theft :murder)
logos.demo=> (crimes "tom")
Does tom commit :theft?
n
Does tom commit :murder?
n
()

I am sure there is a way to retain facts gathered interactively too (maybe the term is tabling), or ideally, to update the fact database in real time as the search progresses. I am currently unaware though.