This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2016-01-21
Channels
- # aatree (88)
- # admin-announcements (14)
- # alda (26)
- # announcements (4)
- # avi (6)
- # aws (7)
- # beginners (80)
- # boot (268)
- # braid-chat (58)
- # cider (4)
- # clara (54)
- # cljs-dev (16)
- # cljsrn (27)
- # clojars (13)
- # clojure (123)
- # clojure-chicago (2)
- # clojure-czech (8)
- # clojure-france (5)
- # clojure-hamburg (2)
- # clojure-miami (6)
- # clojure-nl (5)
- # clojure-russia (285)
- # clojure-spain (2)
- # clojurebridge (3)
- # clojurescript (137)
- # code-reviews (14)
- # community-development (6)
- # core-async (8)
- # core-matrix (10)
- # cursive (2)
- # datascript (1)
- # datomic (24)
- # dirac (2)
- # emacs (5)
- # hoplon (4)
- # incanter (6)
- # jobs (7)
- # ldnclj (42)
- # ldnproclodo (2)
- # leiningen (1)
- # mount (60)
- # off-topic (15)
- # om (134)
- # onyx (65)
- # perun (4)
- # portland-or (2)
- # proton (15)
- # random (1)
- # re-frame (24)
- # reagent (7)
- # testing (4)
- # yada (9)
Hello
I’m new to clara-rules and trying to model a rule with a “must not” condition
like “do not trigger if there is this object present"
is it doable ?
@pguillebert: sure, totally doable.
(defrecord Foo [a])
(defrecord Bar [b])
(defrecord Baz [b])
(defrule foo
[:not [Foo]]
[Bar (= ?b b 3)]
=>
(insert! (->Baz ?b)))
(defquery q:baz []
[?baz <- Baz])
(-> (mk-session [foo q:baz])
(insert-all [;; (->Foo 1)
(->Bar 3)])
(fire-rules)
(query q:baz))
;; => ({:?baz #crede.rules.examples.Baz{:b 3}})
With the same setup, but including a Foo
:
(-> (mk-session [foo q:baz])
(insert-all [(->Foo 1)
(->Bar 3)])
(fire-rules)
(query q:baz))
;; => ()
ok, with :not
(defrule foo
[:exists [Foo]]
[Bar (= ?b b 3)]
=>
(insert! (->Baz ?b)))
(defquery q:baz []
[?baz <- Baz])
(-> (mk-session [foo q:baz])
(insert-all [(->Foo 1)
(->Bar 3)])
(fire-rules)
(query q:baz))
;; => ({:?baz #crede.rules.examples.Baz{:b 3}})
I just have to trust the magic
@pguillebert: it's definitely taken me some getting used to
it's happened more than once now where we wrote some rules, had some initial facts, and then a week or two later realize: "oh, this could be simplified a whole lot"
or "hmm, this record type is too general, we should fan out to multiple types of records in this rule"
ok thanks
@pguillebert: it really helps to keep an up-to-date graph of the logic, how rules are connected
this is really a different way of expressing what I want
yeah ok
@ryanbrush: do you have any thoughts on the following:
(defn group
"Return a generic grouping accumulator. It behaves like
clojure.core/group-by with the exception of specifying the grouping
step.
Parameters:
value-fn - unary function which returns the grouping key.
grouping-fn - binary function which recieves the current key
value the current reduce value and returns a new key value.
combine-fn - function for clara.rules/accumulator :combine-fn.
convert-return-fn - function for clara.rules/accumulator :convert-return-fn."
[{:keys [value-fn grouping-fn combine-fn convert-return-fn]
:or {value-fn identity
combine-fn merge
convert-return-fn identity}}]
{:pre [(ifn? grouping-fn)]}
(clara.acc/accum
{:initial-value {}
:reduce-fn
(fn [m x]
(let [v (value-fn x)]
(update m v grouping-fn x)))
:combine-fn combine-fn
:retract-fn #(throw (RuntimeException. "group retract-fn triggered"))
:convert-return-fn convert-return-fn}))
(defn group-by
"Return an accumulator which behaves like clojure.core/group-by."
[f]
(group
{:value-fn f
:grouping-fn (fnil conj [])
:combine-fn (fn [a b]
(merge-with (comp vec into) a b))}))
(defn group-by
[f]
(clara.acc/reduce-to-accum
(fn [m x]
(let [v (f x)]
(update m v (fnil conj []) x)))
{}
identity
(fn [a b]
(merge-with (comp vec into) a b))))
@devn retract-fn should "undo" the effect of adding an element to the accumulated result. In this case, I think it's the opposite of a group-by step for a single value...which presumably would be finding that value in its underlying group and removing it. Probably not trivial to implement but seems doable.
@ryanbrush: the behavior of group-by with reduce-to-accum seems to be doing what i'm looking for by default. am i crazy?
(defrule foo
[?foo <- (my-group-by :a)
:from [Foo]]
[Bar (= ?b b 3)]
[:test (do (println ?foo)
?foo)]
=>
(println ?foo)
(insert! (->Baz ?foo)))
(defquery q:baz []
[?Qux <- Baz])
(let [f (->Foo 3)]
(-> (mk-session [foo q:baz])
(insert-all [(->Foo 1)
(->Foo 1)
(->Foo 2)
f
(->Bar 3)])
(retract f)
(fire-rules)
(query q:baz)))
prints:
{1 [#myproject.rules.examples.Foo{:a 1} #myproject.rules.examples.Foo{:a 1}], 2 [#myproject.rules.examples.Foo{:a 2}], 3 [#myproject.rules.examples.Foo{:a 3}]}
{1 [#myproject.rules.examples.Foo{:a 1} #myproject.rules.examples.Foo{:a 1}], 2 [#myproject.rules.examples.Foo{:a 2}]}
{1 [#myproject.rules.examples.Foo{:a 1} #myproject.rules.examples.Foo{:a 1}], 2 [#myproject.rules.examples.Foo{:a 2}]}
@devn, no that makes sense. It's probably not the most efficient implementation if you have lots of retracts since it will redo the entire computation. The retract optimization could do something more efficient by removing a single value rather than re-reducing.
Yeah, I wouldn't worry about it unless you have lots of retracts and data, and you can always optimize it later without changing visible behavior.
I think we talked about this awhile back, but any interest in pulling something like this into clara's accumulators?
and honestly, I'm not sure if I'll spend the time there... really, this group-by is just a tad more complicated
it's nice to group-by over a set of facts, and then call a function on the resulting set to bind to ?x
Interesting. It's not a flow we've used directly but makes sense to me. I might actually think about doing so in our own rules.
@ryanbrush: either way i'll send a PR your way with a simple test and we can talk more there
mmmh. apparently this is not working :
[:not [:test (and (= ?ndate ?sdate) (= ?ndes ?sdes))]]
can I negate tests on variables ?
same test applied in a when
in the RHS does what I want (prevent rule firing based on :test)
I mean, a when-not
is this a limitation or am I missing something ?
@pguillebert: does it work if you just (not (and ...))
in a [:test ...]
without the wrapping [:not ...]
?
I just killed my emacs
I didn’t try, will test tomorrow
@pguillebert: my guess is you had an infinite loop
@pguillebert: feel free to gist code in here if you'd like another set of eyes