oracle-samples/clara-rules

Resolution of symbols in constraints that are both vars in the namespace and fields on the fact

Opened this issue · 2 comments

A user on the Slack channel reported a problem with the following rule where ?prev-step was bound to the var "step", in this case a rule structure, rather than to the field value "step". In this case the var happened to be a rule structure, which was probably confusing since the var wasn't user-defined, but I don't see anything here specific to rule/query vars.

(defrecord CurrentValue [value])
(defrecord CurrentStep [step])
(defrecord CurrentTimestamp [instant])

(clara/defrule step
  [CurrentValue (= ?value value)]
  [CurrentStep (= ?prev-step step)
               (= ?curr-step (int (/ ?value 10)))]
  [CurrentTimestamp (= ?instant instant)]
  [:test (> ?curr-step ?prev-step)]
  =>
  (clara/insert! (->Step ?curr-step ?instant)))

The basic question here to me is what Clara's behavior should be when there are essentially two conflicting "definitions" of the symbol that are not resolved by Clojure's rules on the matter since Clara's field binding in rules is outside such resolution. Possible ways to resolve this include:

  1. The field binding is chosen since it has the most local scope. One way I can see this being confusing is if a fact has lots of fields that the user doesn't really control or even interact with (say a third-party JavaBean).
  2. When such an ambiguity exists, force the user to resolve it, either by qualifying the reference if the desired behavior is that it should refer to the var or getting the field value from the "this" binding of the fact in question manually. This would be unambiguous but possibly tedious for users and would be extra error-handling code to maintain.
  3. Keep the current behavior of resolving to the var. This seems like surprising behavior to me since we're essentially choosing external scope over more local scope.

I think option 1 is the right behavior here, since it's the least surprising and keeps the common case simple.

The drawbacks you mention does give me pause, but I don't think it outweighs the above. Users that do want strong control over this can/should explicitly destructure their facts in their rules, keeping the visible variables fully under their control

+1 to option 1. Keeping as close to Clojure scoping semantics makes the most sense to me.