clara-rules
clara-rules copied to clipboard
Unbound variable in defquery
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.