Groovy DSLs, from Beginner to Expert - Guillaume Laforge and Paul King - SpringOne2GX 2011
Upcoming SlideShare
Loading in...5
×
 

Groovy DSLs, from Beginner to Expert - Guillaume Laforge and Paul King - SpringOne2GX 2011

on

  • 16,515 views

Domain-Specific Languages with Groovy

Domain-Specific Languages with Groovy

Statistics

Views

Total Views
16,515
Views on SlideShare
9,904
Embed Views
6,611

Actions

Likes
21
Downloads
419
Comments
0

8 Embeds 6,611

http://glaforge.appspot.com 6334
http://www.chfstudio.com 152
https://twitter.com 73
http://tumblr.hootsuite.com 34
http://chfstudio.tumblr.com 10
http://translate.googleusercontent.com 6
http://paper.li 1
https://si0.twimg.com 1
More...

Accessibility

Categories

Upload Details

Uploaded via as Apple Keynote

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n

Groovy DSLs, from Beginner to Expert - Guillaume Laforge and Paul King - SpringOne2GX 2011 Groovy DSLs, from Beginner to Expert - Guillaume Laforge and Paul King - SpringOne2GX 2011 Presentation Transcript

  • Groovy.DSLs(from: beginner, to: expert)Paul KingCore Groovy CommitterASERT DirectorGuillaume LaforgeGroovy Project ManagerSpringSource, a division of VMware© 2011 SpringOne 2GX 2011. All rights reserved. Do not distribute without permission.
  • Groovy.DSLs(from: beginner, to: expert)Paul King 2 011Core Groovy Committer E ditionASERT DirectorGuillaume LaforgeGroovy Project ManagerSpringSource, a division of VMware© 2011 SpringOne 2GX 2011. All rights reserved. Do not distribute without permission.
  • Guillaume Laforge• Groovy Project Manager at VMware • Initiator of the Grails framework • Creator of the Gaelyk toolkit• Co-author of Groovy in Action• Speaking worldwide w/ a French accent• Follow me on... • My blog: http://glaforge.appspot.com • Twitter: @glaforge • Google+: http://gplus.to/glaforge2 @glaforge — @paulk_asert
  • Guillaume Laforge• Groovy Project Manager at VMware • Initiator of the Grails framework • Creator of the Gaelyk toolkit• Co-author of Groovy in Action• Speaking worldwide w/ a French accent• Follow me on... • My blog: http://glaforge.appspot.com • Twitter: @glaforge • Google+: http://gplus.to/glaforge2 @glaforge — @paulk_asert
  • Paul King• Core Groovy Committer• Co-author of Groovy in Action• Leads ASERT, a Brisbane, Australia, company providing software development, training & mentoring support• International speaker• Follow me on • Website: http://www.asert.com.au • Twitter: @paulk_asert3 @glaforge — @paulk_asert
  • Domain-Specific Languages• Wikipedia definition – A Domain-Specific Language is a programming language or executable specification language that offers, through appropriate notations and abstractions, expressive power focused on, and usually restricted to, a particular problem domain.• In contrast to General Purprose Languages• Somewhere between declarative data and GPLs• Also known as: fluent / human interfaces, language oriented programming, little or mini languages, macros, business4 @glaforge — @paulk_asert
  • Goals of Domain-Specific Languages• Use a more expressive language than a general-purpose one• Share a common metaphore of understanding between develoeprs and subject matter experts• Have domain experts help with the design of the business logic of an application• Avoid cluttering business code with too much boilerplate technical code thanks to a clean seperation5 @glaforge — @paulk_asert
  • Antimalaria drug Insurance policy risk HR skillsresistance calculation engine representationsimulation Nuclear safety simulationsMarket data feeds analysis Loan acceptance rules engine
  • Technical examples Glade XSLT <?xml version="1.0"?> <?xml version="1.0"?> <GTK-Interface> <xsl:stylesheetversion="1.0" <widget> xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <class>GtkWindow</class> <xsl:output method="xml"/> <name>HelloWindow</name> <xsl:template match="*"> <border_width>5</border_width> <xsl:element name="{name()}"> <Signal> <xsl:for-each select="@*"> Regex <xsl:element name="{name()}"> "x.z?z{1,3}y" <name>destroy</name> <handler>gtk_main_quit</handler> <xsl:value-of select="."/> </Signal> </xsl:element> <title>Hello</title> </xsl:for-each> <type>GTK_WINDOW_TOPLEVEL</type> <xsl:apply-templates select="*|text()"/> <position>GTK_WIN_POS_NONE</position> </xsl:element> <allow_shrink>True</allow_shrink> </xsl:template> <allow_grow>True</allow_grow> </xsl:stylesheet> <auto_shrink>False</auto_shrink> <widget> <class>GtkButton</class> fetchmail <name>Hello World</name> <can_focus>True</can_focus> # Poll this site first each cycle. <label>Hello World</label> poll pop.provider.net proto pop3 </widget> user "jsmith" with pass "secret1" is "smith" here </widget> user jones with pass "secret2" is "jjones" here with options </GTK-Interface> keep SQL # Poll this site second, unless Lord Voldemort zaps us first. poll billywig.hogwarts.com with proto imap: SELECT * FROM TABLE user harry_potter with pass "floo" is harry_potter here WHERE NAME LIKE %SMI # Poll this site third in the cycle. ORDER BY NAME # Password will be fetched from ~/.netrc poll mailhost.net with proto imap: user esr is esr here Troff cat thesis.ms | chem | tbl | refer | grap | pic | eqn | groff -Tps > thesis.ps 8 Source: Applying minilanguages: http://www.faqs.org/docs/artu/ch08s02.html @glaforge — @paulk_asert
  • Pros and cons of DSLs• Pros – Safety; as long as the – Domain experts can help, language constructs are safe, validate, modify, and often any DSL sentence can be develop DSL programs considered safe – Somewhat self-documenting – Enhance quality, • Cons productivity, reliability, – Learning cost vs. limited maintainability, portability, applicability reusability – Cost of designing, implementing & maintaining DSLs as well as tools/IDEs – Attaining proper scope8 @glaforge — @paulk_asert
  • • The flexible nature of the language – method call and syntax conventions – native syntax constructs – adding properties to numbers – command chain expressions – operator overloading – hierarchical structures with builders – AST transformations9 © 2011 SpringOne 2GX 2011. All rights reserved. Do not distribute without permission.
  • Scripts vs classes • Hide all the boilerplate technical code – an end-user doesn’t need to know about classes10 @glaforge — @paulk_asert
  • Scripts vs classes • Hide all the boilerplate technical code – an end-user doesn’t need to know about classes public
class
Rule
{ 



public
static
void
main(String[]
args)
{ 







System.out.println("Hello"); 



} }10 @glaforge — @paulk_asert
  • Scripts vs classes • Hide all the boilerplate technical code – an end-user doesn’t need to know about classes public
class
Rule
{ 



public
static
void
main(String[]
args)
{ 







System.out.println("Hello"); 



} } println
"Hello"10 @glaforge — @paulk_asert
  • Optional typing • No need to bother with types or even generics – unless you want to! – but strongly typed if you so desire11 @glaforge — @paulk_asert
  • Optional typing • No need to bother with types or even generics – unless you want to! – but strongly typed if you so desire//
given
this
API
methodpublic
Rate<LoanType,
Duration,
BigDecimal>[]
lookupTable()
{
...
}11 @glaforge — @paulk_asert
  • Optional typing • No need to bother with types or even generics – unless you want to! – but strongly typed if you so desire//
given
this
API
methodpublic
Rate<LoanType,
Duration,
BigDecimal>[]
lookupTable()
{
...
} //
verbose
Java
notation Rate<LoanType,
Duration,
BigDecimal>[]
table
=
lookupTable();11 @glaforge — @paulk_asert
  • Optional typing • No need to bother with types or even generics – unless you want to! – but strongly typed if you so desire//
given
this
API
methodpublic
Rate<LoanType,
Duration,
BigDecimal>[]
lookupTable()
{
...
} //
verbose
Java
notation Rate<LoanType,
Duration,
BigDecimal>[]
table
=
lookupTable(); //
leaner
Groovy
variant def
table
=
lookupTable()11 @glaforge — @paulk_asert
  • Native syntax constructs12 @glaforge — @paulk_asert
  • Native syntax constructs //
Lists12 @glaforge — @paulk_asert
  • Native syntax constructs //
Lists def
days
=
[Monday,
Tuesday,
Wednesday]12 @glaforge — @paulk_asert
  • Native syntax constructs //
Lists def
days
=
[Monday,
Tuesday,
Wednesday] //
Maps12 @glaforge — @paulk_asert
  • Native syntax constructs //
Lists def
days
=
[Monday,
Tuesday,
Wednesday] //
Maps def
states
=
[CA:
California,
TX:
Texas]12 @glaforge — @paulk_asert
  • Native syntax constructs //
Lists def
days
=
[Monday,
Tuesday,
Wednesday] //
Maps def
states
=
[CA:
California,
TX:
Texas] //
Ranges
(you
can
create
your
own)12 @glaforge — @paulk_asert
  • Native syntax constructs //
Lists def
days
=
[Monday,
Tuesday,
Wednesday] //
Maps def
states
=
[CA:
California,
TX:
Texas] //
Ranges
(you
can
create
your
own) def
bizDays
=
Monday..Friday12 @glaforge — @paulk_asert
  • Native syntax constructs //
Lists def
days
=
[Monday,
Tuesday,
Wednesday] //
Maps def
states
=
[CA:
California,
TX:
Texas] //
Ranges
(you
can
create
your
own) def
bizDays
=
Monday..Friday def
allowedAge
=
18..6512 @glaforge — @paulk_asert
  • Optional parens & semis• Make statements and expressions look more like natural languages13 @glaforge — @paulk_asert
  • Optional parens & semis• Make statements and expressions look more like natural languages move(left);13 @glaforge — @paulk_asert
  • Optional parens & semis• Make statements and expressions look more like natural languages move(left); move
left13 @glaforge — @paulk_asert
  • Adding properties to numbers• Several approaches to adding properties to numbers – through a category class
PillsCategory
{ 



static
getPills(Number
n)
{
n
} } use(PillsCategory)
{ 



2.pills









//
2.getPills() } – through ExpandoMetaClass Number.metaClass.getPills
{
‐>
delegate
} 2.pills
















//
2.getPills()14 @glaforge — @paulk_asert
  • Named arguments• In Groovy you can mix named and unnamed arguments for method parameters – named params are actually put in a map parameter – plus optional parens & semis 

take
2.pills,
 


of:
chloroquinine,
 after:
6.hours //
Calls
a
method
signature
like: def
take(Map
m,
MedicineQuantity
mq)15 @glaforge — @paulk_asert
  • Command chain expressions16 @glaforge — @paulk_asert
  • Command chain expressions• A grammar improvement allowing you to drop dots & parens when chaining method calls – an extended version of top-level statements like println16 @glaforge — @paulk_asert
  • Command chain expressions• A grammar improvement allowing you to drop dots & parens when chaining method calls – an extended version of top-level statements like println16 @glaforge — @paulk_asert
  • Command chain expressions• A grammar improvement allowing you to drop dots & parens when chaining method calls – an extended version of top-level statements like println• Less dots, less parens allow you to – write more readable business rules – in almost plain English sentences • (or any language, of course)16 @glaforge — @paulk_asert
  • Command chain expressions• A grammar improvement allowing you to drop dots & parens when chaining method calls – an extended version of top-level statements like println• Less dots, less parens allow you to – write more readable business rules – in almost plain English sentences • (or any language, of course)16 @glaforge — @paulk_asert
  • Command chain expressions• A grammar improvement allowing you to drop dots & parens when chaining method calls – an extended version of top-level statements like println• Less dots, less parens allow you to – write more readable business rules – in almost plain English sentences • (or any language, of course)• Let’s have a look at some examples16 @glaforge — @paulk_asert
  • Command chain expressions 
turn
left

then
right
17 @glaforge — @paulk_asert
  • Command chain expressions Alternation of method names 
turn
left

then
right
17 @glaforge — @paulk_asert
  • Command chain expressions Alternation of method names 
turn
left

then
right
 and parameters (even named ones)17 @glaforge — @paulk_asert
  • Command chain expressions 
turn
left

then
right
17 @glaforge — @paulk_asert
  • Command chain expressions Equivalent to: 




(



).



(




) 
turn
left

then
right
17 @glaforge — @paulk_asert
  • LookM a!N op are ns,no do ts!
  • Command chain expressions Before... we used to do... take
2.pills,
of:
chloroquinine,
after:
6.hours19 @glaforge — @paulk_asert
  • Command chain expressions Before... we used to do... take
2.pills,
of:
chloroquinine,
after:
6.hours Normal argument19 @glaforge — @paulk_asert
  • Command chain expressions Before... we used to do... take
2.pills,
of:
chloroquinine,
after:
6.hours Normal argument Named arguments19 @glaforge — @paulk_asert
  • Command chain expressions Before... we used to do... take
2.pills,
of:
chloroquinine,
after:
6.hours Normal argument Named arguments Woud call: def
take(Map
m,
Quantity
q)19 @glaforge — @paulk_asert
  • Command chain expressions Now, even less punctuation! take
2.pills

of

chloroquinine

after

6.hours20 @glaforge — @paulk_asert
  • Command chain expressions Now, even less punctuation! 



(






).


(












).





(






) take
2.pills

of

chloroquinine

after

6.hours20 @glaforge — @paulk_asert
  • Command chain expressions //
environment
initialization Integer.metaClass.getPills
{
‐>
delegate
} Integer.metaClass.getHours
{
‐>
delegate
} 
 //
variable
injection def
chloroquinine
=
/*...*/ 
 { } //
implementing
the
DSL
logic def
take(n)
{ 



[of:
{
drug
‐> 







[after:
{
time
‐>
/*...*/
}] 



}] } //
‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ take
2.pills
of
chloroquinine
after
6.hours21 @glaforge — @paulk_asert
  • Command chain expressions take
2.pills

of

chloroquinine

after

6.hours22 @glaforge — @paulk_asert
  • Command chain expressions take
2.pills

of

chloroquinine

after

6.hours ... some dots remain ...22 @glaforge — @paulk_asert
  • Command chain expressions Yes, we can... get rid of them :-) take
2

pills
of

chloroquinine

after

6
hours23 @glaforge — @paulk_asert
  • Command chain expressions Yes, we can... get rid of them :-) 



(
).




(

).













(




).
(




) take
2

pills
of

chloroquinine

after

6
hours23 @glaforge — @paulk_asert
  • Command chain expressions //
variable
injection def
(of,
after,
hours)
=
/*...*/ 
 //
implementing
the
DSL
logic { } def
take(n)
{ 



[pills:
{
of
‐> 







[chloroquinine:
{
after
‐> 











[6:
{
time
‐>
}] 







}] 



}] } //
‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ take
2
pills
of
chloroquinine
after
6
hours24 @glaforge — @paulk_asert
  • Command chain expressions @glaforge — @paulk_asert 25
  • Command chain expressions //
methods
with
multiple
arguments
(commas) @glaforge — @paulk_asert 25
  • Command chain expressions //
methods
with
multiple
arguments
(commas) take
coffee

with
sugar,
milk

and
liquor @glaforge — @paulk_asert 25
  • Command chain expressions //
methods
with
multiple
arguments
(commas) take
coffee

with
sugar,
milk

and
liquor //
leverage
named‐args
as
punctuation @glaforge — @paulk_asert 25
  • Command chain expressions //
methods
with
multiple
arguments
(commas) take
coffee

with
sugar,
milk

and
liquor //
leverage
named‐args
as
punctuation check
that:
margarita

tastes
good @glaforge — @paulk_asert 25
  • Command chain expressions //
methods
with
multiple
arguments
(commas) take
coffee

with
sugar,
milk

and
liquor //
leverage
named‐args
as
punctuation check
that:
margarita

tastes
good //
closure
parameters
for
new
control
structures @glaforge — @paulk_asert 25
  • Command chain expressions //
methods
with
multiple
arguments
(commas) take
coffee

with
sugar,
milk

and
liquor //
leverage
named‐args
as
punctuation check
that:
margarita

tastes
good //
closure
parameters
for
new
control
structures given
{}

when
{}

then
{} @glaforge — @paulk_asert 25
  • Command chain expressions //
methods
with
multiple
arguments
(commas) take
coffee

with
sugar,
milk

and
liquor //
leverage
named‐args
as
punctuation check
that:
margarita

tastes
good //
closure
parameters
for
new
control
structures given
{}

when
{}

then
{} //
zero‐arg
methods
require
parens @glaforge — @paulk_asert 25
  • Command chain expressions //
methods
with
multiple
arguments
(commas) take
coffee

with
sugar,
milk

and
liquor //
leverage
named‐args
as
punctuation check
that:
margarita

tastes
good //
closure
parameters
for
new
control
structures given
{}

when
{}

then
{} //
zero‐arg
methods
require
parens select
all

unique()
from
names @glaforge — @paulk_asert 25
  • Command chain expressions //
methods
with
multiple
arguments
(commas) take
coffee

with
sugar,
milk

and
liquor //
leverage
named‐args
as
punctuation check
that:
margarita

tastes
good //
closure
parameters
for
new
control
structures given
{}

when
{}

then
{} //
zero‐arg
methods
require
parens select
all

unique()
from
names //
possible
with
an
odd
number
of
terms @glaforge — @paulk_asert 25
  • Command chain expressions //
methods
with
multiple
arguments
(commas) take
coffee

with
sugar,
milk

and
liquor //
leverage
named‐args
as
punctuation check
that:
margarita

tastes
good //
closure
parameters
for
new
control
structures given
{}

when
{}

then
{} //
zero‐arg
methods
require
parens select
all

unique()
from
names //
possible
with
an
odd
number
of
terms take
3

cookies @glaforge — @paulk_asert 25
  • Command chain expressions //
methods
with
multiple
arguments
(commas) take
coffee

with
sugar,
milk

and
liquor 



(





).



(










).


(





) //
leverage
named‐args
as
punctuation check
that:
margarita

tastes
good //
closure
parameters
for
new
control
structures given
{}

when
{}

then
{} //
zero‐arg
methods
require
parens select
all

unique()
from
names //
possible
with
an
odd
number
of
terms take
3

cookies @glaforge — @paulk_asert 25
  • Command chain expressions //
methods
with
multiple
arguments
(commas) take
coffee

with
sugar,
milk

and
liquor 



(





).



(










).


(





) //
leverage
named‐args
as
punctuation check
that:
margarita

tastes
good 




(














).





(



) //
closure
parameters
for
new
control
structures given
{}

when
{}

then
{} //
zero‐arg
methods
require
parens select
all

unique()
from
names //
possible
with
an
odd
number
of
terms take
3

cookies @glaforge — @paulk_asert 25
  • Command chain expressions //
methods
with
multiple
arguments
(commas) take
coffee

with
sugar,
milk

and
liquor 



(





).



(










).


(





) //
leverage
named‐args
as
punctuation check
that:
margarita

tastes
good 




(














).





(



) //
closure
parameters
for
new
control
structures given
{}

when
{}

then
{} 




(

).



(

).



(

) //
zero‐arg
methods
require
parens select
all

unique()
from
names //
possible
with
an
odd
number
of
terms take
3

cookies @glaforge — @paulk_asert 25
  • Command chain expressions //
methods
with
multiple
arguments
(commas) take
coffee

with
sugar,
milk

and
liquor 



(





).



(










).


(





) //
leverage
named‐args
as
punctuation check
that:
margarita

tastes
good 




(














).





(



) //
closure
parameters
for
new
control
structures given
{}

when
{}

then
{} 




(

).



(

).



(

) //
zero‐arg
methods
require
parens select
all

unique()
from
names 





(


).







.



(




) //
possible
with
an
odd
number
of
terms take
3

cookies @glaforge — @paulk_asert 25
  • Command chain expressions //
methods
with
multiple
arguments
(commas) take
coffee

with
sugar,
milk

and
liquor 



(





).



(










).


(





) //
leverage
named‐args
as
punctuation check
that:
margarita

tastes
good 




(














).





(



) //
closure
parameters
for
new
control
structures given
{}

when
{}

then
{} 




(

).



(

).



(

) //
zero‐arg
methods
require
parens select
all

unique()
from
names 





(


).







.



(




) //
possible
with
an
odd
number
of
terms take
3

cookies 



(
). @glaforge — @paulk_asert 25
  • Operator overloadinga
+
b

//
a.plus(b)a
‐
b

//
a.minus(b) • Currency amountsa
*
b

//
a.multiply(b) – 15.euros + 10.dollarsa
/
b

//
a.divide(b)a
%
b

//
a.modulo(b) • Distance handlinga
**
b
//
a.power(b)a
|
b

//
a.or(b) – 10.kilometers - 10.metersa
&
b

//
a.and(b)a
^
b

//
a.xor(b) • Workflow, concurrencya[b]


//
a.getAt(b) – taskA | taskB & taskCa
<<
b
//
a.leftShift(b)a
>>
b
//
a.rightShift(b)+a




//
a.unaryPlus() • Credit an account‐a




//
a.unaryMinus() – account << 10.dollars~a




//
a.bitwiseNegate() account += 10.dollars26 @glaforge — @paulk_asert
  • Builders• For hierachical structures, extend: – BuilderSupport – FactoryBuilderSupport • nice for extending builders with new nodes and attributes import
groovy.xml.* 
• Roll your own.. def
builder
=
new
MarkupBuilder() builder.html
{ – with invokeMethod() 



head
{
title
"Chicago!"
} or methodMissing() 



body
{ 







div(id:
"main")
{ – with get/setProperty() 











p
"Groovy
rocks!" 







} or propertyMissing() 



} }27 @glaforge — @paulk_asert
  • AST Transformations• Hook into the compiler process to do compile-time metaprogramming by modifying the Abstract Syntax Tree Transformation28 @glaforge — @paulk_asert
  • @InheritConstructors• Classes like Exception are painful when extended, as all the base constructors should be replicated class
CustomException
extends
Exception
{ 



CustomException()























{
super()






} 



CustomException(String
msg)













{
super(msg)



} 



CustomException(String
msg,
Throwable
t)
{
super(msg,
t)
} 



CustomException(Throwable
t)












{
super(t)





} }29 @glaforge — http://glaforge.appspot.com — http://gplus.to/glaforge
  • @InheritConstructors• Classes like Exception are painful when extended, as all the base constructors should be replicated import
groovy.transform.* @InheritConstructors class
CustomException
extends
Exception
{ 



CustomException()























{
super()






} 



CustomException(String
msg)













{
super(msg)



} 



CustomException(String
msg,
Throwable
t)
{
super(msg,
t)
} 



CustomException(Throwable
t)












{
super(t)





} }29 @glaforge — http://glaforge.appspot.com — http://gplus.to/glaforge
  • @InheritConstructor... under the covers//
...ClassNode
sNode
=
cNode.getSuperClass();










for
(ConstructorNode
cn
:
sNode.getDeclaredConstructors())
{



Parameter[]
params
=
cn.getParameters();



if
(cn.isPrivate())
continue;



Parameter[]
newParams
=
new
Parameter[params.length];



List<Expression>
args
=
copyParamsToArgs(params,
newParams);



if
(isClashing(cNode,
newParams))
continue;



BlockStatement
body
=
new
BlockStatement();



ConstructorCallExpression
callArgs
=







new
ConstructorCallExpression(ClassNode.SUPER,






































new
ArgumentListExpression(args));



body.addStatement(new
ExpressionStatement(callArgs));



cNode.addConstructor(cn.getModifiers(),
newParams,

























cn.getExceptions(),
body);}//
...30 @glaforge — http://glaforge.appspot.com — http://gplus.to/glaforge
  • Advanced example with Spockdef
"length
of
Spocks
&
his
friends
names"()
{



expect:







name.size()
==
length



where:







name




|
length







"Spock"

|
5







"Kirk"


|
4







"Scotty"
|
6}31 @glaforge — http://glaforge.appspot.com — http://gplus.to/glaforge
  • • A week in the life of DSL inc. – find the examples on GitHub: https://github.com/paulk-asert/ DSLsFromBeginnerToExpert32 © 2011 SpringOne 2GX 2011. All rights reserved. Do not distribute without permission.
  • MONDAY33 @glaforge — @paulk_asert
  • Currency DSL… From:



customer@acme.org To:





Guillaume Subject:
Project
Request Dear
Guillaume, We
would
like
a
DSL
for
capturing
currency
 expressions.
Just
US
currency
to
start
with. Thanks,
Happy
Customer34 @glaforge — @paulk_asert
  • …Currency DSL... enum
Coin
{ 



penny(1),
nickel(5),
dime(10),
quarter(25) 



Coin(int
value)
{
this.value
=
value
} 



int
value } import
static
Coin.* 
 assert
2
*
quarter.value
+ 






1
*
nickel.value
+ 






2
*
penny.value
==
5735 @glaforge — @paulk_asert
  • …Currency DSL… From:



customer@acme.org To:





Guillaume Subject:
Project
Request Dear
Guillaume, That
works
fairly
well
but
can
we
get
rid
 of
the
‘.value’
part
of
those
expressions.
 They
are
confusing
our
users. Thanks,
Happy
Customer36 @glaforge — @paulk_asert
  • ...Currency DSL...//
Categoryclass
CoinMath
{



static
multiply(Integer
self,
Coin
c)
{







self
*
c.value



}}
use
(CoinMath)
{



assert
2
*
quarter
+










1
*
nickel
+










2
*
penny
==
57}37 @glaforge — @paulk_asert
  • ...Currency DSL...//
Categoryclass
CoinMath
{



static
multiply(Integer
self,
Coin
c)
{







self
*
c.value



}}
use
(CoinMath)
{



assert
2
*
quarter
+










1
*
nickel
+ //
EMC
equivalent










2
*
penny
==
57 Integer.metaClass.multiply
=
{} 



Coin
c
‐>
delegate
*
c.value } assert
2
*
quarter
+
 






1
*
nickel
+
 






2
*
penny
==
5737 @glaforge — @paulk_asert
  • …Currency DSL… From:



customer@acme.org To:





Guillaume Subject:
Project
Request Dear
Guillaume, Much
better
but
our
users
are
sometimes
 using
plural
as
well
as
singular
 expressions.
Is
there
anything
that
you
 can
do
about
that? Thanks,
Happy
Customer38 @glaforge — @paulk_asert
  • ... Currency DSLclass
CoinValues
{



static
get(Integer
self,
String
name)
{







self
*
Coin."${singular(name)}".value



}



static
singular(String
val)
{







val.endsWith(ies)
?
val[0..‐4]
+
y
:
val.endsWith(s)
?
val[0..‐2]
:
val



}}
use
(CoinValues)
{



assert
2.quarters
+
1.nickel
+
2.pennies
==
57}39 @glaforge — @paulk_asert
  • ... Currency DSLclass
CoinValues
{



static
get(Integer
self,
String
name)
{







self
*
Coin."${singular(name)}".value



}



static
singular(String
val)
{







val.endsWith(ies)
?
val[0..‐4]
+
y
:
val.endsWith(s)
?
val[0..‐2]
:
val



}}
use
(CoinValues)
{



assert
2.quarters
+
1.nickel
+
2.pennies
==
57} //
EMC
equivalent Integer.metaClass.getProperty
=
{
String
name
‐> 



def
mp
=
Integer.metaClass.getMetaProperty(name) 



if
(mp)
return
mp.getProperty(delegate) 



def
singular
=
name.endsWith(ies)
?
name[0..‐4]
+
y
: 


















name.endsWith(s)
?
name[0..‐2]
:
name 



delegate
*
Coin."$singular".value } 
 assert
2.quarters
+
1.nickel
+
2.pennies
==
5739 @glaforge — @paulk_asert
  • TUESDAYhttp://www.google.com/imgres?imgurl=http://i733.photobucket.com/albums/ww336/snake1953/Day_of_Week/Tue/HappyTuesda 40 @glaforge — @paulk_asert%3D1%26hl%3Den%26client%3Dfirefox-a%26sa%3DN%26rls%3Dorg.mozilla:en-US:official%26biw%3D1488%26bih%3D852%
  • Business logic DSL From:



customer@acme.org To:





Paul
King Subject:
Project
Request Dear
Paul, Could
you
please
create
us
a
small
DSL
for
capturing our
business
rules.
We
are
thinking
of
something
like: 



price
=
3 



quantity
=
10 



total
=
price
*
quantity Perhaps
also
an
ability
to
check
values: 



assert
total
==
30 Thanks,
Happy
Customer P.S.
Will
send
a
couple
of
more
details
in
a
follow‐up
email
but please
consider
the
requirements
as
being
pretty
much
locked
down.41 @glaforge — @paulk_asert
  • Business logic DSL• Options – Embedded Groovy – Regex parser – Antlr parser – Split / StringTokenizer – Parser combinators42 @glaforge — @paulk_asert
  • Business logic DSL From:



customer@acme.org To:





Paul
King Subject:
Project
Request Date:



Mid
morning Dear
Paul, We
were
thinking
a
bit
more
about
it,
and
it
would
be
good
to
have just
a
few
more
features.
Hopefully,
they
won’t
have
much
impact on
your
existing
design
and
can
be
added
very
quickly.
It
isn’t much,
just
the
ability
to
have
IF,
THEN,
ELSE
like
structures,
oh yeah
and
the
ability
to
have
loops,
and
store
stuff
in
files
and get
stuff
from
databases
and
web
services
if
we
need. Thanks,
Happy
Customer P.S.
It
would
be
great
if
you
can
be
finished
by
this
afternoon. We
have
a
customer
who
would
like
this
feature
RSN.
Thanks.43 @glaforge — @paulk_asert
  • Business logic DSL def
shell
=
new
GroovyShell() shell.evaluate( 



price
=
3 



quantity
=
10 



total
=
price
*
quantity 



assert
total
==
30 )44 @glaforge — @paulk_asert
  • Business logic DSL From:



customer@acme.org To:





Paul
King Subject:
Project
Request Date:



This
afternoon Dear
Paul, We
were
really
happy
with
the
DSL
engine
you
provided
us
 with
but
people
started
importing
all
sorts
of
classes.
Can
 you
stop
them
from
doing
that?
Maybe
just
java.lang.Math
 only.
Also
we
decided
we
don’t
like
“while”
loops
anymore.
 Leave
you
to
it. Thanks,
Happy
Customer P.S.
Have
a
beer
and
crab
dinner
for
me
at
the
conference.
 Bye.45 @glaforge — @paulk_asert
  • Business logic DSL class
CustomerShell
{ 



Object
evaluate(String
text)
{ 







try
{ 











def
loader
=
new
CustomerClassLoader() 











def
clazz
=
loader.parseClass(text) 











def
script
=
clazz.newInstance() 











return
script.run() 







}
catch
(...)
{
...
} 



} } 
 class
CustomerClassLoader
extends
GroovyClassLoader
{ 



def
createCompilationUnit(CompilerConfiguration
config,
CodeSource
codeSource)
{ 







CompilationUnit
cu
=
super.createCompilationUnit(config,
codeSource) 







cu.addPhaseOperation(new
CustomerFilteringNodeOperation(),
Phases.SEMANTIC_ANALYSIS) 







return
cu 



} } 
 private
class
CustomerFilteringNodeOperation
extends
PrimaryClassNodeOperation
{ 



//
... 



private
static
final
allowedStaticImports
=
[Math].asImmutable() 



void
visitStaticMethodCallExpression(StaticMethodCallExpression
smce)
{ 







if
(!allowedStaticImports.contains(smce.ownerType.getTypeClass()))
{ 











throw
new
SecurityException("Static
method
call
expressions
forbidden
in
acme
shell.") 







} 



} 



void
visitWhileLoop(WhileStatement
whileStatement)
{ 







throw
new
SecurityException("While
statements
forbidden
in
acme
shell.") 


} 


//
... } 46 @glaforge — @paulk_asertPlease also see the ArithmeticShell which is included under examples in the Groovy distribution
  • Business logic DSL def
shell
=
new
CustomerShell() shell.evaluate( 



price
=
3 



quantity
=
10 



total
=
price
*
quantity 



assert
total
==
30 )47 @glaforge — @paulk_asert
  • WEDNESDAY 48 @glaforge — @paulk_aserthttp://www.crystalscomments.com/1/days/wednesday/animals/006.jpg
  • Stock Exchange Order DSL From:



customer@finance‐broker.org To:





Guillaume Subject:
Project
Request Date:



Wednesday
morning Dear
Guillaume, For
our
order
processing
system
we
have
a
need
to
capture
 client
orders
using
a
DSL.
An
order
includes
the
name
of
 the
security
to
be
transacted
(buy
or
sell)
as
well
as
 quantity
and
unit
price
details
to
specify
any
constraint
 that
the
counterparty
would
like
to
impose
on
the
price
 of
transaction. Thanks,
Happy
CustomerExample inspiredfrom «DSLs in Action»:http://www.manning.com/ghosh/ 49 @glaforge — @paulk_asert
  • //
‐‐‐‐‐
Implementation
of
the
Fluent
API
‐‐‐‐‐… Stock Exchange Order DSL…enum
Action
{
Buy,
Sell
}
 println
new
Order()class
Order
{ 



.sell(150,
"IBM")



def
security



def
quantity,
limitPrice 



.limitPrice(300)



boolean
allOrNone



def
valueCalculation 



.allOrNone(true)



Action
action 



.valueAs
{
qty,
unitPrice
‐>




def
buy(Integer
quantity,
String
security)
{ 








qty
*
unitPrice
‐
100
}







this.quantity
=
quantity 








this.security
=
security







this.action
=
Action.Buy println
new
Order()







return
this 



.buy(200,
"GOOG")



}



def
sell(Integer
quantity,
String
security)
{ 



.limitPrice(200)







this.quantity
=
quantity 



.allOrNone(true)







this.security
=
security







this.action
=
Action.Sell 



.valueAs{
qty,
unitPrice
‐>







return
this 







qty
*
unitPrice
‐
500
}



}



def
limitPrice(Integer
limit)
{







this.limitPrice
=
limit;
return
this



} Sell 150 shares of IBM at valuation of 44900



def
allOrNone(boolean
allOrNone)
{







this.allOrNone
=
allOrNone;
return
this Buy 200 shares of GOOG at valuation of 39500



}



def
valueAs(Closure
valueCalculation)
{







this.valueCalculation
=
valueCalculation;
return
this



}



String
toString()
{







"$action
$quantity
shares
of
$security
at
valuation
of
${valueCalculation(quantity,
limitPrice)}"



}}50 @glaforge — @paulk_asert
  • Stock Exchange Order DSL From:



customer@finance‐broker.org To:





Guillaume Subject:
Project
Request Date:



Wednesday
morning Dear
Guillaume, That
version
was
great
but
can
you
make
the DSL
a
little
more
fluent.
The
brokers
aren’t programmers
after
all! Thanks,
Happy
Customer51 @glaforge — @paulk_asert
  • Stock Exchange Order DSL class
Order
{ 



def
security,
quantity,
limitPrice,
allOrNone,
value,
bs 
 



def
buy(securityQuantity,
closure)
{ 







bs
=
Bought 







buySell(securityQuantity,
closure) 



} 



 



def
sell(securityQuantity,
closure)
{ 







bs
=
Sold 







buySell(securityQuantity,
closure) 



} 
 



private
buySell(securityQuantity,
closure)
{ 







//
multiple
assignment 







(security,
quantity)
=
[securityQuantity.security,
securityQuantity.quantity] 







//
better
clone
the
closure
to
avoid
multi‐threading
access
issues 







def
c
=
closure.clone() 







//
delegate
the
method
calls
inside
the
closure
to
our
methodMissing 







c.delegationStrategy
=
Closure.DELEGATE_ONLY 







c.delegate
=
this 







def
valuation
=
c() 







println
"$bs
$quantity
$security.name
at
valuation
of
$valuation" 



} 
 



//
methods
inside
the
closure
will
assign
the
Order
properties 



def
methodMissing(String
name,
args)
{
this."$name"
=
args[0]
}52 @glaforge — @paulk_asert
  • Stock Exchange Order DSL 



def
getTo()
{
this
} newOrder.to.buy(100.shares.of(IBM))
{ 

 



limitPrice

300 



def
valueAs(closure)
{ 



allOrNone


true 







value
=
closure(quantity,
limitPrice) 



valueAs




{
qty,
unitPrice
‐> 



} 







qty
*
unitPrice
‐
200
} } 
 } class
Security
{ 
 



String
name newOrder.to.sell
200.shares.of(GOOG),
{ } 



limitPrice

200 
 class
Quantity
{ 



allOrNone


false 



Security
security 



valueAs




{
qty,
unitPrice
‐> 



Integer
quantity 







qty
*
unitPrice
‐
500
} } } 
 Integer.metaClass.getShares
=
{
‐>
delegate
} Integer.metaClass.of
=
{
new
Quantity(security:
it,
quantity:
delegate)
} 
 class
CustomBinding
extends
Binding
{ 



def
getVariable(String
symbol)
{ 







//
create
a
new
order
each
time 







//
for
when
you
pass
several
orders 







if
(symbol
==
"newOrder")
new
Order() 







//
otherwise,
its
an
instrument 







//
trick
to
avoid
using
strings:
use
IBM
instead
of
IBM 







else
new
Security(name:
symbol)
 



} } 
 //
use
the
script
binding
for
retrieving
IBM,
etc. binding
=
new
CustomBinding()53 @glaforge — @paulk_asert
  • Stock Exchange Order DSL From:



customer@finance‐broker.org To:





Guillaume Subject:
Project
Request Date:



Wednesday
lunch
time Dear
Guillaume, That
version
was
great
but
can
we
simplify
the constraint
to
remove
the
parameters,
i.e.:
change 



{
qty,
unitPrice
‐>
qty
*
unitPrice
‐
200
} to
this: 



{
qty
*
unitPrice
‐
200
} Thanks,
Happy
Customer54 @glaforge — @paulk_asert
  • Stock Exchange Order DSL newOrder.to.buy(100.shares.of(IBM))
{class
Order
{ 



limitPrice

300... 



allOrNone


true



def
allOrNone,
valueCalculation,
bs 



valueAs




{
qty
*
unitPrice
‐
200
}...



private
buySell(securityQuantity,
closure)
{ }... 








valueCalculation
=
c() newOrder.to.sell
200.shares.of(GOOG),
{







//
debug
print
resulting
order 



limitPrice

200







println
toString() 



allOrNone


false







return
this 



valueAs




{
qty
*
unitPrice
‐
500
}



} }...



def
valueAs(Closure
wrapee)
{



//
in
order
to
be
able
to
define
closures
like
{
qty
*
unitPrice
}
without
having



//
to
explicitly
pass
the
parameters
to
the
closure
we
can
wrap
the
closure
inside



//
another
one
and
that
closure
sets
a
delegate
to
the
qty
and
unitPrice
variables







def
wrapped
=
{
qty,
unitPrice
‐>











def
cloned
=
wrapee.clone()











cloned.resolveStrategy
=
Closure.DELEGATE_ONLY











cloned.delegate
=
[qty:
qty,
unitPrice:
unitPrice]











cloned()







}







return
wrapped



}...}55 @glaforge — @paulk_asert
  • Stock Exchange Order DSL From:



customer@finance‐broker.org To:





Guillaume Subject:
Project
Request Date:



Wednesday
afternoon Dear
Guillaume, Fantastic!
This
is
getting
better
all
the
 time!
But
can
we
get
rid
most
of
the
‘.’
and
 bracket
symbols! Thanks,
Happy
Customer56 @glaforge — @paulk_asert
  • Stock Exchange Order DSL 



//
Characteristics
of
the
order:
"of
GOOG
{...}" 



def
of(SecurityAndCharacteristics
secAndCharact)
{ 







security
=
secAndCharact.security 







def
c
=
secAndCharact.characteristics.clone() 







c.delegationStrategy
=
Closure.DELEGATE_ONLY 







c.delegate
=
this 







c() 







//
debug
print
of
the
resulting
order 







println
toString() 







return
this 



} 
 




//
Valuation
closure:
"of
{
qty,
unitPrice
‐>
...
}" 




def
of(Closure
valueCalculation)
{ 








//
in
order
to
be
able
to
define
closures
like
{
qty
*
unitPrice
} 








//
without
having
to
explicitly
pass
the
parameters
to
the
closure 








//
we
can
wrap
the
closure
inside
another
one 








//
and
that
closure
sets
a
delegate
to
the
qty
and
unitPrice
variables 








def
wrapped
=
{
qty,
unitPrice
‐> 












def
cloned
=
valueCalculation.clone() 












cloned.resolveStrategy
=
Closure.DELEGATE_ONLY 












cloned.delegate
=
[qty:
qty,
unitPrice:
unitPrice] 












cloned() 








} 








return
wrapped 



} }
57 @glaforge — @paulk_asert http://groovyconsole.appspot.com/script/226001 by glaforge
  • Stock Exchange Order DSL class
Security
{ 



String
name } 
 class
SecurityAndCharacteristics
{ 



Security
security 



Closure
characteristics } 
 class
CustomBinding
extends
Binding
{ 



def
getVariable(String
word)
{ 







//
return
System.out
when
the
script
requests
to
write
to
out 







if
(word
==
"out")
System.out 







//
dont
throw
an
exception
and
return
null 







//
when
a
silent
sentence
word
is
used, 







//
like
"to"
and
"the"
in
our
DSL 







null 



} } 
 //
Script
helper
method
for
"GOOG
{}",
"VMW
{}",
etc. def
methodMissing(String
name,
args)
{ 



new
SecurityAndCharacteristics( 







security:
new
Security(name:
name), 







characteristics:
args[0] 



) } 
 //
Script
helper
method
to
make
"order
to"
silent //
by
just
creating
our
current
order def
order(to)
{
new
Order()
} 
 //
use
the
script
binding
for
silent
sentence
words
like
"to",
"the" binding
=
new
CustomBinding() //
syntax
for
200.shares Integer.metaClass.getShares
=
{
‐>
delegate
}58 @glaforge — @paulk_asert
  • Stock Exchange Order DSL order
to
buy
200.shares
of
GOOG
{ 



limitPrice






500 



allOrNone







false 



at
the
value
of

{
qty
*
unitPrice
‐
100
} } 
 order
to
sell
150.shares
of
VMW
{ 



limitPrice






80 



allOrNone







true 



at
the
value
of

{
qty
*
unitPrice
} }59 http://groovyconsole.appspot.com/script/226001 @glaforge — @paulk_asert by glaforge
  • Stock Exchange Order DSL From:



customer@finance‐broker.org To:





Guillaume Subject:
Project
Request Date:



Wednesday
evening Dear
Guillaume, Brilliant!
Even
our
CEO
could
write
orders! Thanks,
Happy
Customer60 @glaforge — @paulk_asert
  • THURSDAY 61 @glaforge — @paulk_aserthttp://t1.gstatic.com/images?q=tbn:_VBloEP8YC-6IM:http://msp248.photobucket.com/albums/gg171/ingrid2002/TextPL252028
  • Einstein’s Riddle DSL … From:



customer@acme.org To:





Paul
King Subject:
Project
Request Date:



Early
morning Dear
Paul, We
would
like
a
DSL
for
capturing
our
business
 rules.
Our
business
rules
are
captured
in
the
form
 of
logic
clauses.
They
are
very
much
like
those
 found
in
Einstein’s
riddle. Thanks,
Happy
Customer62 @glaforge — @paulk_asert
  • Einstein’s Riddle• Wikipedia: The zebra puzzle is a well-known logic puzzle – It is often called Einsteins Puzzle or Einsteins Riddle because it is said to have been invented by Albert Einstein as a boy, with the claim that Einstein said “only 2 percent of the worlds population can solve it.” – The puzzle is also sometimes attributed to Lewis Carroll. However, there is no known evidence for Einsteins or Carrolls authorship; and the original puzzle cited below mentions brands of cigarette, such as Kools, that did not exist during Carrolls lifetime or Einsteins boyhood63 @glaforge — @paulk_asert
  • Einstein’s Riddle• Some premises: – The British person lives in the red house – The Swede keeps dogs as pets – The Dane drinks tea – The green house is on the left of the white house – The green homeowner drinks coffee – The man who smokes Pall Mall keeps birds – The owner of the yellow house smokes Dunhill – The man living in the center house drinks milk – The Norwegian lives in the first house – The man who smokes Blend lives next to the one who keeps cats – The man who keeps the horse lives next to the man who smokes Dunhill – The man who smokes Bluemaster drinks beer – The German smokes Prince – The Norwegian lives next to the blue house64 @glaforge — @paulk_asert
  • Einstein’s Riddle• Some premises: – The British person lives in the red house • And a question: – The Swede keeps dogs as pets – Who owns the fish? – The Dane drinks tea – The green house is on the left of the white house – The green homeowner drinks coffee – The man who smokes Pall Mall keeps birds – The owner of the yellow house smokes Dunhill – The man living in the center house drinks milk – The Norwegian lives in the first house – The man who smokes Blend lives next to the one who keeps cats – The man who keeps the horse lives next to the man who smokes Dunhill – The man who smokes Bluemaster drinks beer – The German smokes Prince – The Norwegian lives next to the blue house64 @glaforge — @paulk_asert
  • Einstein’s Riddle : Prolog%
from
http://www.baptiste‐wicht.com/2010/09/solve‐einsteins‐riddle‐using‐prolog%
Preliminary
definitionspersons(0,
[])
:‐
!.persons(N,
[(_Men,_Color,_Drink,_Smoke,_Animal)|T])
:‐
N1
is
N‐1,
persons(N1,T).person(1,
[H|_],
H)
:‐
!.person(N,
[_|T],
R)
:‐
N1
is
N‐1,
person(N1,
T,
R).%
The
Brit
lives
in
a
red
househint1([(brit,red,_,
_,
_)|_]).hint1([_|T])
:‐
hint1(T).%
The
Swede
keeps
dogs
as
petshint2([(swede,_,_,_,dog)|_]).hint2([_|T])
:‐
hint2(T).%
The
Dane
drinks
teahint3([(dane,_,tea,_,_)|_]).hint3([_|T])
:‐
hint3(T).%
The
Green
house
is
on
the
left
of
the
White
house
hint4([(_,green,_,_,_),(_,white,_,_,_)|_]).hint4([_|T])
:‐
hint4(T).%
The
owner
of
the
Green
house
drinks
coffee.hint5([(_,green,coffee,_,_)|_]).hint5([_|T])
:‐
hint5(T)....65 @glaforge — @paulk_asert
  • Einstein’s Riddle DSL From:



customer@acme.org To:





Paul
King Subject:
Project
Request Date:



Early
morning Dear
Paul, Thanks
for
your
Prolog
solution
but
we
don’t
have Prolog
installed.
Do
you
have
a
version
that
runs on
the
JVM? Thanks,
Happy
Customer66 @glaforge — @paulk_asert
  • Einstein’s Riddle : Polyglot@GrabResolver(https://oss.sonatype.org/content/repositories/snapshots/)@Grab(org.prolog4j:prolog4j‐api:0.2.1‐SNAPSHOT)@Grab(org.prolog4j:prolog4j‐tuprolog:0.2.1‐SNAPSHOT)import
org.prolog4j.*def
p
=
ProverFactory.proverp.addTheory(new
File(/GroovyExamples/tuProlog/src/einstein.pl).text)def
sol
=
p.solve("solution(Persons).")println
sol.get(Persons)67 @glaforge — @paulk_asert
  • Einstein’s Riddle DSL … From:



customer@acme.org To:





Paul
King Subject:
Project
Request Date:



Early
morning Dear
Paul, Thanks
for
that
version
but
in
terms
of
 maintaining
the
rules,
we
would
like
a
more
 fluent
expression
of
the
rules
rather
than
 Prolog?
Any
thoughts? Thanks,
Happy
Customer68 @glaforge — @paulk_asert
  • Einstein’s Riddle : Polyglot w/ DSL //
define
some
domain
classes
and
objects enum
Pet
{
dog,
cat,
bird,
fish,
horse
} enum
Color
{
green,
white,
red,
blue,
yellow
} enum
Smoke
{
dunhill,
blends,
pallmall,
prince,
bluemaster
} enum
Drink
{
water,
tea,
milk,
coffee,
beer
} enum
Nationality
{
Norwegian,
Dane,
Brit,
German,
Swede
} 
 dogs
=
dog;
birds
=
bird;
cats
=
cat;
horses
=
horse a
=
owner
=
house
=
the
=
abode
=
person
=
man
=
is
=
to
= 



side
=
next
=
who
=
different
=
ignored //
some
preliminary
definitions p
=
ProverFactory.prover hintNum
=
1 
 p.addTheory( 



persons(0,
[])
:‐
!. 



persons(N,
[(_Men,_Color,_Drink,_Smoke,_Animal)|T])
:‐
N1
is
N‐1,
persons(N1,T). 



person(1,
[H|_],
H)
:‐
!. 



person(N,
[_|T],
R)
:‐
N1
is
N‐1,
person(N1,
T,
R). )69 @glaforge — @paulk_asert
  • Einstein’s Riddle : Polyglot w/ DSL… //
define
some
helper
methods
(our
interface
to
prolog)
 def
addPairHint(Map
m)
{ 



def
from
=
m.from?.toString()?.toLowerCase() 



p.addTheory(""" 



hint$hintNum([(${from
?:
_},${m.color
?:
_},${m.drink
?:
_}, 







${m.smoke
?:
_},${m.pet
?:
_})|_]). 



hint$hintNum([_|T])
:‐
hint$hintNum(T). 



""") 



hintNum++ } 
 def
addPositionHint(Map
m,
int
pos)
{ 



def
from
=
m.from?.toString()?.toLowerCase() 



p.addTheory(""" 







hint$hintNum(Persons)
:‐
person($pos,
Persons,
(${from
?:
_}, 







${m.color
?:
_},$



{m.drink
?:
_},${m.smoke
?:
_},${m.pet
?:
_})). 



""") 



hintNum++ } 
 def
addToLeftHint(Map
left,
Map
right)
{ 



p.addTheory(""" 







hint$hintNum([(_,$left.color,_,_,_),(_,$right.color,_,_,_)|_]). 







hint$hintNum([_|T])
:‐
hint$hintNum(T). 



""") 



hintNum++ } ...70 @glaforge — @paulk_asert
  • Einstein’s Riddle : Polyglot w/ DSL //
now
implement
DSL
in
terms
of
helper
methods def
the(Nationality
n)
{ 



def
ctx
=
[from:n] 



[ 







drinks:
{
d
‐>
addPairHint(ctx
+
[drink:d])
}, 







smokes:
{
s
‐>
addPairHint(ctx
+
[smoke:s])
}, 







keeps:
{
p
‐>
addPairHint(ctx
+
[pet:p])
}, 







rears:
{
p
‐>
addPairHint(ctx
+
[pet:p])
}, 







owns:
{
_the
‐>
[first:
{
house
‐>
addPositionHint(ctx,
1)
}]
}, 







has:
{
_a
‐> 











[pet:
{
a
‐>
addPairHint(ctx
+
[pet:a])
}]
+ 















Color.values().collectEntries
{
c
‐> 



















[c.toString(),
{
_dummy
‐>
addPairHint(ctx
+
[color:c])
}
] 















} 







}, 







lives:
{
_next
‐>
[to:
{
_the
‐> 











Color.values().collectEntries{
c
‐> 















[c.toString(),
{
_dummy
‐>
addNeighbourHint(ctx,
[color:c])
}
] 











} 







}]} 



] } ...71 @glaforge — @paulk_asert
  • Einstein’s Riddle : Polyglot w/ DSL//
now
define
the
DSLthe
man
from
the
centre
house
drinks
milkthe
Norwegian
owns
the
first
housethe
Dane
drinks
teathe
German
smokes
princethe
Swede
keeps
dogs
//
alternate
ending:
has
a
pet
dogthe
Brit
has
a
red
house

//
alternate
ending:
red
abodethe
owner
of
the
green
house
drinks
coffeethe
owner
of
the
yellow
house
smokes
dunhillthe
person
known
to
smoke
pallmall
rears
birds
//
alternate
end:
keeps
birdsthe
man
known
to
smoke
bluemaster
drinks
beerthe
green
house
is
on
the
left
side
of
the
white
housethe
man
known
to
smoke
blends
lives
next
to
the
one
who
keeps
catsthe
man
known
to
keep
horses
lives
next
to
the
man
who
smokes
dunhillthe
man
known
to
smoke
blends
lives
next
to
the
one
who
drinks
waterthe
Norwegian
lives
next
to
the
blue
house72 @glaforge — @paulk_asert
  • Einstein’s Riddle DSL From:



customer@acme.org To:





Paul
King Subject:
Project
Request Date:



Early
morning Dear
Paul, That’s
great.
We
even
get
some
code
 completion
When
using
an
IDE.
Is
there
 anything
more
we
can
do
to
get
more
 completion? Thanks,
Happy
Customer73 @glaforge — @paulk_asert
  • Einstein’s Riddle : Polyglot w/ DSL //
now
implement
DSL
in
terms
of
helper
methods def
the(Nationality
n)
{ 



def
ctx
=
[from:n] 



[ 







drinks:
{
d
‐>
addPairHint(ctx
+
[drink:d])
}, 







smokes:
{
s
‐>
addPairHint(ctx
+
[smoke:s])
}, 







keeps:
{
p
‐>
addPairHint(ctx
+
[pet:p])
}, 







... 



] } ... the
German
smokes
prince the(German).smokes(prince)n
=
Germanctx
=
[from:
German][drinks:
…,
smokes:
{
s
‐>
addPairHint([from:
German,
smoke:
s])
},
keeps:
…,
… addPairHint([from:
German,
smoke:
] prince]) 74 @glaforge — @paulk_asert
  • Einstein’s Riddle : Polyglot w/ DSL• Some parts of our DSL are automatically statically inferred, e.g. typing ‘bl’ and then asking for completion yields:• But other parts are not known, e.g. the word ‘house’ in the fragment below: ‘house’ is key for a Map and could be any value75 @glaforge — @paulk_asert
  • Einstein’s Riddle : Polyglot w/ DSL def
the(Color
c1)
{[ 



house:
{
_is
‐>
[on:
{
_the
‐>
[left:
{
_side
‐>
[of:
{
__the
‐> 







Color.values().collectEntries{
c2
‐>
[c2.toString(),
{
_dummy
‐> 











addToLeftHint([color:c1],
[color:c2]) 







}]} 



}]}]}]} ]} class
HousePlaceHolder
{ 



def
c1,
script 



def
house(_is)
{ 







[on:
{
_the
‐>
[left:
{
_side
‐>
[of:
{
__the
‐> 











Color.values().collectEntries
{
c2
‐> 















[c2.toString(),
{
_dummy
‐>
script.addToLeftHint( 



















[color:
c1],
[color:
c2]
)}]} 







}]}]}] 



} } def
the(Color
c1)
{
new
HousePlaceHolder(c1:c1,
script:this)
}76 @glaforge — @paulk_asert ‘house’ is now understood
  • Einstein’s Riddle DSL From:



customer@acme.org To:





Paul
King Subject:
Project
Request Date:



Early
morning Dear
Paul, That’s
fantastic!
But
we
have
just
started standardizing
on
Choco
as
our
logic
solving engine.
I
guess
we
need
to
start
from
scratch. Let
me
know
what
you
think. Thanks,
Happy
Customer77 @glaforge — @paulk_asert
  • Einstein’s Riddle : Choco w/ DSL @GrabResolver(http://www.emn.fr/z‐info/choco‐solver/mvn/repository/) @Grab(choco:choco:2.1.1‐SNAPSHOT) import
static
choco.Choco.* import
choco.kernel.model.variables.integer.* 
 def
m
=
new
choco.cp.model.CPModel() m.metaClass.plus
=
{
m.addConstraint(it);
m
} def
s
=
new
choco.cp.solver.CPSolver() choco.Choco.metaClass.static.eq
=
{
c,
v
‐>
delegate.eq(c,
v.ordinal())
} def
makeEnumVar(st,
arr)
{
 



choco.Choco.makeIntVar(st,
0,
arr.size()‐1,
choco.Options.V_ENUM)
} pets
=
new
IntegerVariable[num] colors
=
new
IntegerVariable[num] smokes
=
new
IntegerVariable[num] drinks
=
new
IntegerVariable[num] nations
=
new
IntegerVariable[num] 
 (0..<num).each
{
i
‐> 





pets[i]
=
makeEnumVar("pet$i",


pets) 



colors[i]
=
makeEnumVar("color$i",
colors) 



smokes[i]
=
makeEnumVar("smoke$i",
smokes) 



drinks[i]
=
makeEnumVar("drink$i",
drinks) 


nations[i]
=
makeEnumVar("nation$i",

nations) } ...78 @glaforge — @paulk_asert
  • Einstein’s Riddle : Choco w/ DSL //
define
DSL
(simplistic
non‐refactored
version) def
neighbours(var1,
val1,
var2,
val2)
{ 



and( 







ifOnlyIf(eq(var1[0],
val1),
eq(var2[1],
val2)), 







implies(eq(var1[1],
val1),
or(eq(var2[0],
val2),
eq(var2[2],
val2))), 







implies(eq(var1[2],
val1),
or(eq(var2[1],
val2),
eq(var2[3],
val2))), 







implies(eq(var1[3],
val1),
or(eq(var2[2],
val2),
eq(var2[4],
val2))), 







ifOnlyIf(eq(var1[4],
val1),
eq(var2[3],
val2)) 



) } iff
=
{
e1,
c1,
e2,
c2
‐>
and(*(0..<num).collect{ 



ifOnlyIf(eq(e1[it],
c1),
eq(e2[it],
c2)) })
} ... //
define
the
DSL
in
terms
of
DSL
implementation def
the(Nationality
n)
{ def
ctx
=
[nations,
n] [ 



drinks:
iff.curry(*ctx,
drinks), 



smokes:
iff.curry(*ctx,
smokes), 



keeps:
iff.curry(*ctx,
pets), 



rears:
iff.curry(*ctx,
pets), 



owns:
{
_the
‐>
[first:
{
house
‐>
eq(nations[first],
n)}]
}, ...79 @glaforge — @paulk_asert
  • Einstein’s Riddle : Choco w/ DSL //
define
rules m
+=
all
pets
are
different m
+=
all
colors
are
different m
+=
all
smokes
are
different m
+=
all
drinks
are
different m
+=
all
nations
are
different m
+=
the
man
from
the
centre
house
drinks
milk m
+=
the
Norwegian
owns
the
first
house m
+=
the
Dane
drinks
tea m
+=
the
German
smokes
prince m
+=
the
Swede
keeps
dogs
//
alternate
ending:
has
a
pet
dog m
+=
the
Brit
has
a
red
house

//
alternate
ending:
red
abode m
+=
the
owner
of
the
green
house
drinks
coffee m
+=
the
owner
of
the
yellow
house
smokes
dunhill m
+=
the
person
known
to
smoke
pallmall
rears
birds
//
alt
end:
keeps
birds m
+=
the
man
known
to
smoke
bluemaster
drinks
beer m
+=
the
green
house
is
on
the
left
side
of
the
white
house m
+=
the
man
known
to
smoke
blends
lives
next
to
the
one
who
keeps
cats m
+=
the
man
known
to
keep
horses
lives
next
to
the
man
who
smokes
dunhill m
+=
the
man
known
to
smoke
blends
lives
next
to
the
one
who
drinks
water m
+=
the
Norwegian
lives
next
to
the
blue
house ...80 @glaforge — @paulk_asert
  • Einstein’s Riddle : Choco w/ DSL def
pretty(s,
c,
arr,
i)
{
 



c.values().find{
it.ordinal()
==
s.getVar(arr[i])?.value
}
 } 
 //
invoke
logic
solver s.read(m) def
more
=
s.solve() while
(more)
{ 



for
(i
in
0..<num)
{ 







print


The













+
pretty(s,
Nationality,
nations,
i) 







print



has
a
pet






+
pretty(s,
Pet,
pets,
i) 







print



smokes









+
pretty(s,
Smoke,
smokes,
i) 







print



drinks









+
pretty(s,
Drink,
drinks,
i) 







println

and
lives
in
a

+
pretty(s,
Color,
colors,
i)
+

house 



} 



more
=
s.nextSolution() } Solving
Einsteins
Riddle: The
Norwegian
has
a
pet
cat
smokes
dunhill
drinks
water
and
lives
in
a
yellow
house The
Dane
has
a
pet
horse
smokes
blends
drinks
tea
and
lives
in
a
blue
house The
Brit
has
a
pet
bird
smokes
pallmall
drinks
milk
and
lives
in
a
red
house The
German
has
a
pet
fish
smokes
prince
drinks
coffee
and
lives
in
a
green
house The
Swede
has
a
pet
dog
smokes
bluemaster
drinks
beer
and
lives
in
a
white
house81 @glaforge — @paulk_asert
  • FRIDAY 82 @glaforge — @paulk_aserthttp://t3.gstatic.com/images?q=tbn:mgkj1lXpQ2-uWM:http://i298.photobucket.com/albums/mm269/bearyjuicylicious/comments
  • Nasa Robot From:



customer@acme.org To:





Guillaume
Laforge Subject:
Project
Request Date:



Early
morning Dear
Guillaume, We’ve
got
a
contract
with
NASA
to
send
a
 rover
on
Mars.
We’d
need
a
DSL
to
lead
 the
robot
on
the
rocky
soil.
Can
you
 help? Thanks,
Happy
Customer83 @glaforge — @paulk_asert
  • Nasa Robot import
static
Direction.* enum
Direction
{ 



left,
right,
forward,
backward } class
Robot
{ 



void
move(Direction
dir)
{ 







println
"robot
moved
$dir" 



} } Nothing special here. def
robot
=
new
Robot() But can we make it prettier? robot.move
left84 @glaforge — @paulk_asert
  • Nasa Robotpackage
projectmars.domainimport
static
Direction.*enum
Direction
{
left,
right,
forward,
backward
}class
Robot
{



void
move(Direction
dir)
{ Some simplistic GroovyShell







println
"robot
moved
$dir"



}} package
projectmars.integration def
shell
=
new
GroovyShell(this.class.classLoader) shell.evaluate
 



import
projectmars.domain.Robot 



import
static
projectmars.domain.Direction.* 



 



def
robot
=
new
Robot() 



 



robot.move
left 85 @glaforge — @paulk_asert
  • …Nasa Robot… package
projectmars.domain import
static
Direction.* enum
Direction
{
left,
right,
forward,
backward
} class
Robot
{ 



void
move(Direction
dir)
{ 







println
"robot
moved
$dir" 



} } Inject robot into binding def
binding
=
new
Binding(robot:
new
Robot()) def
shell
=
new
GroovyShell(this.class.classLoader,
binding) shell.evaluate
 



import
static
projectmars.domain.Direction.* 



robot.move
left 86 @glaforge — @paulk_asert
  • Nasa Robot package
projectmars.domain import
static
Direction.* enum
Direction
{
left,
right,
forward,
backward
} class
Robot
{ 



void
move(Direction
dir)
{ 







println
"robot
moved
$dir" 



} } /* Inject directions into binding def
binding
=
new
Binding( 



robot:
new
Robot(), 



left:
Direction.left,
right:
Direction.right, 



forward:
Direction.forward,
backward:
Direction.backward) */ def
binding
=
new
Binding( 



robot:
new
Robot(), 



*:Direction.values().collectEntries
{
[(it.name()):
it]
}) def
shell
=
new
GroovyShell(this.class.classLoader,
binding) shell.evaluate
 



robot.move
left 87 @glaforge — @paulk_asert
  • …Nasa Robot…package
projectmars.domainimport
org.codehaus.groovy.control.CompilerConfigurationimport
org.codehaus.groovy.control.customizers.*import
static
Direction.*enum
Direction
{
left,
right,
forward,
backward
}class
Robot
{



void
move(Direction
dir)
{
println
"robot
moved
$dir"
}}def
binding
=
new
Binding(robot:
new
Robot()) Inject importdef
importCustomizer
=
new
ImportCustomizer()importCustomizer.addStaticStars
projectmars.domain.Directiondef
config
=
new
CompilerConfiguration()config.addCompilationCustomizers
importCustomizerdef
shell
=
new
GroovyShell(this.class.classLoader,
binding,
config)shell.evaluate




robot.move
left88 @glaforge — @paulk_asert
  • package
projectmars.domain…Nasa Robot…import
org.codehaus.groovy.control.CompilerConfiguration import
org.codehaus.groovy.control.customizers.* import
static
Direction.* enum
Direction
{
left,
right,
forward,
backward
} class
Robot
{ 



void
move(Direction
dir)
{
println
"robot
moved
$dir"
} } def
binding
=
new
Binding(robot:
new
Robot()) def
importCustomizer
=
new
ImportCustomizer() importCustomizer.addStaticStars
projectmars.domain.Direction def
config
=
new
CompilerConfiguration() config.addCompilationCustomizers
importCustomizer def
shell
=
new
GroovyShell(this.class.classLoader,
binding,
config) shell.evaluate
 



move
left 
+
 



def
move(dir)
{ 







robot.move
dir Can we make ‘robot’ implicit? 



} 89 @glaforge — @paulk_asert
  • package
projectmars.domainNasa Robotimport
org.codehaus.groovy.control.CompilerConfiguration import
org.codehaus.groovy.control.customizers.* import
static
Direction.* enum
Direction
{
left,
right,
forward,
backward
} class
Robot
{ 



void
move(Direction
dir)
{
println
"robot
moved
$dir"
} } def
binding
=
new
Binding(robot:
new
Robot()) def
importCustomizer
=
new
ImportCustomizer() importCustomizer.addStaticStars
Direction.class.name def
config
=
new
CompilerConfiguration() Better ‘robot’ implicit config.addCompilationCustomizers
importCustomizer config.scriptBaseClass
=
RobotBaseScriptClass.class.name def
shell
=
new
GroovyShell(this.class.classLoader,
binding,
config) shell.evaluate
 



move
left abstract
class
RobotBaseScriptClass
extends
Script
{ 



void
move(Direction
dir)
{ 







this.binding.robot.move
dir 



} }90 @glaforge — @paulk_asert
  • package
projectmars.domainNasa Robotimport
org.codehaus.groovy.control.CompilerConfigurationimport
org.codehaus.groovy.control.customizers.*import
static
Direction.*enum
Direction
{
left,
right,
forward,
backward
}class
Robot
{



void
move(Direction
dir)
{
println
"robot
moved
$dir"
}} Better ‘robot’ implicitdef
robot
=
new
Robot()def
binding
=
new
Binding(robot:
robot,
move:
robot.&move)def
importCustomizer
=
new
ImportCustomizer()importCustomizer.addStaticStars
Direction.class.namedef
config
=
new
CompilerConfiguration()config.addCompilationCustomizers
importCustomizerdef
shell
=
new
GroovyShell(this.class.classLoader,
binding,
config)shell.evaluate




move
left91 @glaforge — @paulk_asert
  • …Nasa Robot shell.evaluate
 



mOvE
lefT Case insensitive? abstract
class
RobotBaseScriptClass
extends
Script
{ 



@Delegate
@Lazy
Robot
robot
=
this.binding.robot 



 



def
invokeMethod(String
name,
args)
{ 







robot."${name.toLowerCase()}"(*args) 



} } class
CustomBinding
extends
Binding
{ 



private
Map
variables 



 



CustomBinding(Map
vars)
{ 







this.variables
=
[ 











*:vars,
 











*:Direction.values().collectEntries
{
[(it.name()):
it]
} 







] 



} 



 



def
getVariable(String
name)
{ 







variables[name.toLowerCase()] 



}92 } @glaforge — @paulk_asert
  • package
projectmars.domainNasa Robot import
org.codehaus.groovy.control.CompilerConfiguration import
org.codehaus.groovy.control.customizers.* import
groovy.transform.TupleConstructor import
static
Direction.* import
static
Duration.* enum
Direction
{ 



left,
right,
forward,
backward } enum
Unit
{ 



centimeter
(cm,
0.01), Going further? 



meter





(
m,



1), 



kilometer

(km,
1000)



 



String
abbreviation 



double
multiplier 



 



Unit(String
abbr,
double
mult)
{ 







this.abbreviation
=
abbr 







this.multiplier
=
mult 



} 



 



String
toString()
{
abbreviation
} } …93 @glaforge — @paulk_asert
  • … …Nasa Robot…enum
Duration
{
hour
}@TupleConstructor …class
Distance
{ class
DistanceCategory
{



double
amount 



static
Distance
getCentimeters(Number
num)
{



Unit
unit 







new
Distance(num,
Unit.centimeter)



 



}



Speed
div(Duration
dur)
{ 



static
Distance
getMeters(Number
num)
{







new
Speed(amount,
unit) 







new
Distance(num,
Unit.meter)



} 



}



 



static
Distance
getKilometers(Number
num)
{



String
toString()
{
"$amount$unit"
} 







new
Distance(num,
Unit.kilometer)} 



} 



static
Distance
getCm(Number
num)
{
getCentimeters(num)
}@TupleConstructor 



static
Distance
getM(Number
num)

{
getMeters(num)
}class
Speed
{ 



static
Distance
getKm(Number
num)
{
getKilometers(num)
}



double
amount }



Unit
unit



 class
Robot
{



String
toString()
{
"$amount
$unit/h"
} 



void
move(Direction
dir)
{} 







println
"robot
moved
$dir"… 



} 



void
move(Direction
dir,
Distance
d)
{ 







println
"robot
moved
$dir
by
$d" 



} 



void
move(Map
m,
Direction
dir)
{ 







println
"robot
moved
$dir
by
$m.by
at
${m.at
?:
1
km 



} } … 94 @glaforge — @paulk_asert
  • …Nasa Robot… … def
binding
=
new
Binding(robot:
new
Robot(),
h:
Duration.hour) def
importCustomizer
=
new
ImportCustomizer() importCustomizer.addStaticStars
Direction.class.name def
config
=
new
CompilerConfiguration() config.addCompilationCustomizers
importCustomizer config.scriptBaseClass
=
RobotBaseScriptClass.class.name def
shell
=
new
GroovyShell(this.class.classLoader,
binding,
config) use(DistanceCategory)
{ shell.evaluate
 



move
left 



move
right,
3.meters 



move
right,
by:
3.meters 



move
right,
by:
3.meters,
at:
5.km/h } abstract
class
RobotBaseScriptClass
extends
Script
{ 



@Delegate
@Lazy
Robot
robot
=
this.binding.robot }95 @glaforge — @paulk_asert
  • Nasa Robot Going even further? shell.evaluate
 



move
left 



move
right,
3.meters 
 



move
right,
by:
3.meters 



move
right,
by:
3.meters,
at:
5.km/h 
 



move
right
by
3.meters
at
5.km/h

 
 



deploy
left
arm 96 @glaforge — @paulk_asert
  • Domain-Specific Language Descriptors• DSLs are nice, but what about support in my IDE?• DSLD to the rescue – Domain-Specific Language Descriptors – for Eclipse STS (but GDLS concent available in IntelliJ IDEA too)• Idea: a DSL for describing DSLs, in order to provide – code-completion – code navigation – documentation hovers97 @glaforge — @paulk_asert
  • Domain-Specific Language Descriptors98 @glaforge — @paulk_asert
  • Domain-Specific Language Descriptors currentType(
subType(
Number
)
).accept
{ 



[m:
"meter",
yd:
"yard",
cm:
"centimeter",
mi:
"mile",
km:
"kilometer"].each
{ 







property
name:it.key,
type:"Distance", 











doc:
"""A
<code>${it.value}</code>
from
<a
href="$url">$url</a>""" 



} }98 @glaforge — @paulk_asert
  • Domain-Specific Language Descriptors currentType(
subType(
Number
)
).accept
{ 



[m:
"meter",
yd:
"yard",
cm:
"centimeter",
mi:
"mile",
km:
"kilometer"].each
{ 







property
name:it.key,
type:"Distance", 











doc:
"""A
<code>${it.value}</code>
from
<a
href="$url">$url</a>""" 



} }98 @glaforge — @paulk_asert
  • Important business meetings all afternoon! 99 @glaforge — @paulk_aserthttp://www.flickr.com/photos/bobswanson/5093775403/sizes/l/in/pool-52240148330@N01/
  • @paulk _asert@ glaforge
  • Q&A — Got questions, Really?101 © 2011 SpringOne 2GX 2011. All rights reserved. Do not distribute without permission.
  • Image credits• Pros and Cons: http://allrmc.com/images/pros_cons.gif• Bicycle: http://drunkcyclist.com/gallery/main.php?g2_view=core.DownloadItem&g2_itemId=2131&g2_serialNumber=2• Pills: http://www.we-ew.com/wp-content/uploads/2011/01/plan-b-pills.jpg• Chains: http://2.bp.blogspot.com/-GXDVqUYSCa0/TVdBsON4tdI/AAAAAAAAAW4/EgJOUmAxB28/s1600/breaking-chains5_copy9611.jpg• Stock exchange graph: http://www.inet.mn/ispages/isimage/inetnews/y80ni17/42349_77215.jpeg• Nuclear plant: http://www.exponent.com/files/Uploads/Images/Energy/nuclear%20plant.jpg• Dollars: http://padma-bali.com/wp-content/uploads/2011/04/Online-Payday-Loan-1.jpg• Insurance: http://lemeilleurtauxcredit.fr/wp-content/uploads/insurance.jpg• HR: http://www.montekservices.com/wp-content/uploads/2011/08/HR-outsourcing.jpg• Bacteria: http://www.editionsmondialis.com/wp-content/uploads/2010/11/malaria-red.jpg• Email: http://www.tenso.fr/emarketing/wp-content/uploads/2011/02/email.jpg• Thank you: http://www.teachermum.com/wp-content/uploads/2011/07/thank-you1.jpg•102 @glaforge — @paulk_asert