Hammurabi

25
Hammurabi A Scala rule engine by Mario Fusco [email protected] twitter: @mariofusco

description

A Scala rule engine

Transcript of Hammurabi

Page 1: Hammurabi

HammurabiA Scala rule engine

by Mario Fusco [email protected] twitter: @mariofusco

Page 2: Hammurabi

"Any fool can write code that a

computer can understand.

Good programmers write code that

humans can understand“

Martin Fowler

Page 3: Hammurabi

Programming can be fun,

so can cryptography;

however they should not

be 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]];eval('~o(e,f){f.appendChild(k=d.createElement(e));return k}~m(t,x,y){o(y?y:"button",x);k.appendChild(d.createTextNode(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("select",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)}'.replace(/~/g,'function '))

Page 4: Hammurabi

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

Page 5: Hammurabi

How a rule-based system works

Page 6: Hammurabi

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?”

Page 7: Hammurabi

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))))

))

Page 8: Hammurabi

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))(pants-color (of Fred) (is ?c1))

[……]

;; 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))

)

Uniqueness of colors and

positions is spread in all rules

Shared variables oblige to

have one single BIG rule

Page 9: Hammurabi

"Domain users shouldn't be writing

code in our DSL but it must be

designed for them to understand

and validate“

Debasish Ghosh

Page 10: Hammurabi

The only purpose of languages,

even programming ones

IS COMMUNICATION

Page 11: Hammurabi
Page 12: Hammurabi

The Hammurabi Solution (1)var allPos = (1 to 4).toSetvar allColors =

Set("blue", "plaid", "red", "orange")

val assign = new {def position(p: Int) = new {

def to(person: Person) = {person.pos = pallPos = availablePos - p

}}

def color(c: String) = new {def to(person: Person) = {

person.color = callColors = availableColors - c

}}

}

class Person(n: String) {val name = nvar pos: Int = _var color: String = _

}

Page 13: Hammurabi

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

}}

)

Page 14: Hammurabi

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 workingMemory

val allPersons = workingMemory.all(classOf[Person])

val tom = workingMemory.firstHaving[Person](_.name == "Tom").get

Page 15: Hammurabi

Why an internal DSL?

� I am lazyo I didn't want to implement a parser

o I wanted the Scala compiler to syntactically validate the rules

� I wanted Hammurabi's users to be lazier than meo No need to learn a new language: it's 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

Page 16: Hammurabi

Working with immutable objects

case 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

}}

}

Page 17: Hammurabi

Exiting with a result

rule("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

Page 18: Hammurabi

Making evaluation fail

rule ("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)

}}

Page 19: Hammurabi

Changing rule's priority

Sometimes you may find that a particular rule should be

treated as a special case

rule ("Important rule") withSalience 10 let { ... }

A rule that reports a security breach might need to fire immediately …

rule ("Negligible rule") withSalience -5 let { ... }

… and on the other hand, a rule that cleans up unused facts might

only need to run during the idle time

Page 20: Hammurabi

Selecting with Boolean functions

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}

}

kindOf[Person] having (_.name == "Fred")

p2.pos equals p1.pos + 1

Page 21: Hammurabi

Hammurabi internals

Working

Memory

Rule1

Rule Evaluator

(Actor)

Rule2

Rule Evaluator

(Actor)

Rule3

Rule Evaluator

(Actor)

Evaluate

EvaluationFinished

Rule Engine

Agenda

RuleExecutor

RuleExecutor

RuleExecutor

RuleExecutor

S

a

l

i

e

n

c

e

Evaluate

EvaluationFinished

Evaluate

EvaluationFinished

RuleSet

Page 22: Hammurabi

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 {...

}

Page 23: Hammurabi

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()

}

Page 24: Hammurabi

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!

Page 25: Hammurabi

Mario Fusco [email protected] twitter: @mariofusco

Questions?

Don’t forget to check out Hammurabi at:

http://hammurabi.googlecode.com

Thank you!