Fork me on GitHub
#clara
<
2017-10-05
>
dg18:10:09

has anyone ever seen an exception from (explain-activations session) when the rules are all working well?

UnsupportedOperationException nth not supported on this type: PersistentArrayMap  clojure.lang.RT.nthFrom (RT.java:947)

dg19:10:53

it seems like explain-activations may just be broken. it expects (:matches explanation) to be a vector of [fact condition] but it's actually a map of {:fact .. :condition ... }

enn19:10:28

I have a clause like this:

[:not
   [:and
    (x (= ?foo foo))
    (y (= ?foo bar))]]
When it fires, I get this error: > Using variable that is not previously bound…. Note that variables used in negations are not bound for subsequent rules since the negation can never match. To my thinking, the y clause is not a subsequent rule since it’s part of the same :and. I want the clause to match cases where this combination of x and y facts doesn’t exist, though an individual x or y fact may exist. How can I express this?

mikerod19:10:03

@enn So Clara (similar to some other rete-based engines) convert the logical structures to DNF (disjunctive normal form). This is typically done to simplify the Rete tree construction and data flow.

mikerod19:10:28

So in this case, it’d be NOT (X AND Y) converted to NOT X OR NOT Y and the OR divides the two clauses into distinct chunks. To deal with this, there is typical special handling for “negated conjunctions”.

mikerod19:10:46

I wouldn’t expect this behavior since Clara has some special handling of a negated conjunction like this.

mikerod19:10:54

Is your rule more complex than just what you posted here?

enn20:10:05

@mikerod yes, a bit more complex--here’s a more accurate version of that clause, and there are also other clauses in the rule:

[:not
   [:and
    (x
     (= ?foo foo))
    (y
     ((set (map :id ?foo)) foo_id)
     (= ?previously_bound_lvar other_id))]]

enn20:10:14

I wonder if the set membership check was messing things up?

enn20:10:54

I ended up working around it by getting all values of x.foo in an accumulator, then doing a :not clause checking those values (if any) against y.foo_id

mikerod20:10:06

> I wonder if the set membership check was messing things up? I don’t think it should

mikerod20:10:16

So the :not clause is not nested within anything else though right?

mikerod20:10:06

I’d have to mess around with that one to see what may be teh issue

mikerod20:10:25

However, https://github.com/cerner/clara-rules/pull/342 could also be related to that sort of logic. There was some edge cases to how it was handled.

mikerod20:10:38

This isn’t the same as the binding not being present in the rule though

enn20:10:29

It’s interesting to hear that it should work. I’ll see if I can come up with a minimal test case, and open a ticket if so.

mikerod20:10:59

that’d be good to see

mikerod20:10:26

(r/mk-session
 [(dsl/parse-query []
                   [[:not
                     [:and
                      [:x (= ?z (:z this))]]]])])

mikerod20:10:46

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 [[:not [:and {:type :x, :constraints [(= ?z (:z this))]}]]], :params #{}}
Unbound variables: #{?z}  clojure.core/ex-info (core.clj:4593)

mikerod20:10:25

That is pretty minimal

mikerod20:10:37

@enn I think the issue is that Clara it not allowing a condition within a negation to introduce a new bound variable

mikerod20:10:14

This gets even more minimal:

(mk-session
 [(dsl/parse-query []
                   [[:not [:x (= ?z (:z this))]]])])
fails due to Unbound variables: #{?z} yet this is ok:
(mk-session
 [(dsl/parse-query []
                   [[:y ( = ?z (:z this))]
                    [:not [:x (= ?z (:z this))]]])])

mikerod20:10:57

The case of a single condition making a bound variable within a negation doesn’t make sense. However, in negated conjunction like in your previous example it does. I think this may be an edge case that Clara isn’t appropriately handling.

mikerod20:10:06

I can log the issue

enn21:10:38

Thank you!

afurmanov21:10:37

Hi, I wonder if I have facts like Shape, Rectangle, Square, what would be recommended way to query all shapes?

afurmanov21:10:10

I am new to Clojure/Clara, and what I am trying to achieve is to make query find-all-shapes to return rectangles as well as squares:

(ns dsl.test
  (:require
    [clara.rules :refer :all]
    ))

(defrecord Shape [center])
(defrecord Rectangle [center width height])
(defrecord Square [center width])


(defquery find-all-shapes
  []
  [?result <- Shape]
  )

(println (-> (mk-session 'dsl.test)
           (insert
             (->Rectangle [1 1] 2 3)
             (->Square [3 3] 5))
           (fire-rules)
           (query find-all-shapes)))

afurmanov21:10:23

From clara documentation I guess I have to make Shape an ancestor of Rectangle and Square, but not sure how to do this and wether it is right direction at all.

mikerod22:10:42

@alex.furmanov Clara’s defaults are to use the type and ancestors fn’s from Clojure

mikerod22:10:07

So you can work it into those in several different ways

mikerod22:10:33

I’m not sure if you are using clj or cljs

mikerod22:10:43

if you are using clj and like defrecord to define your types you could just make a marker interface and use the standard Java type hierarchy

(definterface Shape)
(defrecord Rectangle [center width height] Shape)
<etc>

mikerod22:10:58

You could also utilize Clojure’s derive

mikerod22:10:30

From your example it isn’t completely clear, but I don’t think you actually have facts that are actually the type Shape e.g. (->Shape [1 1]) right?

afurmanov22:10:41

No, the real case is different: facts are indications of some sort of progress.

afurmanov22:10:05

Some could be as simple as counter, others are pairs [invites, money], other type of progress possible

afurmanov22:10:32

Thanks, @mikerod! The (definterface ...) works!

mikerod22:10:35

No problem. Technical note: for Clojure I would choose definterface as the way to mark the record hierarchy just because using defprotocol for it’s underlying generated interface is a bit of a gray area. You could get away with defprotocol as well though. It has edge cases though and it may be better to avoid.

mikerod22:10:12

If you are still interested, what I mean is, e.g.

(defprotocol Shape)

(extend-type String Shape)
Doesn’t mean that String’s are now considered to be Shape’s by the built-in Clojure type hierarchy. Also, the protocol will leave a var by the name of Shape that will clash with the classname produced in your ns and that is confusing as well.

afurmanov22:10:32

I did not know that, thanks for explanations!

afurmanov23:10:28

How to call interface method in LHS constraint?

(definterface IProgress
  (^int counter [])
  (^int customer [])
  )

(defrecord SpecialIProgress [counter_ customer_ details] IProgress
  (counter [this] (+ counter_ 1))
  (customer [this] 1)
  )

(defquery find-all-i-progresses
  []
;;  [?result <- IProgress (= 1 .customer)] ;; DOES NOT WORK
  [?result <- IProgress]
  )

(println (-> (mk-session 'dsl.test)
  (insert
    (->SpecialIProgress 2 1 "2 out of 3"))
  (fire-rules)
  (query find-all-i-progresses)))

afurmanov23:10:56

If I uncomment [?result <- IProgress (= 1 .customer)] line I am getting an error: "Unable to resolve symbol: .customer in this context"

afurmanov23:10:25

Cannot use customer in constraint either, same type of error

afurmanov23:10:36

This: https://clojuredocs.org/clojure.core/definterface#example-5568aadfe4b03e2132e7d175 is saying that "Using an interface, only the dot form of the method is available with defrecord"