Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unbound variable in defquery #470

Open
vganshin opened this issue Nov 9, 2021 · 3 comments
Open

Unbound variable in defquery #470

vganshin opened this issue Nov 9, 2021 · 3 comments

Comments

@vganshin
Copy link

vganshin commented Nov 9, 2021

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}}
@EthanEChristian
Copy link
Contributor

@vganshin,
What version of Clara are you using?

@vganshin
Copy link
Author

@EthanEChristian the latest one. 0.21.1

@EthanEChristian
Copy link
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants