I took a shot at forcing conditional inserts in the async handler, if only as an exercise to understand the code better. I got as far as actually getting the insert to occur, basically grabbing the *rule-context*
and making it available to the handler, and adding an overload of fire-rules
that would accept that as an extra parameter. The rule context was modified so it only contained non-empty :batched-logical-insertions
with the facts being inserted as a logical consequence of the rule triggering the async effect. clara.rules.engine/*fire-rules*
then also had an extra arity to deal with externally supplied rule context. At this point, I guessed that this approach probably wouldn't work in general, because it looks to me like the :token
in the rule context contains the fact bindings which are the logical antecedent. If any of those facts had been retracted in the session between the request and response of the async effect, I assume that clara.rules.memory/add-insertions!
would be unhappy, as the token would be inconsistent with the state of working memory. Is that correct?
Yeah. I don’t think that’d be very tractable.
Also working memory is in a mutable state. I think things could easily become inconsistent.
Makes sense.
Is there a way to force a rule to fire, even if the fact values don't change? I'm playing around with using an atom to solve this case, but of course the atom reference doesn't change when the atom is reset, only the wrapped value changes, so the rule won't fire after the first time.
@dave.dixon > Is there a way to force a rule to fire, even if the fact values don’t change? No offense intended at all but that seems off to a bad start even to me…. 😄 What are you trying to do?
Does Clara support namespace aliases in Fact matching? The following throws an exception, but the long-form works:
[encounter-actions/EncounterFindingChanged ..] ;; short -- throws ex
[justice.models.core.actions.encounter.EncounterFindingChanged ..] ;; long
Yeah, it sounds bad. I'm trying to get the rhs to evaluate when a fact refers to an atom and the atom value changes.
Interesting. Why an atom?
I hit a similar issue using a macro in a rule definition.
So I can put the response from an async request in it.
Hm ok. Have you tried calling deref on the atom in the condition
Yes. Doesn't help. In fact, it doesn't work in general for making the bindings, which seems odd.
Hm. Potentially wild things I’d try: 1. (deref a) instead of the macro if you’re not already, 2. :test expression
Have tried both. Deref doesn't help, but the reader macro works in a test and rhs.
@dadair it should. If it doesn’t it probably needs to be looked at.
As an issue I mean
Well I’m confused. @mikerod might be able to shine light on it
@mikerod I'll file an issue, I replicated it with the intro to clara example just moving the defrecords into another ns
@dadair if it’s cljs perhaps it relates to some recent issues already out there. I thought it was fixed though.
nope just .clj
on 0.17.0
Hmm. I’m traveling today so can’t look.
I am not sure I understand still. Been traveling today though so somewhat on and off here
It should definitely support aliasing.
Unless you didn’t include the alias in the ns or something. Hah. If you could make like a small example where it isn’t working that’d be nice to see.
(ns abc.other)
(defrecord SupportRequest [client level])
(defrecord ClientRepresentative [name client])
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(ns abc.core
(:require [clara.rules :refer :all]
[abc.other :as other]))
(defrule is-important
"Find important support requests."
[abc.other.SupportRequest (= :high level)] ;; full path here works; change to other/SupportRequest and an exception is thrown
=>
(println "High support requested!"))
(defrule notify-client-rep
"Find the client representative and request support."
[abc.other.SupportRequest (= ?client client)]
[abc.other.ClientRepresentative (= ?client client) (= ?name name)]
=>
(println "Notify" ?name "that"
?client "has a new support request!"))
(-> (mk-session 'abc.core)
(insert (other/->ClientRepresentative "Alice" "Acme")
(other/->SupportRequest "Acme" :high))
(fire-rules))
the above example reproduces the problem
Record types in clojure are referred to by their Java Class name
You have to use import for those.
ah right
They don’t support ns aliasing. Their builder functions do but not the type class itsel
Sort of a clj quirk that is unfortunate.
Clara is just using clj for the symbol resolution there though.
I think I’d have to see an example here. Hah
I always forget about imports, thanks!
I'll post it when I get home.
Playing around with atoms in facts. Both facts above are maps. The ::specs/Tag
contains one attribute :tags
which is an atom. The ::specs/Time
fact is there just to force the rule to fire when the value wrapped by the :tags
atom is changed. This is the version of the rule that works.
Apply deref in the fact binding does not work, which is surprising. Same effect whether deref
or @
is used. When written this way, the rule just never fires.
This was the original attempt, which doesn't work either, and led to me adding the ::specs/Time
fact. Of course perhaps this doesn't work because there's something fishy about deref
in the LHS, as seen in the previous example. I originally thought it was because it was assumed that if a binding value didn't change, than neither would a function of that bound value. But there seems to be more going on when deref
is involved.
@dave.dixon so in the one that works are there different time facts with different time values?
@alex-dixon I haven't tried multiple time facts. I think it should work correctly with upsert, though. Retracting existing fact would lead to retraction of the generated Tag facts, and the subsequent insert would generate an updated set.
I was hoping to get away from having auxiliary unconditional facts like Time
. The other version of async is structurally similar, but there the external fact is Response
, which is correlated via a rule with the original Request
. There's an additional rule that forces retract of Response
when the corresponding Request
no longer exists.
Given that both approaches require unconditional inserts, the Request
/`Response` pattern is arguably cleaner, as the atom approach requires some extra coordination outside of the rulebase to correctly update the session when the atom value changes.
I’ll definitely keep this in mind as I try to do similar things. Wish I could be more help but it gets hard for me to reason about and I find my thinking tending toward more immutable approaches which seems like the opposite of the overall approach (fewer intermediate facts/garbage). I’ll try to experiment next time I’m at a laptop
Curious, did you try a callback in the rhs and logical insert? Seem to recall that being your first approach. If so what happened? Loss of session context?