oracle-samples / clara-rules

Forward-chaining rules in Clojure(Script)

Home Page:http://www.clara-rules.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Unbound variable in defquery

vganshin opened this issue · comments

Hello. Could you assist on an exception I got while playing with clara-rules?

I'm trying to make a simple rule that calculates patient's age and a query which retrieve patients older than certain age. When I run (mk-session 'clara.example) I get an exception which says I have Unbound variables: #{?age} in my query. I don't understand, why is it unbound if it's passed as a parameter.

(ns clara.example
  (:require [clara.rules :refer :all]
            [clj-time.core]
            [clojure.string :as str]))

(defn to-int [n]
  (Integer/parseInt n))

(defn age [birth-date]
  (clj-time.core/in-years
   (clj-time.core/interval
    (apply clj-time.core/date-time
           (mapv
            to-int
            (str/split birth-date #"-")))
    (clj-time.core/now))))

(defrule pt-age
  ["Patient" [pt] (= ?pt pt) (some? (get-in pt [:birthDate]))]
  =>
  (insert! {:resourceType "PatientAge"
            :id (:id ?pt)
            :age (age (:birthDate ?pt))}))


(defquery get-pt-older-than
  [?age]
  [?pt <- "PatientAge" [pt-age] (> (:age pt-age) ?age)])

(def sess
  (-> (mk-session 'clara.example :fact-type-fn :resourceType)
      (insert {:id "pt-1"
               :resourceType "Patient"
               :birthDate "1994-09-26"
               :name [{:given ["John"]
                       :family "Smith"}]})
      (insert {:id "pt-2"
               :resourceType "Patient"
               :birthDate "1990-01-01"
               :name [{:given ["Toto"]
                       :family "Ro"}]})
      (fire-rules)))

(query sess get-pt-older-than :?age 30)
   #:clojure.error{:phase :execution, :line 33, :column 7, :source "example.clj"}
             Compiler.java: 3711  clojure.lang.Compiler$InvokeExpr/eval
             Compiler.java: 3705  clojure.lang.Compiler$InvokeExpr/eval
             Compiler.java: 3705  clojure.lang.Compiler$InvokeExpr/eval
             Compiler.java: 3705  clojure.lang.Compiler$InvokeExpr/eval
             Compiler.java:  457  clojure.lang.Compiler$DefExpr/eval
             Compiler.java: 7186  clojure.lang.Compiler/eval
             Compiler.java: 7640  clojure.lang.Compiler/load
                      REPL:    1  user/eval41266
                      REPL:    1  user/eval41266
             Compiler.java: 7181  clojure.lang.Compiler/eval
             Compiler.java: 7136  clojure.lang.Compiler/eval
                  core.clj: 3202  clojure.core/eval
                  core.clj: 3198  clojure.core/eval
    interruptible_eval.clj:   87  nrepl.middleware.interruptible-eval/evaluate/fn/fn
                  AFn.java:  152  clojure.lang.AFn/applyToHelper
                  AFn.java:  144  clojure.lang.AFn/applyTo
                  core.clj:  667  clojure.core/apply
                  core.clj: 1977  clojure.core/with-bindings*
                  core.clj: 1977  clojure.core/with-bindings*
               RestFn.java:  425  clojure.lang.RestFn/invoke
    interruptible_eval.clj:   87  nrepl.middleware.interruptible-eval/evaluate/fn
                  main.clj:  437  clojure.main/repl/read-eval-print/fn
                  main.clj:  437  clojure.main/repl/read-eval-print
                  main.clj:  458  clojure.main/repl/fn
                  main.clj:  458  clojure.main/repl
                  main.clj:  368  clojure.main/repl
               RestFn.java: 1523  clojure.lang.RestFn/invoke
    interruptible_eval.clj:   84  nrepl.middleware.interruptible-eval/evaluate
    interruptible_eval.clj:   56  nrepl.middleware.interruptible-eval/evaluate
    interruptible_eval.clj:  152  nrepl.middleware.interruptible-eval/interruptible-eval/fn/fn
                  AFn.java:   22  clojure.lang.AFn/run
               session.clj:  202  nrepl.middleware.session/session-exec/main-loop/fn
               session.clj:  201  nrepl.middleware.session/session-exec/main-loop
                  AFn.java:   22  clojure.lang.AFn/run
               Thread.java:  833  java.lang.Thread/run

1. Caused by clojure.lang.ExceptionInfo
   Using variable that is not previously bound. This can happen when an
   expression uses a previously unbound variable, or if a variable is referenced
   in a nested part of a parent expression, such as (or (= ?my-expression
   my-field) ...). Note that variables used in negations are not bound for
   subsequent rules since the negation can never match. Production: {:lhs
   [{:type "PatientAge", :constraints [(> (:age pt-age) ?age)], :args [pt-age],
   :fact-binding :?pt}], :params #{:?age}, :name
   "clara.example/get-pt-older-than"} Unbound variables: #{?age}
   {:production
    {:lhs
     [{:type "PatientAge",
       :constraints [(> (:age pt-age) ?age)],
       :args [pt-age],
       :fact-binding :?pt}],
     :params #{:?age},
     :name "clara.example/get-pt-older-than"},
    :variables #{?age}}

@vganshin,
What version of Clara are you using?

@EthanEChristian the latest one. 0.21.1

Slipped my mind, but the failure above is due to the parameter not being used as a "binding".

Clara's queries assume that the "parameter" should be used as one would use a binding. So something like,

(defquery get-pt-older-than
  [?age]
  [?pt <- "PatientAge" [pt-age] (= (:age pt-age) ?age)])

instead of:

(defquery get-pt-older-than
  [?age]
  [?pt <- "PatientAge" [pt-age] (> (:age pt-age) ?age)])

The reason for this behavior comes down to how Clara "executes" queries, or rather doesn't execute queries.
Rather than having a separate underlying construct for queries, Clara simply sees queries as rules without a RHS.

When a session is queried, rather than running logic for a query, instead Clara is simply asking "memory":

For this value(parameter/s), what satisfied this rule with these bindings?

To support the pattern above, Clara would have to somehow maintain all constraints with references to parameters and then upon request, apply the constraints after the initial facts were returned from memory.