Fork me on GitHub

@ryanbrush: Is it possible to use [:or [?foo <- my/accumulator [X (= a 1)]] [?bar <- my/accumulator [Y (= b 2)]]]


@ryanbrush: here's a sketch of what we imagine:

(defrule thing-a
  [?foo <- [:or
            [my/accumulator [Foo (= a 1)]]
            [:not [Foo (= a 1)]]]]
  (when-not (nil? ?foo)
    (insert! (map->Bar ?foo))))


We want Foo to be optional, but when it exists, we'd like to accumulate over matching Foo facts.


The best way to solve this problem is to have my/accumulator provide a default value. That way if there are no Foo records, the default value will be returned from the accumulator and propagate down.


For instance, the provided sum accumulator will return a sum of zero if there are no items to sum.


Funnily enough, we talked about that a bit.


As a side note, you can't quite think of [:or]'s in rules quite like you'd think of or expressions in Clojure. [:or ..] doesn't "return" a value that can be bound to. (In fact, in the Rete algorithm [:or] actually causes two separate rules to be generated for each branch.)


@devn By the way, when fiddling with this I noticed Clara isn't reporting an error if a user attempts to bind a variable in an unsupported location. This is a bug, so I logged it here


@ryanbrush: that is good to know, indeed


@ryanbrush: trying to be respectful of your time here, but another question: with accumulators, can I make a function which produces an accumulator and use that in the LHS?



[Bar (= ?x x)]
[?foo <- (make-accumulator ?x) :from [Foo (= ?y y]]


this basically comes down to wanting to tweak the initial value to the accumulator


@devn Definitely! The accumulators in clara.rules.accumulators are all just functions that return accumulators and should be a good reference. They use constants for the initial value but you can easily pass it as a param.


asking because all of the examples i've seen are def'd and only took the field to compare on


cool, guess I could have tried it, but didn't know calling a function in place of a def'd accumulator had any edges i needed to be aware of


Any function that returns an accumulator will work. The provided ones just happen to use fields.


@ryanbrush: a couple other small bits - Any reason that things like min/max couldn't optionally take a function to coerce the value of a field to be comparable? For instance, dates. - Using reduce-to-accum seems to imply :supports-retract given that min, max etc. call comparison-based, which in turn calls reduce-to-accum with an initial value of nil. Is that true? I remember reading the pull request a couple of times, but are there any good rules of thumb to use when building accumulators to know that you've supported retraction? Now that I write that out, my other question is: When retract is mentioned, is it talking about automatic retraction, explicit retraction, or both?


As long as your accumulator has a retract-fn, it supports retraction. reduce-to-accum is a convenience function that makes it easier to turn general reduce functions into accumulators by (by default) creating a retract-fn that removes the retracted item from the accumulator and re-runs the reduce. In some cases you can create more efficient retract-fn implementations. See, for instance, retract a value from a sum you can just subtract the value, which is of course more efficient than re-running the entire sum with the value removed.


This applies to both automatic and explicit retraction.


Really the retract-fn is just to "undo" the presence of an item in the accumulated value, like subtraction undoes the value in a sum. the reduce-to-accum just offers a "brute force" way to do this for all reduce functions by re-running them. In some very performance-sensitive cases where you don't care about retraction you can skip supporting it, but I wouldn't do so unless you have a special need.


FWIW, Clara accumulators work pretty much exactly like the same concept in Jess and Drools. You just write the functions in Clojure. 😉


(Also, I need to drop offline for a bit but will be back on later today.)


@ryanbrush: that's a lovely explanation, thank you!


(mentioned the @logbot so there's actually a log of some of this stuff, it scans every 2 hours, so it should join shortly.)