4. Goals of DSLs
• Use a more expressive language than a
general-purpose one
• Share a common metaphor of
understanding between developers and
subject matter experts
• Have domain experts help with the design
of the business logic of an application
• Avoid cluttering business code with
boilerplate technical code thanks to a
clean separation
• Let business rules have their own lifecycle
5. Why Scripting DSLs
package org.drools.examples.golfing;
dialect "mvel"
import org.drools.examples.golfing.GolfingExample.Golfer;
rule "find solution"
when
// Bob is wearing plaid pants
$bob : Golfer( name == "Bob", color == "plaid")
// ...
then
System.out.println( "Bob " + $bob.getColor() );
end
package org.drools.examples.golfing;
dialect "mvel"
import org.drools.examples.golfing.GolfingExample.Golfer;
rule "find solution"
when
Bob is wearing plaid pants
// ...
then
Display all details
end
when Bob is wearing plaid pants display all details
Compile time
translation
Compile time
or runtime
translation
30. Groovy provides
• A flexible and malleable syntax
– scripts, native syntax constructs (list, map,
ranges)
• Closures, less punctuation...
– Compile-time and runtime meta-programming
– metaclasses, AST transformations
– also operator overloading
• The ability to easily integrate into Java,
app’n server apps
– compile into bytecode or leave in source form
– also security and safety
35. Rules engines
• Backward chaining starts with a list of goals (or a
hypothesis) and works backwards applying rules to
derive new hypotheses until available data is found
to support the hypotheses or all rules and data have
been exhausted
– http://en.wikipedia.org/wiki/Backward_chaining
• Forward chaining starts with the available facts and
applies rules to derive or infer more facts until a
goal is reached
– http://en.wikipedia.org/wiki/Forward_chaining
36. Backward chaining example
• Rules
– If X croaks and eats flies – Then X is a frog
– If X chirps and sings – Then X is a canary
– If X is a frog – Then X is green
– If X is a canary – Then X is yellow
• Facts
– Fritz croaks
– Fritz eats flies
– Tweety eats flies
– Tweety chirps
– Tweety is yellow
Who is a frog?
? is a frog
Based on rule 1, the computer can derive:
2. ? croaks and eats flies
Based on logic, the computer can derive:
3. ? croaks and ? eats flies
Based on the facts, the computer can derive:
4. Fritz croaks and Fritz eats flies
37. Forward chaining example
• Rules
– If X croaks and eats flies – Then X is a frog
– If X chirps and sings – Then X is a canary
– If X is a frog – Then X is green
– If X is a canary – Then X is yellow
• Facts
– Fritz croaks
– Fritz eats flies
– Tweety eats flies
– Tweety chirps
– Tweety is yellow
Who is a frog?
1. Fritz croaks and Fritz eats flies
Based on logic, the computer can derive:
2. Fritz croaks and eats flies
Based on rule 1, the computer can derive:
3. Fritz is a frog
38. Drools Expert Golfing Example…/*
* Copyright 2010 JBoss Inc
* Licensed under the Apache License…
*/
package org.drools.examples.golfing;
import org.drools.KnowledgeBase;
import org.drools.KnowledgeBaseFactory;
import org.drools.builder.KnowledgeBuilder;
import org.drools.builder.KnowledgeBuilderFactory;
import org.drools.builder.ResourceType;
import org.drools.io.ResourceFactory;
import org.drools.runtime.StatefulKnowledgeSession;
public class GolfingExample {
/**
* @param args
*/
public static void main(final String[] args) {
final KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
kbuilder.add(ResourceFactory.newClassPathResource("golf.drl",
GolfingExample.class),
ResourceType.DRL);
final KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
kbase.addKnowledgePackages(kbuilder.getKnowledgePackages());
final StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();
String[] names = new String[]{"Fred", "Joe", "Bob", "Tom"};
String[] colors = new String[]{"red", "blue", "plaid", "orange"};
int[] positions = new int[]{1, 2, 3, 4};
for (int n = 0; n < names.length; n++) {
for (int c = 0; c < colors.length; c++) {
for (int p = 0; p < positions.length; p++) {
ksession.insert(new Golfer(names[n],
colors[c],
positions[p]));
}
}
}
// ...
// ...
ksession.fireAllRules();
ksession.dispose();
}
public static class Golfer {
private String name;
private String color;
private int position;
public Golfer() { }
public Golfer(String name,
String color,
int position) {
super();
this.name = name;
this.color = color;
this.position = position;
}
/**
* @return the color
*/
public String getColor() {
return this.color;
}
/**
* @return the name
*/
public String getName() {
return this.name;
}
/**
* @return the name
*/
public int getPosition() {
return this.position;
}
}
}
42. @Grab…
• Set up dependencies as per Java
– E.g. manually add to classpath or use
Maven/Gradle/Ant or rely on IDE features
• Or @Grab declares dependencies inline
– Makes scripts environment independent
– Downloads transient dependencies as needed
– (Uncomment in github examples)
//@GrabResolver('https://repository.jboss.org/nexus/content/groups/public-jboss/')
//@Grab('org.drools:drools-compiler:5.5.0.Final')
//@Grab('org.drools:drools-core:5.5.0.Final')
//@Grab('com.sun.xml.bind:jaxb-xjc:2.2.5.jboss-1;transitive=false')
//@Grab('com.google.protobuf:protobuf-java:2.4.1')
//@Grab('org.slf4j:slf4j-simple:1.6.4')
import groovy.transform.Immutable
import org.drools.builder.ResourceType
import static org.drools.KnowledgeBaseFactory.newKnowledgeBase
import static org.drools.builder.KnowledgeBuilderFactory.newKnowledgeBuilder
import static org.drools.io.ResourceFactory.newClassPathResource
def kbuilder = newKnowledgeBuilder()
// ...
43. …@Grab…
> groovy -classpath C:ProjectsGroovyProblemSolversoutproductionDroolsExpert;
C:Userspaulk.groovygrapesorg.droolsdrools-compilerjarsdrools-compiler-5.3.3.Final.jar;
C:Userspaulk.groovygrapesorg.antlrantlr-runtimejarsantlr-runtime-3.3.jar;
C:Userspaulk.groovygrapesorg.antlrantlrjarsantlr-3.3.jar;
C:Userspaulk.groovygrapesorg.antlrstringtemplatejarsstringtemplate-3.2.1.jar;
C:Userspaulk.groovygrapesantlrantlrjarsantlr-2.7.7.jar;
C:Userspaulk.groovygrapesorg.eclipse.jdt.core.compilerecjjarsecj-3.5.1.jar;
C:Userspaulk.groovygrapesorg.mvelmvel2jarsmvel2-2.1.0.drools16.jar;
C:Userspaulk.groovygrapescom.sun.xml.bindjaxb-xjcjarsjaxb-xjc-2.2.5.jboss-1.jar;
C:Userspaulk.groovygrapescom.sun.xml.bindjaxb-impljarsjaxb-impl-2.2.5.jboss-1.jar;
C:Userspaulk.groovygrapesjavax.xml.bindjaxb-apijarsjaxb-api-2.2.6.jar;
C:Userspaulk.groovygrapescom.sun.istackistack-commons-runtimejarsistack-commons-runtime-2.6.1.jar;
C:Userspaulk.groovygrapesjavax.xml.streamstax-apijarsstax-api-1.0-2.jar;
C:Userspaulk.groovygrapesjavax.activationactivationjarsactivation-1.1.jar;
C:Userspaulk.groovygrapescom.sun.xml.txw2txw2jarstxw2-20110809.jar;
C:Userspaulk.groovygrapesrelaxngDatatyperelaxngDatatypejarsrelaxngDatatype-20020414.jar;
C:Userspaulk.groovygrapescom.sun.codemodelcodemodeljarscodemodel-2.6.jar;
C:Userspaulk.groovygrapescom.sun.xml.dtd-parserdtd-parserjarsdtd-parser-1.1.jboss-1.jar;
C:Userspaulk.groovygrapescom.sun.istackistack-commons-toolsjarsistack-commons-tools-2.6.1.jar;
C:Userspaulk.groovygrapesorg.apache.antantjarsant-1.7.0.jar;
C:Userspaulk.groovygrapesorg.apache.antant-launcherjarsant-launcher-1.7.0.jar;
C:Userspaulk.groovygrapesorg.kohsuke.rngomrngomjarsrngom-201103.jboss-1.jar;
C:Userspaulk.groovygrapescom.sun.xsomxsomjarsxsom-20110809.jar;
C:Userspaulk.groovygrapesxml-resolverxml-resolverjarsxml-resolver-1.1.jar;
C:Userspaulk.groovygrapesorg.droolsdrools-corejarsdrools-core-5.3.3.Final.jar;
C:Userspaulk.groovygrapesorg.droolsknowledge-apijarsknowledge-api-5.3.3.Final.jar;
C:ProjectsGroovyProblemSolversDroolsExpertsrcresources GolfExample.groovy
Or you can precompile:
> groovyc -classpath ... GolfExample.groovy
> jar ...
Then use groovy or java commands to run.
Old school
46. golf.drl…
dialect "mvel"
import Golfer;
rule "find solution"
when
// There is a golfer named Fred,
$fred : Golfer( name == "Fred" )
// Joe is in position 2
$joe : Golfer( name == "Joe",
position == 2,
position != $fred.position,
color != $fred.color )
// Bob is wearing plaid pants
$bob : Golfer( name == "Bob",
position != $fred.position,
position != $joe.position,
color == "plaid",
color != $fred.color,
color != $joe.color )
// ...
47. …golf.drl
// Tom isn't in position 1 or 4
// and isn't wearing orange
$tom : Golfer( name == "Tom",
position != 1,
position != 4,
position != $fred.position,
position != $joe.position,
position != $bob.position,
color != "orange",
color != $fred.color,
color != $joe.color,
color != $bob.color )
// The golfer to Fred's immediate right
// is wearing blue pants
Golfer( position == ( $fred.position + 1 ),
color == "blue",
this in ( $joe, $bob, $tom ) )
then
System.out.println("Fred " + $fred.getPosition() + " " + $fred.getColor());
System.out.println("Joe " + $joe.getPosition() + " " + $joe.getColor());
System.out.println("Bob " + $bob.getPosition() + " " + $bob.getColor());
System.out.println("Tom " + $tom.getPosition() + " " + $tom.getColor());
end
49. Tortoises & Cranes
• Around a pond dwell tortoises and cranes
• There are 7 animals in total
• There are 20 legs in total
• How many of each animal are there?
Source: http://www.youtube.com/watch?v=tUs4olWQYS4
50. Tortoises & Cranes: Choco…
//@GrabResolver('http://www.emn.fr/z-info/choco-repo/mvn/repository')
//@Grab('choco:choco-solver:2.1.5')
import static choco.Choco.*
import choco.cp.model.CPModel
import choco.cp.solver.CPSolver
def m = new CPModel()
def s = new CPSolver()
def totalAnimals = 7
def totalLegs = 20
def c = makeIntVar('Cranes', 0, totalAnimals)
def t = makeIntVar('Tortoises', 0, totalAnimals)
m.addConstraint(eq(plus(c, t), totalAnimals))
m.addConstraint(eq(plus(mult(c, 2), mult(t, 4)), totalLegs))
s.read(m)
def more = s.solve()
while (more) {
println "Found a solution:"
[c, t].each {
def v = s.getVar(it)
if (v.val) println " $v.val * $v.name"
}
more = s.nextSolution()
}
Found a solution:
4 * Cranes
3 * Tortoises
51. …Tortoises & Cranes: Choco
import static choco.Choco.*
import choco.cp.model.CPModel
import choco.cp.solver.CPSolver
import choco.kernel.model.variables.integer.IntegerVariable
def m = new CPModel()
def s = new CPSolver()
def totalAnimals = 7
def totalLegs = 20
def c = makeIntVar('Cranes', 0, totalAnimals)
def t = makeIntVar('Tortoises', 0, totalAnimals)
IntegerVariable[] animals = [c, t]
m.addConstraint(eq(plus(c, t), totalAnimals))
m.addConstraint(eq(scalar(animals, [2, 4] as int[]), totalLegs))
s.read(m)
def more = s.solve()
while (more) {
println "Found a solution:"
animals.each {
def v = s.getVar(it)
if (v.val) println " $v.val * $v.name"
}
more = s.nextSolution()
}
Slight variant
using scalars.
Well suited to
scaling to
more animals
58. …Tortoises & Cranes: DSL
…
cranes have 2 legs
tortoises have 4 legs
//millipedes have 1000 legs
there are 7 animals
//there are 8 animals
there are 20 legs
//there are 1020 legs
new GroovyShell([animals: animalProps] as Binding).evaluate(
animalProps.collect { key, val ->
def capKey = key.capitalize()
"""
@groovy.transform.Immutable
class $capKey {
static int numLegs = $val
int quantity
}
"""
}.join('n') + "Solver.main(animals, $props.totalAnimals, $props.totalLegs, getClass().classLoader)"
)
Cranes 4
Tortoises 3
Cranes 4
Tortoises 3
Millipedes 1
81. Discussion points
• Choosing granularity
• Choosing the level of dynamic/static typing
• Multi-paradigm solutions
• Capturing Rule Design Patterns using AST
transforms
82. Granularity
Solve manners2009
Neighbours must share a hobby
Neighbours are of a different gender
There should be 2 doctors at each table
Each doctor at a table should be a different kind
...
The Guest at position 2 on table 1 should have a
different gender to the Guest at position 1
The Guest at position 2 on table 1 should have a
different gender to the Guest at position 3
...
84. …Typing…
import groovy.transform.TypeChecked
import experimental.SprintfTypeCheckingVisitor
@TypeChecked(visitor=SprintfTypeCheckingVisitor)
void main() {
sprintf('%s will turn %d on %tF', 'John', new Date(), 21)
}
[Static type checking] - Parameter types didn't match types
expected from the format String:
For placeholder 2 [%d] expected 'int' but was 'java.util.Date'
For placeholder 3 [%tF] expected 'java.util.Date' but was 'int'
sprintf has an Object varargs
parameter, hence not normally
amenable to further static checking
but for constant Strings we can do
better using a custom type checking
plugin.
85. …Typing…
import groovy.transform.TypeChecked
import tictactoe.*
Import static tictactoe.Position.*
@TypeChecked(visitor=TicTacToeTypeVisitor)
void main() {
Board.empty().move(NW).move(C).move(W).move(SW).move(SE)
}
package tictactoe
enum Position {
NW, N, NE, W, C, E, SW, S, SE
}
class Board {
static Board empty() { new Board() }
Board move(Position p) { this }
}
86. …Typing
import groovy.transform.TypeChecked
import tictactoe.*
Import static tictactoe.Position.*
@TypeChecked(visitor=TicTacToeTypeVisitor)
void main() {
Board.empty().move(NW).move(C).move(W).move(SW).move(SE)
}
package tictactoe
enum Position {
NW, N, NE, W, C, E, SW, S, SE
}
[Static type checking] - Attempt to call suboptimal
move SE not allowed [HINT: try NE]
Custom type checker which fails
compilation if programmer attempts
to code a suboptimal solution. Where
suboptimal means doesn’t agree with
what is returned by a minimax,
alpha-beta pruning, iterative
deepening solving engine.
87. Multi-paradigm solutions
• Imperative
• Functional
– Leveraging immutable data structures
– Persistent data structures
– Higher-order functions
• Rules-based
• Concurrency, e.g. Gpars
– Data Parallelism: Map, Reduce
– DataFlow
– Others: Fork Join, Actors
88. Using compile-time Metaprogramming
• Powerful mechanism
– As illustrated by GOF examples
– @Immutable, @Delegate and others
• Rich area for further research
– Explore whether rules design patterns can be readily
embodied within AST transforms