Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

QUESTION: Dynamic definition of rules? #32

Closed
dobladez opened this issue Nov 20, 2013 · 7 comments
Closed

QUESTION: Dynamic definition of rules? #32

dobladez opened this issue Nov 20, 2013 · 7 comments
Assignees
Milestone

Comments

@dobladez
Copy link

This is a question not an "issue" per se.

Do you have examples of defining rules dynamically (instead of using defrule) ?

Use case:

  • allow users to define some rules via a custom-made UI
  • persist such rules

Thanks! Clara looks great.

@rbrush
Copy link
Contributor

rbrush commented Nov 20, 2013

Hey, Fernando.

There is a mk-rule macro in the clara.rules namespace that does something like what you're looking for. It has to be a macro rather than a function since it re-structures the given S-expressions into compilable functions. I need to improve the documentation, but it takes a vector of left-hand-side conditions and an S-expression for the right hand side. Here's an example from the unit tests. (The example updates an atom, but that's only so the unit test can validate the rule fired. It also accesses the token that fired the rule for testing purposes, but that won't be necessary in most real rules.)

(deftest test-simple-rule
  (let [rule-output (atom nil)
        cold-rule (mk-rule [[Temperature (< temperature 20)]] 
                            (reset! rule-output ?__token__) )

        session (-> (mk-rulebase cold-rule)
                    (mk-session)
                    (insert (->Temperature 10 "MCI"))
                    (fire-rules))]

    (is (= 
         (->Token [(->Temperature 10 "MCI")] {})
         @rule-output))))

You're not the first person with interest in this, though, so I've been thinking of expanding this capability. For instance, one option would be to define the structure of rules using the Prismatic Schema library...providing clear documentation and validation of the structure. We can re-purpose this bug (or create another one) for this sort of thing if it sounds interesting.

Another technique I've used is to create high-level descriptions in a structure that matches your domain, and create the rules at runtime. So rather than persisting the rules themselves, you're persisting some description of the logic tailored to your domain, which can then be easily converted to rules. I have no idea whether such a practice is applicable for your use case, but it seems effective for some problems.

Hope this helps!
-Ryan

@ghost ghost assigned rbrush Nov 30, 2013
@rbrush
Copy link
Contributor

rbrush commented Nov 30, 2013

Alright, I think I'm going to do something along these lines for the next release (0.4.0, excluding bug fixes to the 0.3 stream).

I'm considering creating Clojure structure (possibly using Prismatic's schema to define and validate it) to allow callers to define their own rules by simply creating instances of that structure for each rule.

{
:name "example-rule"
:lhs '[[Temperature (< temperature 20)]]
:rhs '(println "It's cold out")
}

Note the items are quoted so they can be saved and manipulate in a simple file. They will be evaled at runtime to create the Rete network.

This should support the ability to create rules with arbitrary tooling, and also the ability to display and manipulate rule sets (such as showing rule interdependencies), which has some interesting use cases.

@dobladez
Copy link
Author

Thanks Ryan for your prompt response... I didn't reply to your first response 'cause I couldn't yet spend enough time to play with it. I hope to do so this weekend.

I like what you propose for the next milestone... it fits perfectly with the use cases I need to cover.

@mrrodriguez
Copy link
Collaborator

Are these changes in the 0.4.0-SNAPSHOT version? Does your model support all of the rule constructs that are present in Clara, e.g. accumulators, nested conditional expressions, negation, etc?
I guess I should start digging around to find out.

Just wanted to point out, I really like the idea of exposing Clara rules structures as a data model like you described above. That seems to add a ton of flexibility to how rules can be created/generated/analyzed from arbitrary tooling like you said.

I think this goes well with the idea (1) of building a strong information model (machine-friendly) that you can build a (or multiple) simple DSL(s) on top of, rather than have a DSL layer (human-friendly) with a hidden information model.

+1 to this!

(1) By "the idea" I am pulling mostly from a talk by Rich Hickey (can't remember which one) where he discusses SQL only having a human-friendly API (this is a gross over-simplification) and Stuart Halloway in his talk "Narcissistic Design", where they both bring up the value of having a solid information model that APIs and DSLs can be built on top of.

@rbrush
Copy link
Contributor

rbrush commented Dec 6, 2013

Hey Mike,

These changes aren't in the snapshot version yet, but I should have some time to dig into them soon. I'll try to get a workable snapshot build of this up within the next week.

I appreciate and agree with the thoughts. I hadn't made the connection with those thoughts from Rich or Stu, but I like it.

@rbrush
Copy link
Contributor

rbrush commented Dec 31, 2013

So this was more involved than I thought, but I have a preview branch that refactors Clara's internals to work purely in Schema-defined structures. Documentation and examples will be forthcoming, but you can look at the schema at [1]. defrule and defquery now simply create Production or Query structures that match this schema, and mk-session simply accepts a sequence of maps that follow this schema as well.

Below is a simple interactive session where I create a simple rule, and can see the schema-defined structure by simply pretty printing the var the rule creates. An arbitrary program could create and store this structure independently and pass it to mk-session, without even using defrule if desired.

user> (defrule cold-and-wind
  "Rule to determine whether it is indeed cold and windy."

  [Temperature (< temperature 20) (== ?t temperature)]
  [WindSpeed (> windspeed 30) (== ?w windspeed)]
  =>
  (insert! (->ColdAndWindy ?t ?w)))
#'user/cold-and-wind
user> (pprint cold-and-wind)
{:lhs
 [{:constraints [(< temperature 20) (== ?t temperature)],
   :type clara.rules.testfacts.Temperature}
  {:constraints [(> windspeed 30) (== ?w windspeed)],
   :type clara.rules.testfacts.WindSpeed}],
 :rhs (clara.rules/insert! (->ColdAndWindy ?t ?w))}
nil
user> 

One note: I temporarily removed ClojureScript support, with reasons and a plan to reintroduce it described in issue #34. A snapshot build of this is available at [2]. Feel free to grab and experiment with this...I think the schema is pretty closed to being final, although there's a lot of cleanup and documentation to be done here.

[1]
https://github.com/rbrush/clara-rules/blob/0.4-preview/src/main/clojure/clara/rules/schema.clj
[2]
https://oss.sonatype.org/content/repositories/snapshots/org/toomuchcode/clara-rules/0.4.0-alpha1-SNAPSHOT/

@rbrush
Copy link
Contributor

rbrush commented Feb 7, 2014

The functionality described here is release in 0.4 and described at the link below. Marking as closed.

http://www.toomuchcode.org/blog/2014/01/19/rules-as-data/

@rbrush rbrush closed this as completed Feb 7, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants