Published on

A Scala rule engine

Published in: Technology, Business
  • Be the first to comment

No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide


  1. 1. Hammurabi A Scala rule engineby Mario Fuscomario.fusco@gmail.comtwitter: @mariofusco
  2. 2. "Any fool can write code that acomputer can understand.Good programmers write code thathumans can understand“ Martin Fowler
  3. 3. Programming can be fun,so can cryptography;however they should notbe combined d=document,l=Math.floor,g=[],r=0,z=50,i=[],v=500,y="option ",$=[[],[200,251,299,300,301],[0,49,50,51,99,101,150]];eva l(~o(e,f){f.appendChild(k=d.createElement(e));return k}~m(t,x,y){o(y?y:"button",x);k.appendChild(d.createTextNo de(t));return k}onload=~(){b=d.body;b.style.margin=0;x=(c=b.children[0]) .getContext("2d");o("br",b);c.width=c.height=v;c.onclick=~ (e){n=l(e.clientX/10)+l(e.clientY/10)*z;f(g[n],n)};c=o("se lect",b);m("Empty",c,y);m("Glider",c,y);m("Small Exploder",c,y);(c.onchange=~(){for(a=0;a<z*z;a++)f(0,a,1), f(1,a);for(a in y=$[c.selectedIndex])f(0,y[a]+1075)})();m("Play/Pause",b). onclick=~(){if(r++)r=0,clearTimeout(t);else u()};(j=m("Faster",b)).onclick=m("Slower",b).onclick=~(){v *=this==j?.8:1.25}};~f(b,n,w){s=w?1:2;h=w?8:6;x.fillStyle= (g[n]=!b)?"#000":"#fff";x.fillRect((n%z)*10+s,l(n/z)*10+s, h,h)}~u(){i=g.slice();for(a=0;a<z*z;a++){s=0;for(h=- 1;h<2;h++)for(y=- 1;y<2;y++)n=y*z+a,b=(a+h)%z,s+=(h|y)&n<z*z&0<n&b<z+h&b>=h& i[n+h];f(i[a]?s&6^2:s!=3,a)}t=setTimeout("u()",v)}.replac e(/~/g,function ))
  4. 4. What a rule-based program is• A rule-based program is made up of discrete rules, each of which applies to some subset of the problem• It is simpler, because you can concentrate on the rules for one situation at a time• It can be more flexible in the face of fragmentary or poorly conditioned inputs• Used for problems involving control, diagnosis, prediction, classification, pattern recognition … in short, all problems without clear algorithmic solutions Declarative vs. Imperative
  5. 5. How a rule-based system works
  6. 6. The golfers problem• A foursome of golfers is standing at a tee, in a line from left to right. Each golfer wears different colored pants; one is wearing red pants.• The golfer to Fred’s immediate right is wearing blue pants.• Joe is second in line.• Bob is wearing plaid pants.• Tom isn’t in position one or four, and he isn’t wearing the hideous orange pants.• In what order will the four golfers tee off, and what color are each golfer’s pants?”
  7. 7. The Jess Solution (1)(deftemplate pants-color (slot of) (slot is))(deftemplate position (slot of) (slot is))(defrule generate-possibilities => (foreach ?name (create$ Fred Joe Bob Tom) (foreach ?color (create$ red blue plaid orange) (assert (pants-color (of ?name)(is ?color))) ) (foreach ?position (create$ 1 2 3 4) (assert (position (of ?name)(is ?position))) ) ))
  8. 8. The Jess Solution (2)(defrule find-solution ;; There is a golfer named Fred, whose position is ?p1 ;; and pants color is ?c1 (position (of Fred) (is ?p1)) Shared variables oblige to (pants-color (of Fred) (is ?c1)) have one single BIG rule[……] Uniqueness of colors and positions is spread in all rules ;; Bob is wearing the plaid pants (position (of Bob)(is ?p3&~?p1&~?p&~?p2)) (pants-color (of Bob&~?n)(is plaid&?c3&~?c1&~?c2)) ;; Tom is not in position 1 or 4 ;; and is not wearing orange (position (of Tom&~?n)(is ?p4&~1&~4&~?p1&~?p2&~?p3)) (pants-color (of Tom)(is ?c4&~orange&~blue&~?c1&~?c2&~?c3)))
  9. 9. "Domain users shouldnt be writingcode in our DSL but it must bedesigned for them to understandand validate“ Debasish Ghosh
  10. 10. The only purpose of languages, even programming ones IS COMMUNICATION
  11. 11. The Hammurabi Solution (1) var allPos = (1 to 4).toSet var allColors =class Person(n: String) { Set("blue", "plaid", "red", "orange") val name = n var pos: Int = _ val assign = new { var color: String = _ def position(p: Int) = new {} def to(person: Person) = { person.pos = p allPos = availablePos - p } } def color(c: String) = new { def to(person: Person) = { person.color = c allColors = availableColors - c } } }
  12. 12. The Hammurabi Solution (2)import hammurabi.Rule._val ruleSet = Set( rule ("Unique positions") let { val p = any(kindOf[Person]) when { (availablePos.size equals 1) and (p.pos equals 0) } then { assign position availablePos.head to p } },[……] rule ("Person to Fred’s immediate right is wearing blue pants") let { val p1 = any(kindOf[Person]) val p2 = any(kindOf[Person]) when { (p1.name equals "Fred") and (p2.pos equals p1.pos + 1) } then { assign color "blue" to p2 } })
  13. 13. The Hammurabi Solution (3)val tom = new Person("Tom")val joe = new Person("Joe")val fred = new Person("Fred")val bob = new Person("Bob")val workingMemory = WorkingMemory(tom, joe, fred, bob)RuleEngine(ruleSet) execOn workingMemoryval allPersons = workingMemory.all(classOf[Person])val tom = workingMemory.firstHaving[Person](_.name == "Tom").get
  14. 14. Why an internal DSL?I am lazy o I didnt want to implement a parser o I wanted the Scala compiler to syntactically validate the rulesI wanted Hammurabis users to be lazier than me o No need to learn a new language: its plain Scala o Leverage all the goodies of your favorite IDE like: autocompletion, syntax highligthing, … Scala allows all of us to stay lazy and have a very readable and flexible DSL at the same time
  15. 15. Working with immutable objectscase class Person(name: String, pos: Int = 0, color: String = null)val assign = new { def color(color: String) = new { def to(person: Person) = { remove(person) produce(person.copy(color = color)) availableColors = availableColors - color } } def position(pos: Int) = new { def to(person: Person) = { remove(person) produce(person.copy(pos = pos)) availablePos = availablePos - pos } }}
  16. 16. Exiting with a resultrule("Person to Joe’s immediate right is wearing blue pants") let { val p1 = any(kindOf[Person]) val p2 = any(kindOf[Person]) when { (p1.name equals "Joe") and (p2.pos equals p1.pos + 1) } then { p2.color = "blue“ exitWith(p2) }}val result = RuleEngine(ruleSet).execOn(workingMemory).get
  17. 17. Making evaluation failrule ("Unique positions") let { val p = any(kindOf[Person]) when { (availablePos.size equals 0) and (p.pos equals 0) } then { failWith("No more positions available for " + p.name) }}
  18. 18. Changing rules prioritySometimes you may find that a particular rule should betreated as a special caseA rule that reports a security breach might need to fire immediately … rule ("Important rule") withSalience 10 let { ... }… and on the other hand, a rule that cleans up unused facts mightonly need to run during the idle time rule ("Negligible rule") withSalience -5 let { ... }
  19. 19. Selecting with Boolean functionsrule ("Person to Fred’s immediate right is wearing blue pants") let { val p1 = any(kindOf[Person]) kindOf[Person] having (_.name == "Fred") val p2 = any(kindOf[Person]) when { (p1.name equals "Fred") 1and (p2.pos equals p1.pos + 1) p2.pos equals p1.pos + } then { assign color "blue" to p2 } }
  20. 20. Hammurabi internals Evaluate Rule Engine Rule Evaluator EvaluationFinished (Actor) RuleSet Rule1S Workinga Agenda Memoryl RuleExecutor Rule Evaluatori Evaluate (Actor) RuleExecutore Rule2n RuleExecutor EvaluationFinishedce RuleExecutor Evaluate Rule Evaluator (Actor) EvaluationFinished Rule3
  21. 21. How Hammurabi DSL works (1)case class Rule(description: String, bind: () => RuleDefinition[_], salience: Int = 0)case class RuleDefinition[A](condition: () => Boolean, execution: () => A)def rule(description: String) = new { def let(letClause: => RuleDefinition[_]): Rule = Rule(description, letClause _) def withSalience(salience: Int) = new { def let(letClause: => RuleDefinition[_]): Rule = Rule(description, letClause _, salience) }}rule ("An extremly useful rule") withSalience 5 let { ...}
  22. 22. How Hammurabi DSL works (2)def when(condition: => Boolean) = new { def then[A](execution: => A): RuleDefinition = RuleDefinition(condition _, execution _)}rule("Joe is in position 2") let { val p = any(kindOf[Person]) when { p.name equals "Joe" } then { assign position 2 to p }}def ruleExecution() = { val ruleDef = rule.bind() if (ruleDef.condition()) ruleDef.execution()}
  23. 23. Future enhancements Evaluate use of Scala 2.9 parallel collections instead of actors Improve performances by implementing the RETE algorithm Provide alternative way to select objects from the working memory. For example: produce(person) as VIP rule ("only for Very Important Persons") let { val vip = any(VIP) ... }Any feedback or suggestion is welcome!
  24. 24. Questions? Don’t forget to check out Hammurabi at:http://hammurabi.googlecode.com Thank you!Mario Fuscomario.fusco@gmail.comtwitter: @mariofusco