Fork me on GitHub

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?


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?


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.


Well I’m confused. @mikerod might be able to shine light on it


I am not sure I understand still. Been traveling today though so somewhat on and off here


I think I’d have to see an example here. Hah


I'll post it when I get home.


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


I hit a similar issue using a macro in a rule definition.


@dadair it should. If it doesn’t it probably needs to be looked at.


As an issue I mean


@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


Hmm. I’m traveling today so can’t look.


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))


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.


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 always forget about imports, thanks!


@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?