This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-11-09
Channels
- # announcements (14)
- # architecture (42)
- # babashka (23)
- # beginners (37)
- # biff (8)
- # calva (2)
- # cider (3)
- # clara (42)
- # clerk (14)
- # clojure (55)
- # clojure-brasil (3)
- # clojure-dev (5)
- # clojure-europe (18)
- # clojure-hungary (88)
- # clojure-losangeles (3)
- # clojure-nl (1)
- # clojure-norway (66)
- # clojure-uk (9)
- # clojurescript (16)
- # core-logic (16)
- # datomic (6)
- # fulcro (32)
- # hyperfiddle (25)
- # instaparse (12)
- # joyride (2)
- # lsp (13)
- # malli (15)
- # off-topic (50)
- # polylith (11)
- # portal (3)
- # re-frame (2)
- # reitit (2)
- # sci (8)
- # shadow-cljs (16)
- # tools-deps (13)
- # xtdb (5)
How do I write the rule below? Kit has a list of pieces, I want this to trigger when all the pieces of the Kit are available.
(defrecord Kit [pieces])
(defrecord Piece [kind])
(r/defrule complete-kit-rule
[Kit (= ?pieces pieces)]
[?assembly <- (acc/all) :from [Piece (= ?kind kind) (some #(= ?kind %) ?pieces)]]
=>
(prn "complete kit"))
(-> (r/mk-session)
(r/insert (->Kit [:a :c]))
(r/insert (->Piece :a))
(r/insert (->Piece :c))
(r/fire-rules))
This fires for each piece (when the kit isn’t actually complete). Adding the following test makes the rule not fire at all:
[:test (= (count ?pieces) (count ?assembly))]
How is Kit obtaining more pieces over time? That part confused me in the problem statement
However it seems like one approach would be for the RHS to insert an intermediate fact that combined a Kit with its Pieces as done in your rule here. But it also would have a new field/flag indicating if it was “complete”. You’d have then another rule that operated on complete flagged facts to do whatever it is.
@U0LK1552A The Pieces are getting inserted from other rules. I want the Kit to be complete when it has all it’s pieces. The kit length won’t change, but could be various size.
What I find odd is that the rule does not include both pieces in the assembly I get this
"complete kit" [#Piece{:kind :a}]
"complete kit" [#Piece{:kind :c}]
So @U0LK1552A not sure how I could signal a complete kit.@U3KC48GHW I’m unclear on your point about “group-by”. Why aren’t both pieces part of the accumulator?
?kind is a new binding in the accumulator, the reason its logged twice in the comment above is because “kind” differs on the two pieces. https://www.clara-rules.org/docs/accumulators/ > Note that we accumulate once per distinct set of bindings. So, for example, if we have temperatures from 3 different locations, the rule get-current-temperature above will fire three times, once per distinct location.
> But doesn’t it return all the WindSpeeds for a location? Not just 1? yes, it will return all wind speeds for a location and execute the RHS for each location. Similarly, your rule will collect all pieces by kind and execute the RHS per kind
I want the same behavior as Windspeed, but it gives a max, I want all (the ones in the kit list).
I think that some
condition is saying, yeah, i got one for you, but i need to filter in the reducer fn.
Yes! That did it, I suspected the (= ?kind kind) was superfluous but it changed the logic, didn’t realize that.
If you are using normal insert!
(truth maintenance system maintained), your RHS facts would reflect the eventual final state of the working memory
And then a flag could be added to the result type to distinguish it from incompletes in other rules. However, for your specific case, now that I see what you’re doing more, I think the non-grouping acc/all
+ a :test
seems to capture what you want.
In a similar vein, is it possible to iterate over the list of pieces? Say, instead I want to create each piece (insert) if it doesn’t exist that is in the Kit. Something like:
[?each <- (acc/all) :from [Kit (= pieces ?pieces)]]
[:not (Piece (= kind ?each))]
=>
(insert (->Piece ?each))
I think the obvious thing, falls prey to this:
(r/defrule pair-kit-pieces
[Kit (= pieces ?kit-pieces)]
[?pieces <- (acc/all) :from [Piece (some #(= kind %) ?kit-pieces)]]
=>
(let [matched-pieces (set ?pieces)]
(insert (map->KitPieces {:pieces ?pieces
:missing-pieces (filterv matched-pieces ?kit-pieces)}))))
(r/defrule add-missing-kit-pieces
[KitPieces (= ?missing-pieces missing-pieces)]
=>
(r/insert-all! (for [missing ?missing-pieces]
(->Piece missing))))
One way out of that, is to use a different fact for a “generated piece” (aka automatically added) vs a regular piece:
(r/defrule pair-kit-pieces
[Kit (= pieces ?kit-pieces)]
[?pieces <- (acc/all) :from [Piece (some #(= kind %) ?kit-pieces)]]
=>
(let [matched-pieces (set ?pieces)]
(insert (map->KitPieces {:pieces ?pieces
:missing-pieces (filterv matched-pieces ?kit-pieces)}))))
(r/defrule add-missing-kit-pieces
[KitPieces (= ?missing-pieces missing-pieces)]
=>
(r/insert-all! (for [missing ?missing-pieces]
(->GeneratedPiece missing))))
(r/defrule assemble-kit
[KitPieces (= ?pieces pieces)]
[?matched-pieces <- (acc/all) :from [Piece (some #(= kind %) ?pieces)]]
[?gen-pieces <- (acc/all) :from [GeneratedPiece (some #(= kind %) ?pieces)]]
=>
(r/insert! (->Assembled (into (vec ?matched-pieces)
?gen-pieces))))
I think more could be done here that I’d prefer to clean up. Such as not having a Kit
fact have all the pieces collected forcing us to keep using accumulators.
Instead it’d be nice if Kit
had an :id
of some sort and we could decompose it into individual KitRequiredPiece <id> <kind>
facts.
Not 100% necessary though. I’d probably at least abstract (some #(= kind %) ?pieces)
fn though (although the overhead is still there)
eg.
(r/defrule pair-kit-pieces
[Kit (= pieces ?kit-pieces) (= ?id id)]
[?pieces <- (acc/all) :from [Piece (some #(= kind %) ?kit-pieces)]]
=>
(let [matched-pieces (set ?pieces)]
(insert (map->KitPieces {:id ?id
:pieces ?pieces
:missing-pieces (filterv matched-pieces ?kit-pieces)}))))
(r/defrule add-missing-kit-pieces
[KitPieces (= ?missing-pieces missing-pieces)]
=>
(r/insert-all! (for [missing ?missing-pieces]
(map->GeneratedPiece {:kit-id ?id
:kind missing}))))
(r/defrule assemble-kit
[KitPieces (= ?pieces pieces) (= ?kit-id id)]
[?matched-pieces <- (acc/all) :from [Piece (some #(= kind %) ?pieces)]]
[?gen-pieces <- (acc/all) :from [GeneratedPiece (= ?kit-id kit-id)]]
=>
(r/insert! (map->Assembled {:kit-id ?kit-id
:pieces (into (vec ?matched-pieces)
?gen-pieces)})))
Basically, having something like an “id” helps be more “database”-like, which I think rules typically are.That’s the “conflict” I was struggling a bit with. If I want a rule to fire more than once it’s like I need a separate fact for it versus trying to use accumulators somehow. I could also insert these separate before fire-rules as well in my case. But I think in any case, you’ve definitely delineated the solution space. Thanks.
• https://gist.github.com/mrrodriguez/09df52e5f9729992c61f21fc638a20e6 • https://gist.github.com/mrrodriguez/6a6f8373b25d69826b3efe154c928fac Not the exact same topic but a related topics conceptually. Maybe helpful to see a similar idea template.
And even further removed but still related: https://www.metasimple.org/2017/12/23/clara-updating-facts.html