SlideShare a Scribd company logo
1 of 194
Download to read offline
Chicago, October 19 - 22, 2010
Paul King
@paulk_asert
Groovy Committer
ASERT, Australia
Groovy.DSLs(from: beginner,
to: expert)
Guillaume Laforge
@glaforge
Groovy Project Manager
SpringSource / VMWare
Topics
Introduction
• Groovy DSL Features
• A Week In The Life Of DSL INC
• Summary
• More Info
DSLs 2010 - 2
©ASERT2006-2010
What is a DSL?
• 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, general-purpose languages are created to solve
problems in many domains
– Somewhere between declarative data and a full blown general-
purpose programming language (GPL)
– AKA: fluent / human interfaces, language oriented
programming, problem-oriented languages, little / mini
languages, macros, business natural languages
Sources:
http://en.wikipedia.org/wiki/Domain-specific_language
van Deursen, A., Klint, P., Visser, J.: Domain-specific languages: an annotated bibliography. ACM SIGPLAN Notices 35 (2000) 26–36
©ASERT2006-2010
1. e4 e5
2. Nf3 Nc6
3. Bb5 a6
L2 U F-1 B L2 F B -1 U L2
Visual!
Technical Examples
Source: Applying minilanguages: http://www.faqs.org/docs/artu/ch08s02.html
<?xml version="1.0"?>
<xsl:stylesheetversion="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<xsl:template match="*">
<xsl:element name="{name()}">
<xsl:for-each select="@*">
<xsl:element name="{name()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
<xsl:apply-templates select="*|text()"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
cat thesis.ms | chem | tbl | refer | grap | pic | eqn | groff -Tps > thesis.ps
<?xml version="1.0"?>
<GTK-Interface>
<widget>
<class>GtkWindow</class>
<name>HelloWindow</name>
<border_width>5</border_width>
<Signal>
<name>destroy</name>
<handler>gtk_main_quit</handler>
</Signal>
<title>Hello</title>
<type>GTK_WINDOW_TOPLEVEL</type>
<position>GTK_WIN_POS_NONE</position>
<allow_shrink>True</allow_shrink>
<allow_grow>True</allow_grow>
<auto_shrink>False</auto_shrink>
<widget>
<class>GtkButton</class>
<name>Hello World</name>
<can_focus>True</can_focus>
<label>Hello World</label>
</widget>
</widget>
</GTK-Interface>
# Poll this site first each cycle.
poll pop.provider.net proto pop3
user "jsmith" with pass "secret1" is "smith" here
user jones with pass "secret2" is "jjones" here with options keep
# Poll this site second, unless Lord Voldemort zaps us first.
poll billywig.hogwarts.com with proto imap:
user harry_potter with pass "floo" is harry_potter here
# Poll this site third in the cycle.
# Password will be fetched from ~/.netrc
poll mailhost.net with proto imap:
user esr is esr here
Glade
Troff
XSLT
fetchmail
Regex
"x.z?z{1,3}y"
SQL
SELECT * FROM TABLE
WHERE NAME LIKE '%SMI'
ORDER BY NAME
DSLs 2010 - 7
Origins
• It has always been a
holy grail of computer
users to be able to
speak directly to our
computers
• Different languages have pushed the
boundaries further than others
– APT for numerically controlled machine tools (1957)
– BNF (1959), COBOL, 4GLs
– LISP & Smalltalk
– Unix "little languages"
DSLs 2010 - 8
©ASERT2006-2010
Origins
• It has always been a
holy grail of computer
users to be able to
speak directly to our
computers
DSLs 2010 - 9
http://dsal.uchicago.edu/reference/gazetteer/pager.html?objectid=DS405.1.I34_V02_298.gif
©ASERT2006-2010
Origins: LISP & Smalltalk
“In Lisp, you don’t just write your
program down toward the language,
you also build the language up
toward your program”
- Paul Graham
“When [Smalltalk] is used to
describe an application system, the
developer extends Smalltalk,
creating a domain-specific language
by adding a new vocabulary of
language elements ...”
- Adele Goldberg
DSLs 2010 - 10
©ASERT2006-2010
Origins: Minilanguages...
Source: The Art of Unix Programming:Taxonomy of languages: http://www.faqs.org/docs/artu/ch08s01.html
%!PS-Adobe-3.0
%%Creator: groff version 1.20.1
...
597.6 12 72 12 DL(increasing loopiness)297.71 8.2 Q(/etc/passwd)102.67
94.6 Q(.ne)110.715 106.6 Q(wsrc)-.25 E(SNG)195.2 100.6 Q(re)243.8 94.6 Q
(ge)-.15 E(xps)-.15 E(Glade)247.26 106.6 Q(m4)306.81 58.6 Q -1(Ya)303.43
70.6 S(cc)1 E(Le)305.5 82.6 Q(x)-.15 E(mak)302.42 94.6 Q(e)-.1 E(XSL)
301.16 106.6 Q(T)-.92 E(pic)307.09 118.6 Q(tbl)307.92 130.6 Q(eqn)305.98
142.6 Q(fetchmail)344.715 82.6 Q -.15(aw)355.345 94.6 S(k).15 E(trof)
354.84 106.6 Q(f)-.25 E(Postscript)343.875 118.6 Q(dc)412.88 94.6 Q(bc)
412.88 106.6 Q(Emacs Lisp)462.53 94.6 Q(Ja)465.395 106.6 Q -.25(va)-.2 G
(Script).25 E(sh)529.075 94.6 Q(tcl)528.52 106.6 Q(Perl)565.065 88.6 Q
(Python)558.95 100.6 Q(Ja)564.46 112.6 Q -.25(va)-.2 G 0 Cg EP
%%Trailer
end
%%EOF
Input DSL: pic for above picture Output "DSL": Postscript for above picture
# Minilanguage taxonomy
# Base ellipses
define smallellipse {ellipse width 3.0 height 1.5}
M: ellipse width 3.0 height 1.8 fill 0.2
line from M.n to M.s dashed
D: smallellipse() with .e at M.w + (0.8, 0)
line from D.n to D.s dashed
I: smallellipse() with .w at M.e - (0.8, 0)
# Arrow headings
arrow from D.w + (0.4, 0.8) to D.e + (-0.4, 0.8)
"flat to structured" "" at last arrow.c
...
# Interpreters
"Emacs Lisp" "JavaScript" at 0.25 between M.e and I.e
"sh" "tcl" at 0.55 between M.e and I.e
"Perl" "Python" "Java" at 0.8 between M.e and I.e
DSLs 2010 - 11
...Origins: Minilanguages
PowerPoint DSL for previous slide
import builder.PowerPointBuilder
def name = 'minilanguages'
assert new File("${name}.pic').exists()
"groff -e -p ${name}.pic > ${name}.ps".execute()
"gs -q -sDEVICE='ppmraw' -g2600x3500 -r300x300 -sOutputFile='-' -dBATCH ↵
–dNOPAUSE ${name}.ps | pnmcrop | ppmtogif > ${name}.gif".execute()
def builder = new PowerPointBuilder() // Adapted from Erik Pragt
builder.slideshow(filename: 'DSLsInGroovy_MiniLanguages.ppt') {
slide(title: 'Origins: Minilanguages...') {
image(
origin: [0, 15],
src: "${name}.gif",
caption: 'Source: The Art of Unix Programming:Taxonomy...' )
textbox(
origin: [5, 100],
text: new File("${name}.pic").text,
caption: 'Input DSL: pic for above picture' )
textbox(
origin: [115, 100],
text: new File("${name}.ps").text,
caption: 'Output "DSL": Postscript for above picture' )
}
}
DSLs 2010 - 12
Subject Matter Experts,
Business Analysts...
HAI CAN HAS STDIO?
I HAS A VAR
IM IN YR LOOP
UP VAR!!1
VISIBLE VAR
IZ VAR BIGGER THAN 10?
KTHXBYE
IM OUTTA YR LOOP
KTHXBYE
Developer producing LOLCODE
And in the end...
nobody understands
each other
DSL: a potential solution?
1616
• 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
too much boilerplate technical code
• Cleanly separate business logic from
application code
• Let business rules have their own lifecycle
17
Copyright 2010 SpringSource. Copying, publishing or distributing without express written permission is prohibited.
Towards more readibility (1)
20%
17
18
Copyright 2010 SpringSource. Copying, publishing or distributing without express written permission is prohibited.
Towards more readibility (2)
80%
Why a DSL?
• Advantages:
– Domain experts can
understand, validate, modify,
and often even develop DSL
programs
– Somewhat self-documenting
– Enhance quality,
productivity, reliability,
maintainability, portability
and reusability
– Safety; as long as the
language constructs are safe
any sentence written with
them can be considered safe
• Disadvantages:
– Learning cost vs. limited
applicability
– Cost of designing,
implementing & maintaining
DSL as well as the tools/IDEs
– Attaining proper scope
– Trade-offs between domain-
specificity and general-
purpose programming
language constructs
– Efficiency costs
– Proliferation of similar non-
standard DSLs, i.e. different
but similar DSLs used within
two insurance companies
Source: http://en.wikipedia.org/wiki/Domain-specific_language DSLs 2010 - 19
©ASERT2006-2010
DSL usage patterns...
• Internal (uses a general purpose language)
– AKA embedded
– Can be a subset or superset
– Can be generated/converted from other "language"
– Numerous mechanisms to make as close to the
domain as possible
• External (custom language)
– May involve writing your own traditional parser
– Using parser combinators
– Interpreting
– String grepping
• Other characteristics
– Functional vs OO
DSLs 2010 - 20
©ASERT2006-2010
...DSL usage patterns...
Source: DSLs in Action, Debasish Ghosh, Manning now in MEAP DSLs 2010 - 21
©ASERT2006-2010
...DSL usage patterns
Source: DSLs in Action, Debasish Ghosh, Manning now in MEAP DSLs 2010 - 22
©ASERT2006-2010
DSL usage patterns (Advanced)
Source: http://www.spinellis.gr/pubs/jrnl/2000-JSS-DSLPatterns/html/dslpat.html
See: Diomidis Spinellis. Notable design patterns for domain specific languages. Journal of Systems and Software, 56(1):91–99, February 2001.
©ASERT2006-2010
DSLs 2010 - 24
©ASERT2006-2010
What is Groovy?
• “Groovy is like a super version
of Java. It can leverage Java's
enterprise capabilities but also
has cool productivity features like closures,
DSL support, builders and dynamic typing.”
Groovy = Java – boiler plate code
+ optional dynamic typing
+ closures
+ domain specific languages
+ builders
+ metaprogramming
DSLs 2010 - 25
©ASERT2006-2010
Groovy Goodies Overview
• Fully object oriented
• Closures: reusable
and assignable
pieces of code
• Operators can be
overloaded
• Multimethods
• Literal declaration for
lists (arrays), maps,
ranges and regular
expressions
• GPath: efficient
object navigation
• GroovyBeans
• grep and switch
• Templates, builder,
swing, Ant, markup,
XML, SQL, XML-RPC,
Scriptom, Grails,
tests, Mocks
Growing Acceptance …
A slow and steady start but now gaining in
momentum, maturity and mindshare
Now free
DSLs 2010 - 27
©ASERT2006-2010
The Landscape of JVM Languages
Java bytecode calls
for static types
Dynamic features call
for dynamic types
optional
static
types
The terms “Java Virtual Machine” and “JVM” mean a Virtual Machine for the Java™ platform.
DSLs 2010 - 28
©ASERT2006-2010
Groovy Starter
System.out.println("Hello, World!"); // supports Java syntax
println 'Hello, World!' // but can remove some syntax
String name = 'Guillaume' // Explicit typing/awareness
println "$name, I'll get the car." // Gstring (interpolation)
def longer = """${name}, the car
is in the next row.""" // multi-line, implicit type
assert 0.5 == 1/2 // BigDecimal equals()
assert 0.1 + 0.2 == 0.3 // and arithmetic
def printSize(obj) { // implicit/duck typing
print obj?.size() // safe dereferencing
}
def pets = ['ant', 'bee', 'cat'] // native list syntax
pets.each { pet -> // closure support
assert pet < 'dog' // overloading '<' on String
} // or: for (pet in pets)...
A Better Java...
DSLs 2010 - 29
©ASERT2006-2010
import java.util.List;
import java.util.ArrayList;
class Erase {
private List removeLongerThan(List strings, int length) {
List result = new ArrayList();
for (int i = 0; i < strings.size(); i++) {
String s = (String) strings.get(i);
if (s.length() <= length) {
result.add(s);
}
}
return result;
}
public static void main(String[] args) {
List names = new ArrayList();
names.add("Ted"); names.add("Fred");
names.add("Jed"); names.add("Ned");
System.out.println(names);
Erase e = new Erase();
List shortNames = e.removeLongerThan(names, 3);
System.out.println(shortNames.size());
for (int i = 0; i < shortNames.size(); i++) {
String s = (String) shortNames.get(i);
System.out.println(s);
}
}
}
This code
is valid
Java and
valid Groovy
Based on an
example by
Jim Weirich
& Ted Leung
...A Better Java...
DSLs 2010 - 30
©ASERT2006-2010
import java.util.List;
import java.util.ArrayList;
class Erase {
private List removeLongerThan(List strings, int length) {
List result = new ArrayList();
for (int i = 0; i < strings.size(); i++) {
String s = (String) strings.get(i);
if (s.length() <= length) {
result.add(s);
}
}
return result;
}
public static void main(String[] args) {
List names = new ArrayList();
names.add("Ted"); names.add("Fred");
names.add("Jed"); names.add("Ned");
System.out.println(names);
Erase e = new Erase();
List shortNames = e.removeLongerThan(names, 3);
System.out.println(shortNames.size());
for (int i = 0; i < shortNames.size(); i++) {
String s = (String) shortNames.get(i);
System.out.println(s);
}
}
}
Do the
semicolons
add anything?
And shouldn‟t
we us more
modern list
notation?
Why not
import common
libraries?
...A Better Java...
DSLs 2010 - 31
©ASERT2006-2010
class Erase {
private List removeLongerThan(List strings, int length) {
List result = new ArrayList()
for (String s in strings) {
if (s.length() <= length) {
result.add(s)
}
}
return result
}
public static void main(String[] args) {
List names = new ArrayList()
names.add("Ted"); names.add("Fred")
names.add("Jed"); names.add("Ned")
System.out.println(names)
Erase e = new Erase()
List shortNames = e.removeLongerThan(names, 3)
System.out.println(shortNames.size())
for (String s in shortNames) {
System.out.println(s)
}
}
}
...A Better Java...
DSLs 2010 - 32
©ASERT2006-2010
class Erase {
private List removeLongerThan(List strings, int length) {
List result = new ArrayList()
for (String s in strings) {
if (s.length() <= length) {
result.add(s)
}
}
return result
}
public static void main(String[] args) {
List names = new ArrayList()
names.add("Ted"); names.add("Fred")
names.add("Jed"); names.add("Ned")
System.out.println(names)
Erase e = new Erase()
List shortNames = e.removeLongerThan(names, 3)
System.out.println(shortNames.size())
for (String s in shortNames) {
System.out.println(s)
}
}
}
Do we need
the static types?
Must we always
have a main
method and
class definition?
How about
improved
consistency?
...A Better Java...
DSLs 2010 - 33
©ASERT2006-2010
def removeLongerThan(strings, length) {
def result = new ArrayList()
for (s in strings) {
if (s.size() <= length) {
result.add(s)
}
}
return result
}
names = new ArrayList()
names.add("Ted")
names.add("Fred")
names.add("Jed")
names.add("Ned")
System.out.println(names)
shortNames = removeLongerThan(names, 3)
System.out.println(shortNames.size())
for (s in shortNames) {
System.out.println(s)
}
...A Better Java...
DSLs 2010 - 34
©ASERT2006-2010
def removeLongerThan(strings, length) {
def result = new ArrayList()
for (s in strings) {
if (s.size() <= length) {
result.add(s)
}
}
return result
}
names = new ArrayList()
names.add("Ted")
names.add("Fred")
names.add("Jed")
names.add("Ned")
System.out.println(names)
shortNames = removeLongerThan(names, 3)
System.out.println(shortNames.size())
for (s in shortNames) {
System.out.println(s)
}
Shouldn‟t we
have special
notation for lists?
And special
facilities for
list processing?
Is „return‟
needed at end?
...A Better Java...
DSLs 2010 - 35
©ASERT2006-2010
def removeLongerThan(strings, length) {
strings.findAll{ it.size() <= length }
}
names = ["Ted", "Fred", "Jed", "Ned"]
System.out.println(names)
shortNames = removeLongerThan(names, 3)
System.out.println(shortNames.size())
shortNames.each{ System.out.println(s) }
...A Better Java...
DSLs 2010 - 36
©ASERT2006-2010
def removeLongerThan(strings, length) {
strings.findAll{ it.size() <= length }
}
names = ["Ted", "Fred", "Jed", "Ned"]
System.out.println(names)
shortNames = removeLongerThan(names, 3)
System.out.println(shortNames.size())
shortNames.each{ System.out.println(s) }
Is the method
now needed?
Easier ways to
use common
methods?
Are brackets
required here?
...A Better Java...
DSLs 2010 - 37
©ASERT2006-2010
names = ["Ted", "Fred", "Jed", "Ned"]
println names
shortNames = names.findAll{ it.size() <= 3 }
println shortNames.size()
shortNames.each{ println it }
...A Better Java
DSLs 2010 - 38
©ASERT2006-2010
names = ["Ted", "Fred", "Jed", "Ned"]
println names
shortNames = names.findAll{ it.size() <= 3 }
println shortNames.size()
shortNames.each{ println it }
["Ted", "Fred", "Jed", "Ned"]
3
Ted
Jed
Ned
Grapes / Grab
DSLs 2010 - 39
©ASERT2006-2010
// Google Collections example
@Grab('com.google.collections:google-collections:1.0')
import com.google.common.collect.HashBiMap
HashBiMap fruit =
[grape:'purple', lemon:'yellow', lime:'green']
assert fruit.lemon == 'yellow'
assert fruit.inverse().yellow == 'lemon'
Malleable Syntax
DSLs 2010 - 40
©ASERT2006-2010
order to buy 200.shares of GOOG {
limitPrice 500
allOrNone false
at the value of { qty * unitPrice - 100 }
}
take 2.pills of chloroquinine after 6.hours
Groovy 1.8+
def "length of Spock's & his friends' names"() {
expect:
name.size() == length
where:
name | length
"Spock" | 5
"Kirk" | 4
"Scotty" | 6
}
41
Copyright 2010 SpringSource. Copying, publishing or distributing without express written permission is prohibited.
Real-life Groovy DSL examples
• Anti-malaria drug resistance simulation
• Human Resources employee skills
representation
• Insurance policies risk calculation engine
• Loan acceptance rules engine for a
financial platform
• Mathematica-like lingua for nuclear safety
simulations
• Market data feeds evolution scenarios
• and more...
42
Copyright 2010 SpringSource. Copying, publishing or distributing without express written permission is prohibited.
DSLs within the Groovy Ecosystem
• Testing DSLs
– EasyB, Spock, JBehave, …
• Builders
– MarkupBuilder, SwingBuilder
• Build Tool DSLs
– AntBuilder, Gant, Gradle
• Grails
– BeanBuilder, Criteria, ConfigSlurper
• GPars
– DSL for concurrent activities
• and more...
Testing DSLs: Spock...
©ASERT2006-2010
DSLs 2010 - 43
...Testing DSLs: Spock...
©ASERT2006-2010
• Testing framework for Java and Groovy
• Highly expressive specification language
– No assertion API
– No record &
replay
mocking API
– No
superfluous
annotations
– Meaningful
assert error
messages
– Extensible
– Compatible
with JUnit
reportingwise
@Speck
@RunWith(Sputnik)
class PublisherSubscriberSpeck {
def "events are received by all subscribers"() {
def pub = new Publisher()
def sub1 = Mock(Subscriber)
def sub2 = Mock(Subscriber)
pub.subscribers << sub1 << sub2
when:
pub.send("event")
then:
1 * sub1.receive("event")
1 * sub2.receive("event")
}
}
DSLs 2010 - 44
...Testing DSLs: Spock...
©ASERT2006-2010
import com.gargoylesoftware.htmlunit.WebClient
import spock.lang.*
import org.junit.runner.RunWith
@Speck ()
@RunWith (Sputnik)
class TestSimpBlogSpock {
def page, subheadings, para, form, result
@Unroll("When #author posts a #category blog with content '#content' it sho
def "when creating a new blog entry"() {
given:
page = new WebClient().getPage('http://localhost:8080/postForm')
form = page.getFormByName('post')
when:
form.getInputByName('title').setValueAttribute("$author was here (and s
form.getSelectByName('category').getOptions().find { it.text == categor
form.getSelectByName('author').getOptions().find { it.text == author }.
form.getTextAreaByName('content').setText(content)
result = form.getInputByName('btnPost').click()
subheadings = result.getElementsByTagName('h3')
para = result.getByXPath('//TABLE//TR/TD/P')[0]
...
DSLs 2010 - 45
... Testing DSLs: Spock
©ASERT2006-2010
...
then:
page.titleText == 'Welcome to SimpBlog'
result.getElementsByTagName('h1').item(0).textContent.matches("Post.*: $au
subheadings.item(1).textContent == "Category: $category"
subheadings.item(2).textContent == "Author: $author"
and:
para.textContent == content
where:
author << ['Bart', 'Homer', 'Lisa']
category << ['Home', 'Work', 'Food']
content << ['foo', 'bar', 'baz']
}
}
// Optional use of 'and:'
DSLs 2010 - 46
©ASERT2006-2010
Testing DSLs: EasyB…
• Description: BDD, Rspec-like testing library
narrative 'segment flown', {
as_a 'frequent flyer'
i_want 'to accrue rewards points for every segment I fly'
so_that 'I can receive free flights for my dedication to the airline'
}
scenario 'segment flown', {
given 'a frequent flyer with a rewards balance of 1500 points'
when 'that flyer completes a segment worth 500 points'
then 'that flyer has a new rewards balance of 2000 points'
}
scenario 'segment flown', {
given 'a frequent flyer with a rewards balance of 1500 points', {
flyer = new FrequentFlyer(1500)
}
when 'that flyer completes a segment worth 500 points', {
flyer.fly(new Segment(500))
}
then 'that flyer has a new rewards balance of 2000 points', {
flyer.pointsBalance.shouldBe 2000
}
}
DSLs 2010 - 47
…Testing DSLs: EasyB
• When run will be marked as pending
– perfect for ATDD
©ASERT2006-2010
scenario "Bart posts a new blog entry", {
given "we are on the create blog entry page"
when "I have entered 'Bart was here' as the title"
and "I have entered 'Cowabunga Dude!' into the content"
and "I have selected 'Home' as the category"
and "I have selected 'Bart' as the author"
and "I click the 'Create Post' button"
then "I expect the entry to be posted"
}
DSLs 2010 - 48
Testing DSLS: Cucumber...
©ASERT2006-2010
# language: en
@newpost
Feature: New Blog Post
In order to create a new blog entry
Bloggers should be able to select their name and category and enter text
Scenario: New Posting
Given we are on the create blog entry page
When I have entered "Bart was here" as the title
And I have entered "Cowabunga Dude!" as the content
And I have selected "Home" from the "category" dropdown
And I have selected "Bart" from the "author" dropdown
And I click the 'Create Post' button
Then I should see a heading message matching "Post.*: Bart was here.*"
DSLs 2010 - 49
... Testing DSLS: Cucumber...
©ASERT2006-2010
DSLs 2010 - 50
...Testing DSLS: Cucumber
©ASERT2006-2010
import com.gargoylesoftware.htmlunit.WebClient
this.metaClass.mixin(cuke4duke.GroovyDsl)
Given ~/we are on the create blog entry page/, { ->
page = new WebClient().getPage('http://localhost:8080/postForm')
}
When(~/I have entered "(.*)" as the title/) {String title ->
form = page.getFormByName('post')
form.getInputByName('title').setValueAttribute(title + ' (and so was Cucumber)')
}
When(~'I have entered "(.*)" as the content') {String content ->
form.getTextAreaByName('content').setText(content)
}
When(~'I have selected "(.*)" from the "(.*)" dropdown') {String option, String name ->
form.getSelectByName(name).getOptions().find {
it.text == option }.setSelected(true)
}
When(~"I click the 'Create Post' button") { ->
result = form.getInputByName('btnPost').click()
}
Then(~'I should see a heading message matching "(.*)"') {String pattern ->
// ensureThat result.getElementsByTagName('h1').item(0).textContent.matches(pattern)
assert result.getElementsByTagName('h1').item(0).textContent.matches(pattern)
}
DSLs 2010 - 51
Grails Criteria
DSLs 2010 - 52
©ASERT2006-2010
// Account is a POJO in our domain/model
def c = Account.createCriteria()
def results = c {
like("holderFirstName", "Fred%")
and {
between("balance", 500, 1000)
eq("branch", "London")
}
maxResults(10)
order("holderLastName", "desc")
}
// source: Grails doco: 5. Object Relational Mapping (GORM): 5.4.2 Criteria
Grails Criteria Example
DSLs 2010 - 53
©ASERT2006-2010
// Book is a POJO in our domain/model
def book = Book.findByTitle("The Stand")
book = Book.findByTitleLike("Harry Pot%")
book = Book.findByReleaseDateBetween( firstDate, secondDate )
book = Book.findByReleaseDateGreaterThan( someDate )
book = Book.findByTitleLikeOrReleaseDateLessThan(
"%Something%", someDate )
books = Book.findAllByTitleLikeAndReleaseDateGreaterThan(
"%Java%", new Date()-30)
// source: Grails doco: 5. Object Relational Mapping (GORM): 5.4.1 Dynamic Finders
Grails Bean Builder Example
DSLs 2010 - 54
©ASERT2006-2010
bb.beans {
marge(Person) {
name = "marge"
husband = { Person p ->
name = "homer"
age = 45
props = [overweight:true, height:"1.8m"]
}
children = [bart, lisa]
}
bart(Person) {
name = "Bart"
age = 11
}
lisa(Person) {
name = "Lisa"
age = 9
}
}
// source: 14. Grails and Spring: 14.3 Runtime Spring with the Beans DSL
GPars...
import static org.gparallelizer.actors.pooledActors.PooledActors.*
// create a new actor that prints out received messages
def console = actor {
loop {
react {message ->
println message
}
}
}
// start the actor and send it a message
console.start()
console.send('Message')
DSLs 2010 - 55
...GPars
import static org.gparallelizer.dataflow.DataFlow.thread
final def x = new DataFlowVariable()
final def y = new DataFlowVariable()
final def z = new DataFlowVariable()
thread {
z << ~x + ~y
println "Result: ${~z}"
}
thread {
x << 10
}
thread {
y << 5
}
No race-conditions
No deadlocks
No live-locks
Completely deterministic programs
BEAUTIFUL code
DSLs 2010 - 56
Topics
• Introduction
Groovy DSL Features
• A Week In The Life Of DSL INC
• Summary
• More Info
DSLs 2010 - 57
©ASERT2006-2010
Groovy DSL Features
DSLs 2010 - 58
©ASERT2006-2010
Import / Import Static
• Imports
• Static Imports
DSLs 2010 - 59
©ASERT2006-2010
@Grab('com.google.collections:google-collections:1.0')
import com.google.common.collect.HashBiMap as HashMap
def m = new HashMap()
m.key = 'value'
assert m.inverse().value == 'key'
import static java.util.Calendar.getInstance as now
println now().format('yyyy/MMM/dd')
What Java gives us plus aliases
Static Imports...
DSLs 2010 - 60
©ASERT2006-2010
import groovy.swing.SwingXBuilder
import static java.awt.Color.*
import static java.lang.Math.*
def swing = new SwingXBuilder()
def frame = swing.frame(size: [300, 300]) {
graph(plots: [
[GREEN, {value -> sin(value)}],
[BLUE, {value -> cos(value)}],
[RED, {value -> tan(value)}]
])
}.show()
 Java Static Imports
Discussed later:
 Builders
 Closures
...Static Imports...
DSLs 2010 - 61
©ASERT2006-2010
import groovy.swing.SwingXBuilder
import static java.awt.Color.*
import static java.lang.Math.*
def swing = new SwingXBuilder()
def frame = swing.frame(size: [300, 300]) {
graph(plots: [
[GREEN, {value -> sin(value)}],
[BLUE, {value -> cos(value)}],
[RED, {value -> tan(value)}]
])
}.show()
Java gives us this
...Static Imports...
DSLs 2010 - 62
©ASERT2006-2010
import groovy.swing.SwingXBuilder
import static java.awt.Color.*
import static java.lang.Math.*
def swing = new SwingXBuilder()
def frame = swing.frame(size: [300, 300]) {
graph(plots: [
[GREEN, {value -> sin(value)}],
[BLUE, {value -> cos(value)}],
[RED, {value -> tan(value)}]
])
}.show()
Java gives us this
...Static Imports...
DSLs 2010 - 63
©ASERT2006-2010
Source: http://www.thedoghousediaries.com/?p=1406
...Static Imports
DSLs 2010 - 64
©ASERT2006-2010
import groovy.swing.SwingXBuilder
import static java.lang.Math.*
import static java.awt.Color.GREEN as Lime
import static java.awt.Color.BLUE as Sky
import static java.awt.Color.RED as Maraschino
def swing = new SwingXBuilder()
def frame = swing.frame(size: [300, 300]) {
graph(plots: [
[Lime, {value -> sin(value)}],
[Sky, {value -> cos(value)}],
[Maraschino, {value -> tan(value)}]
])
}.show()
Java doesn‟t give us this!
Literal Syntax Conventions
• Lists
– Special syntax for list literals
– Additional common methods (operator overloading)
• Maps
– Special syntax for map literals
– Additional common methods
• Ranges
– Special syntax for various kinds of ranges
DSLs 2010 - 65
©ASERT2006-2010
def list = [3, new Date(), 'Jan']
assert list + list == list * 2
def map = [a: 1, b: 2]
assert map['a'] == 1 && map.b == 2
def letters = 'a'..'z'
def numbers = 0..<10
Literal Syntax Conventions in DSLs
DSLs 2010 - 66
©ASERT2006-2010
import static java.util.Calendar.getInstance as getNow
import static java.util.Calendar.*
def discount = [normal:0, silver:5, gold:10]
def roomrate = [weekday:150, weekend:95]
for (level in ['normal', 'silver', 'gold']) {
def multiplier = 1 - discount[level] / 100
print 'Your room rate is: '
if (now[DAY_OF_WEEK] in MONDAY..FRIDAY)
println roomrate.weekday * multiplier
else
println roomrate.weekend * multiplier
}
Your room rate is: 150
Your room rate is: 142.50
Your room rate is: 135.0
 Literal list & map syntax
 Ranges
Also
 Static import aliases
 BigDecimal arithmetic
Compact Syntax...
• Java
• Groovy
DSLs 2010 - 67
©ASERT2006-2010
public class BuySharesJava {
public static void buyShares(int qty, String name) {
// business logic here ...
System.out.println("buying " + qty
+ " shares of " + name);
}
public static void main(String[] args) {
buyShares(3, "BHP");
}
}
def buyShares(qty, name) {
// business logic here ...
println "buying $qty shares of $name"
}
buyShares 3, 'BHP'
 Script syntax
 Conventions for visibility
 GDK methods: println
 Implicit typing
 GString interpolation
 Optional brackets & „;‟
...Compact Syntax...
• Java
• Groovy
DSLs 2010 - 68
©ASERT2006-2010
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class ProcessCustomerJava {
public static void printDetails(Map<String, String> cust) {
System.out.println("Details as at: " + new Date());
String first = cust.get("first");
System.out.println("First name: " + first);
String last = cust.get("last");
System.out.println("Last name: " + (last != null ? last : "unknown"));
}
public static void main(String[] args) {
Map<String, String> details = new HashMap<String, String>();
details.put("first", "John");
details.put("last", "Smith");
printDetails(details);
}
} def printDetails(cust) {
println """Details as at: ${new Date()}
First name: $cust.first
Last name: ${cust.last ?: 'unknown'}"""
}
printDetails first: 'John', last: 'Smith'
 Script syntax, Named params, Extra imports
 Conventions for visibility, GDK methods
 Implicit typing, Multi-line GString
 Elvis operator, Optional brackets & „;‟
...Compact Syntax
• Java
• Groovy
DSLs 2010 - 69
©ASERT2006-2010
import java.util.Date;
public class ProcessStrongCustomerJava {
public static void printDetails(CustomerJ cust) {
System.out.println("Details as at: " + new Date());
String first = cust.getFirst();
System.out.println("First name: " + first);
String last = cust.getLast();
System.out.println("Last name: " + (last != null ? last : "unknown"));
}
public static void main(String[] args) {
CustomerJ c = new CustomerJ();
c.setFirst("John");
c.setLast("Smith");
printDetails(c);
}
}
class CustomerJ {
private String first;
private String last;
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
public String getLast() {
return last;
}
public void setLast(String last) {
this.last = last;
}
}
class CustomerG { String first, last }
def printDetails(cust) {
println """Details as at: ${new Date()}
First name: $cust.first
Last name: ${cust.last ?: 'unknown'}"""
}
printDetails new CustomerG(first: 'John',
last: 'Smith')
Plus:
 Named params for constructors
 JavaBean conventions
 Leverage Duck Typing
Using With Example
DSLs 2010 - 70
©ASERT2006-2010
letters = ['a', 'b', 'c']
range = 'b'..'d'
letters.with {
add 'd'
remove 'a'
}
assert letters == range
map = [a:10, b:4, c:7]
map.with {
assert (a + b) / c == 2
}
Just normal methods
for ArrayList here
Closures...
• Traditional mainstream languages
– Data can be stored in variables, passed around,
combined in structured ways to form more complex
data; code stays put where it is defined
• Languages supporting closures
– Data and code can be stored in variables, passed
around, combined in structured ways to form more
complex algorithms and data; functional coding style
DSLs 2010 - 71
©ASERT2006-2010
doubleNum = { num -> num * 2 }
println doubleNum(3) // => 6
processThenPrint = { num, closure ->
num = closure(num); println "num is $num"
}
processThenPrint(3, doubleNum) // => num is 6
processThenPrint(10) { it / 2 } // => num is 5
...Closures...
DSLs 2010 - 72
©ASERT2006-2010
int myConst = 4
def multiplier = { number -> number * myConst }
assert multiplier(10) == 40
Closure other = { it + myConst }
assert other.call(10) == 14
def twice = { it * 2 } // anonymous functions
assert twice(10) == 20 // implemented as Closures
Name
...Closures...
DSLs 2010 - 73
©ASERT2006-2010
int myConst = 4
def multiplier = { number -> number * myConst }
assert multiplier(10) == 40
Closure other = { it + myConst }
assert other.call(10) == 14
def twice = { it * 2 } // anonymous functions
assert twice(10) == 20 // implemented as Closures
Code
...Closures...
DSLs 2010 - 74
©ASERT2006-2010
int myConst = 4
def multiplier = { number -> number * myConst }
assert multiplier(10) == 40
Closure other = { it + myConst }
assert other.call(10) == 14
def twice = { it * 2 } // anonymous functions
assert twice(10) == 20 // implemented as Closures
Parameter(s)
Default parameter
...Closures...
DSLs 2010 - 75
©ASERT2006-2010
int myConst = 4
def multiplier = { number -> number * myConst }
assert multiplier(10) == 40
Closure other = { it + myConst }
assert other.call(10) == 14
def twice = { it * 2 } // anonymous functions
assert twice(10) == 20 // implemented as Closures
Call closure
Alternative syntax
...Closures...
DSLs 2010 - 76
©ASERT2006-2010
int myConst = 4
def multiplier = { number -> number * myConst }
assert multiplier(10) == 40
Closure other = { it + myConst }
assert other.call(10) == 14
def twice = { it * 2 } // anonymous functions
assert twice(10) == 20 // implemented as Closures
Free variable
...Closures...
DSLs 2010 - 77
©ASERT2006-2010
int myConst = 4
def multiplier = { number -> number * myConst }
assert multiplier(10) == 40
Closure other = { it + myConst }
assert other.call(10) == 14
def twice = { it * 2 } // anonymous functions
assert twice(10) == 20 // implemented as Closures
Bound to environment/context when called
Closures in DSLs...
DSLs 2010 - 78
©ASERT2006-2010
import static java.util.Calendar.*
def cart1 = [
[qty: 3, unitPrice: 50.0, gift: true,
name: 'Groovy in Action Book'],
[qty: 1, unitPrice: 40.0, gift: false,
name: 'Grails in Action eBook'],
[qty: 2, unitPrice: 25.0, gift: true,
name: 'Avatar DVD']
]
def cart2 = [
[qty: 1, unitPrice: 50.0, gift: true,
name: 'Groovy in Action Book'],
[qty: 2, unitPrice: 30.0, gift: false,
name: 'Avatar 3D DVD']
]
// ...
Nothing special here.
Just a list of maps.
... Closures in DSLs...
DSLs 2010 - 79
©ASERT2006-2010
// ...
def noDiscount = { it.unitPrice * it.qty }
def tenPercentOffAll = { it.unitPrice * it.qty * 0.9 }
def tenPercentOffGifts = { it.with {
unitPrice * qty * (gift ? 0.9 : 1.0)
} }
def tenPercentOffOverForty = { it.with {
unitPrice * qty * (unitPrice > 40 ? 0.9 : 1.0)
} }
def tenPercentOffBooks = { it.with {
unitPrice * qty * (name.contains('Book') ? 0.9 : 1.0)
} }
def tenPercentOffDVDs = { it.with {
unitPrice * qty * (name.contains('DVD') ? 0.9 : 1.0)
} }
def buyThreeGetOneFree = { it.with {
unitPrice * (qty % 3 + 2 * qty.intdiv(3))
} }
// ...
Closure Name
... Closures in DSLs...
DSLs 2010 - 80
©ASERT2006-2010
// ...
def noDiscount = { it.unitPrice * it.qty }
def tenPercentOffAll = { it.unitPrice * it.qty * 0.9 }
def tenPercentOffGifts = { it.with {
unitPrice * qty * (gift ? 0.9 : 1.0)
} }
def tenPercentOffOverForty = { it.with {
unitPrice * qty * (unitPrice > 40 ? 0.9 : 1.0)
} }
def tenPercentOffBooks = { it.with {
unitPrice * qty * (name.contains('Book') ? 0.9 : 1.0)
} }
def tenPercentOffDVDs = { it.with {
unitPrice * qty * (name.contains('DVD') ? 0.9 : 1.0)
} }
def buyThreeGetOneFree = { it.with {
unitPrice * (qty % 3 + 2 * qty.intdiv(3))
} }
// ...
Closure Code
... Closures in DSLs
DSLs 2010 - 81
©ASERT2006-2010
// ...
def specials = [noDiscount, tenPercentOffAll,
tenPercentOffGifts, tenPercentOffOverForty,
tenPercentOffBooks, tenPercentOffDVDs,
buyThreeGetOneFree]
[
SUNDAY..SATURDAY,
[cart1, cart2]
].combinations().each { day, cart ->
printf "%d %2.2fn", day,
cart.sum(specials[day-1])
}
1 240.00
2 216.00
3 220.00
4 225.00
5 221.00
6 235.00
7 190.00
1 110.00
2 99.00
3 105.00
4 105.00
5 105.00
6 104.00
7 110.00
Operator Overloading Example
DSLs 2010 - 82
©ASERT2006-2010
BigDecimal a = new BigDecimal(3.5d);
BigDecimal b = new BigDecimal(4.0d);
assert a.multiply(b).compareTo(new BigDecimal(14.0d)) == 0;
assert a.multiply(b).equals(new BigDecimal(14.0d).setScale(1));
def c = 3.5, d = 4.0
assert c * d == 14.0
• Java
• Groovy
Groovy Lab Example
DSLs 2010 - 83
©ASERT2006-2010
// require GroovyLab
import static org.math.array.Matrix.*
import static org.math.plot.Plot.*
def A = rand(10,3) // random Matrix of 10 rows and 3 columns
def B = fill(10,3,1.0) // one Matrix of 10 rows and 3 columns
def C = A + B // support for matrix addition with "+" or "-"
def D = A - 2.0 // support for number addition with "+" or "-"
def E = A * B // support for matrix multiplication or division
def F = rand(3,3)
def G = F**(-1) // support for matrix power (with integers only)
println A // display Matrix content
plot("A",A,"SCATTER") // plot Matrix values as ScatterPlot
def M = rand(5,5) + id(5) // Eigenvalues decomposition
println "M=n" + M
println "V=n" + V(M)
println "D=n" + D(M)
println "M~n" + (V(M) * D(M) * V(M)**(-1))
©ASERT2006-2010
Better Control Structures: Switch Poker…
suits = 'SHDC'
ranks = '23456789TJQKA'
suit = { String card -> suits.indexOf(card[1]) }
rank = { String card -> ranks.indexOf(card[0]) }
rankSizes = { List cards ->
cards.groupBy(rank).collect{ k, v -> v.size() }.sort() }
rankValues = { List cards ->
cards.collect{ rank(it) }.sort() }
// ...
8C TS KC 9H 4S 7D 2S 5D 3S AC
hand1 hand2
println rankSizes(["7S", "7H", "2H", "7D", "AH"])
// => [1, 1, 3]
DSLs 2010 - 84
©ASERT2006-2010
…Better Control Structures: Switch Poker…
// ...
flush = { List cards -> cards.groupBy(suit).size() == 1 }
straight = { def v = rankValues(it); v == v[0]..v[0]+4 }
straightFlush = { List cards -> straight(cards) && flush(cards)
}
fourOfAKind = { List cards -> rankSizes(cards) == [1, 4] }
fullHouse = { List cards -> rankSizes(cards) == [2, 3] }
threeOfAKind = { List cards -> rankSizes(cards) == [1, 1, 3] }
twoPair = { List cards -> rankSizes(cards) == [1, 2, 2] }
pair = { List cards -> rankSizes(cards) == [1, 1, 1, 2] }
// ...
DSLs 2010 - 85
©ASERT2006-2010
… Better Control Structures: Switch Poker
// ...
def rankHand(List cards) {
switch (cards) {
case straightFlush : return 9
case fourOfAKind : return 8
case fullHouse : return 7
case flush : return 6
case straight : return 5
case threeOfAKind : return 4
case twoPair : return 3
case pair : return 2
default : return 1
}
}
// ...
DSLs 2010 - 86
Type Transformation Example
DSLs 2010 - 87
©ASERT2006-2010
class InventoryItem {
def weight, name
InventoryItem(Map m) {
this.weight = m.weight; this.name = m.name
}
InventoryItem(weight, name) {
this.weight = weight; this.name = name
}
InventoryItem(String s) {
s.find(/weight=(d*)/) { all, w -> this.weight = w }
s.find(/name=(.*)/) { all, n -> this.name = n }
}
}
def room = [:]
def gold = [weight:50, name:'Gold'] as InventoryItem
def emerald = [10, 'Emerald'] as InventoryItem
def dagger = ['weight=5, name=Dagger'] as InventoryItem
room.contents = [gold, emerald, dagger]
room.contents.each{ println it.dump() }
DSLs 2010 - 88
Groovy DSL Features
©ASERT2006-2010
Metaprogramming
• Runtime Metaprogramming
– Categories including DGM
– invokeMethod, methodMissing, getProperty
– GroovyInterceptable
– ExpandoMetaClass
DSLs 2010 - 89
©ASERT2006-2010
DSLs 2010 - 90
©ASERT2006-2010
Builders…
<html>
<head>
<title>Hello</title>
</head>
<body>
<ul>
<li>world 1</li>
<li>world 2</li>
<li>world 3</li>
<li>world 4</li>
<li>world 5</li>
</ul>
</body>
</html>
import groovy.xml.*
def page = new MarkupBuilder()
page.html {
head { title 'Hello' }
body {
ul {
for (count in 1..5) {
li "world $count"
} } } }
• MarkupBuilder
...Builders…
DSLs 2010 - 91
©ASERT2006-2010
def colors = ['red','darkOrange','blue','darkGreen']
(0..3).each { index ->
star( cx: 50 + (index*110), cy: 50, or: 40, ir: 15,
borderColor: 'black', count: 2+index, fill: colors[index] )
star( cx: 50 + (index*110), cy: 140, or: 40, ir: 15,
borderColor: 'black‘, count: 7+index, fill: colors[index] )
}
• GraphicsBuilder
…Builders
new AntBuilder().with {
echo(file:'Temp.java', '''
class Temp {
public static void main(String[] args) {
System.out.println("Hello");
}
}
''')
javac(srcdir:'.', includes:'Temp.java', fork:'true')
java(classpath:'.', classname:'Temp', fork:'true')
echo('Done')
}
// =>
// [javac] Compiling 1 source file
// [java] Hello
// [echo] Done
©ASERT2006-2010
DSLs 2010 - 92
93
Copyright 2010 SpringSource. Copying, publishing or distributing without express written permission is prohibited.
A builder for HR
DSLs 2010 - 94
©ASERT2006-2010
ExpandoMetaClass…
import static java.lang.Character.isUpperCase
String.metaClass.swapCase = {
delegate.collect { ch ->
isUpperCase(ch as char) ?
ch.toLowerCase() :
ch.toUpperCase()
}.join()
}
println new Date().toString().swapCase()
// => sUN nOV 09 17:30:06 est 2008
List.metaClass.sizeDoubled = {-> delegate.size() * 2 }
LinkedList list = []
list << 1
list << 2
assert 4 == list.sizeDoubled()
DSLs 2010 - 95
©ASERT2006-2010
…ExpandoMetaClass
class Person {
String name
}
class MortgageLender {
def borrowMoney() {
"buy house"
}
}
def lender = new MortgageLender()
Person.metaClass.buyHouse = lender.&borrowMoney
def p = new Person()
assert "buy house" == p.buyHouse()
Adding your own control structures
• Thread enhancement
DSLs 2010 - 96
ROCK!
. 25
. 33
. 34
. 35
. 36
.. 131
.. 134
.. 137
.. 138
.. 139
... 232
... 234
... 237
... 238
... 239
.... 334
.... 336
.... 337
.... 338
.... 339
import java.util.concurrent.locks.ReentrantLock
import static System.currentTimeMillis as now
def startTime = now()
ReentrantLock.metaClass.withLock = { critical ->
lock()
try { critical() }
finally { unlock() }
}
def lock = new ReentrantLock()
def worker = { threadNum ->
4.times { count ->
lock.withLock {
print " " * threadNum
print "." * (count + 1)
println " ${now() - startTime}"
}
Thread.sleep 100
}
}
5.times { Thread.start worker.curry(it) }
println "ROCK!"
Source: http://chrisbroadfoot.id.au/articles/2008/08/06/groovy-threads
EMC DSL…
DSLs 2010 - 97
©ASERT2006-2010
...EMC Neo4j DSL…
DSLs 2010 - 98
©ASERT2006-2010
@GrabResolver(name= 'neo4j-public-repo', root= 'http://m2.neo4j.org')
@Grab('org.neo4j:neo4j-kernel:1.1.1')
import org.neo4j.kernel.EmbeddedGraphDatabase
import org.neo4j.graphdb.*
// an enum helper
enum MyRelationships implements RelationshipType { knows }
// some optional syntactic sugar using EMC DSL
Node.metaClass {
propertyMissing { String name, val ->
delegate.setProperty(name, val) }
propertyMissing { String name -> delegate.getProperty(name) }
methodMissing { String name, args ->
delegate.createRelationshipTo(args[0], MyRelationships."$name") }
}
Relationship.metaClass {
propertyMissing { String name, val ->
delegate.setProperty(name, val) }
propertyMissing { String name -> delegate.getProperty(name) }
}
// ...
…EMC Neo4j DSL
DSLs 2010 - 99
©ASERT2006-2010
// ...
def graphDb = new EmbeddedGraphDatabase("graphdb")
def tx = graphDb.beginTx()
def firstNode, secondNode, relationship
try {
firstNode = graphDb.createNode()
secondNode = graphDb.createNode()
relationship = firstNode.knows(secondNode)
firstNode.message = "Hello,"
secondNode.message = "world!"
relationship.message = "brave Neo4j"
tx.success()
} finally {
tx.finish()
println "$firstNode.message $relationship.message $secondNode.message"
// => Hello, brave Neo4j world!
graphDb.shutdown()
}
Game example...
DSLs 2010 - 100
©ASERT2006-2010
// Trying out the game DSL idea by Sten Anderson from:
// http://blogs.citytechinc.com/sanderson/?p=92
class GameUtils {
static VOWELS = ['a', 'e', 'i', 'o', 'u']
static listItems(things) {
def result = ''
things.eachWithIndex{ thing, index ->
if (index > 0) {
if (index == things.size() - 1) result += ' and '
else if (index < things.size() - 1) result += ', '
}
result += "${thing.toLowerCase()[0] in VOWELS ? 'an' : 'a'} $thing
}
result ?: 'nothing'
}
}
import static GameUtils.*
...
...Game example...
DSLs 2010 - 101
©ASERT2006-2010
...
class Room {
def description
def contents = []
}
...
...Game example...
DSLs 2010 - 102
©ASERT2006-2010
...
class Player {
def currentRoom
def inventory = []
void look() {
println "You are in ${currentRoom?.description?:'the void'} 
which contains ${listItems(currentRoom?.contents)}"
}
void inv() {
println "You are holding ${listItems(inventory)}"
}
void take(item) {
if (currentRoom?.contents?.remove(item)) {
inventory << item
println "You took the $item"
} else {
println "I see no $item here"
}
}
...
...Game example...
DSLs 2010 - 103
©ASERT2006-2010
...
void drop(item) {
if (inventory?.remove(item)) {
currentRoom?.contents << item
println "You dropped the $item"
} else {
println "You don't have the $item"
}
}
def propertyMissing(String name) {
if (metaClass.respondsTo(this, name)) {
this."$name"()
}
name
}
}
...
...Game example...
DSLs 2010 - 104
©ASERT2006-2010
...
Room plainRoom = new Room(description:'a plain white room',
contents:['dagger', 'emerald', 'key'])
Player player = new Player(currentRoom:plainRoom)
player.with {
inv
look
take dagger
inv
look
take emerald
inv
look
take key
drop emerald
inv
look
}
assert player.inventory == ['dagger', 'key']
...
...Game example
DSLs 2010 - 105
©ASERT2006-2010
...
// now try some error conditions
plainRoom.description = null
player.with {
drop gold
take gold
drop emerald
take emerald
take emerald
look
}
GEP3 example...
DSLs 2010 - 106
©ASERT2006-2010
Object.metaClass.please =
{ clos -> clos(delegate) }
Object.metaClass.the =
{ clos -> delegate[1](clos(delegate[0])) }
show = { thing -> [thing, { println it }] }
square_root = { Math.sqrt(it) }
given = { it }
given 100 please show the square_root
// ==> 10.0
...GEP3 example...
DSLs 2010 - 107
©ASERT2006-2010
Object.metaClass.of =
{ delegate[0](delegate[1](it)) }
Object.metaClass.the =
{ clos -> [delegate[0], clos] }
show = [{ println it }]
square_root = { Math.sqrt(it) }
please = { it }
please show the square_root of 100
// ==> 10.0
...GEP3 example...
DSLs 2010 - 108
©ASERT2006-2010
show = { println it }
square_root = { Math.sqrt(it) }
def please(action) {
[the: { what ->
[of: { n -> action(what(n)) }]
}]
}
please show the square_root of 100
// ==> 10.0
Inspiration for this example came from …
...GEP3 example...
DSLs 2010 - 109
©ASERT2006-2010
// Japanese DSL using GEP3 rules
Object.metaClass.を =
Object.metaClass.の =
{ clos -> clos(delegate) }
まず = { it }
表示する = { println it }
平方根 = { Math.sqrt(it) }
まず 100 の 平方根 を 表示する
// First, show the square root of 100
// => 10.0
// source: http://d.hatena.ne.jp/uehaj/20100919/1284906117
// http://groovyconsole.appspot.com/edit/241001
...GEP3 example
DSLs 2010 - 110
©ASERT2006-2010
// source: http://d.hatena.ne.jp/uehaj/20100919/1284906117
// http://groovyconsole.appspot.com/edit/241001
Medical Prescription DSL…
DSLs 2010 - 111
©ASERT2006-2010
class Drug {
String name
String toString() { name }
}
class Measure {
Number number
String unit, units
String toString() { number == 1 ? "1 $unit" : "$number $units" }
}
class Quantity extends Measure {}
class Duration extends Measure {}
// ...
http://groovyconsole.appspot.com/script/240001 by glaforge/Mihai Cazacu
…Medical Prescription DSL…
DSLs 2010 - 112
©ASERT2006-2010
// ...
// ---- DSL implementation ------------------------------
Integer.metaClass.getHour = {-> 1.hours }
Integer.metaClass.getPill = {-> 1.pills }
Integer.metaClass.getHours = {-> new Duration(number: delegate,
unit: 'hour', units: 'hours') }
Integer.metaClass.getPills = {-> new Quantity(number: delegate,
unit: 'pill', units: 'pills') }
// use the script binding for storing new drugs available as variables
binding = new Binding() {
def getVariable(String drug) {
new Drug(name: drug)
}
}
take = {Quantity quantity ->
['of': {Drug drug ->
['after': {Duration duration ->
println "Take $quantity of $drug afer $duration"
}]
}]
}
// ...
…Medical Prescription DSL
DSLs 2010 - 113
©ASERT2006-2010
// ...
// ---- DSL -------------------------------
take 2.pills of chloroquinine after 6.hours
http://groovyconsole.appspot.com/script/240001 by glaforge/Mihai Cazacu
Groovy DSL Features
DSLs 2010 - 114
©ASERT2006-2010
Transformation
AST Transformations
• With metaprogramming, Groovy‟s able to
modify the behaviour of programs... at
runtime
• AST Transformations provide a compile-
time approach to modify programs
– AST: Abstract Syntax Tree
– Ability to change what‟s being compiled at compile-
time!
• No runtime impact!
• Lets you change the semantics of your programs!
• Nice way of implementing patterns and removing boiler-plate
technical code
• Two kinds of transformations: global & local
DSLs 2010 - 115
Better Design Patterns: Immutable...
• Java Immutable Class
– As per Joshua Bloch
Effective Java
DSLs 2010 - 116
©ASERT2006-2010
public final class Punter {
private final String first;
private final String last;
public String getFirst() {
return first;
}
public String getLast() {
return last;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((first == null)
? 0 : first.hashCode());
result = prime * result + ((last == null)
? 0 : last.hashCode());
return result;
}
public Punter(String first, String last) {
this.first = first;
this.last = last;
}
// ...
// ...
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Punter other = (Punter) obj;
if (first == null) {
if (other.first != null)
return false;
} else if (!first.equals(other.first))
return false;
if (last == null) {
if (other.last != null)
return false;
} else if (!last.equals(other.last))
return false;
return true;
}
@Override
public String toString() {
return "Punter(first:" + first
+ ", last:" + last + ")";
}
}
...Better Design Patterns: Immutable...
• Java Immutable Class
– As per Joshua Bloch
Effective Java
DSLs 2010 - 117
©ASERT2006-2010
public final class Punter {
private final String first;
private final String last;
public String getFirst() {
return first;
}
public String getLast() {
return last;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((first == null)
? 0 : first.hashCode());
result = prime * result + ((last == null)
? 0 : last.hashCode());
return result;
}
public Punter(String first, String last) {
this.first = first;
this.last = last;
}
// ...
// ...
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Punter other = (Punter) obj;
if (first == null) {
if (other.first != null)
return false;
} else if (!first.equals(other.first))
return false;
if (last == null) {
if (other.last != null)
return false;
} else if (!last.equals(other.last))
return false;
return true;
}
@Override
public String toString() {
return "Punter(first:" + first
+ ", last:" + last + ")";
}
}
boilerplate
...Better Design Patterns: Immutable
DSLs 2010 - 118
©ASERT2006-2010
@Immutable class Punter {
String first, last
}
Spock
DSLs 2010 - 119
©ASERT2006-2010
def "length of Spock's & his friends' names"() {
expect:
name.size() == length
where:
name | length
"Spock" | 5
"Kirk" | 4
"Scotty" | 6
}
AST Builder
• Numerous approaches, still evolving.
“From code” approach:
• Produces:
def result = new AstBuilder().buildFromCode {
println "Hello World"
}
BlockStatement
-> ReturnStatement
-> MethodCallExpression
-> VariableExpression("this")
-> ConstantExpression("println")
-> ArgumentListExpression
-> ConstantExpression("Hello World")
DSLs 2010 - 120
©ASERT2006-2010
EasyB…
• Description: BDD, Rspec-like testing library
narrative 'segment flown', {
as_a 'frequent flyer'
i_want 'to accrue rewards points for every segment I fly'
so_that 'I can receive free flights for my dedication to the airline'
}
scenario 'segment flown', {
given 'a frequent flyer with a rewards balance of 1500 points'
when 'that flyer completes a segment worth 500 points'
then 'that flyer has a new rewards balance of 2000 points'
}
scenario 'segment flown', {
given 'a frequent flyer with a rewards balance of 1500 points', {
flyer = new FrequentFlyer(1500)
}
when 'that flyer completes a segment worth 500 points', {
flyer.fly(new Segment(500))
}
then 'that flyer has a new rewards balance of 2000 points', {
flyer.pointsBalance.shouldBe 2000
}
}
DSLs 2010 - 121
©ASERT2006-2010
…EasyB
• Description: BDD, Rspec-like testing library
examples "The number #{number}' should be converted to #{romanNumerals}", {
number = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
romanNumerals = ["I", "II", "III", "IV", "V",
"VI", "VII", "VIII", "IX", "X"]
}
scenario "Converting #number into the roman numeral #romanNumerals", {
given "the number #number", {
theNumber = number
}
when "the system converts this number to the roman numeral equivalent", {
theConvertedNumber = RomanNumerals.fromInteger(theNumber)
}
then "the result should be #romanNumerals", {
theConvertedNumber.shouldBe romanNumerals
}
}
DSLs 2010 - 122
Source: http://www.wakaleo.com/blog/285-example-driven-testing-with-easyb
EasyB Preprocessing...
• EasyB supports either of these:
• But we would like this:
given ("some data") {
println '... setting expectations'
}
Source: http://hamletdarcy.blogspot.com/2010/02/groovy-antlr-plugins-for-better-dsls.html
given "some data", {
println '... setting expectations'
}
given "some data" {
println '... setting expectations'
}
DSLs 2010 - 123
But also consider Command Expressions (GEP-3) for Groovy1.8+.
...EasyB Preprocessing...
String addCommas(text) {
def pattern =
~/(.*)(given|when|then) "([^"]*(.[^"]*)*)" {(.*)/
def replacement = /$1$2 "$3", {$4/
(text =~ pattern).replaceAll(replacement)
}
Source: http://hamletdarcy.blogspot.com/2010/02/groovy-antlr-plugins-for-better-dsls.html
DSLs 2010 - 124
Adds the comma in where required before Groovy sees it.
...EasyB Preprocessing
class SourceModifierParserPlugin extends AntlrParserPlugin {
Reduction parseCST(SourceUnit sourceUnit, Reader reader)
throws CompilationFailedException {
def text = addCommas(reader.text)
StringReader stringReader = new StringReader(text)
super.parseCST(sourceUnit, stringReader)
}
}
def parserPluginFactory = new ParserPluginFactory() {
ParserPlugin createParserPlugin() {
new SourceModifierParserPlugin()
}
}
def conf = new CompilerConfiguration(pluginFactory: parserPluginFactory)
def binding = ...
def shell = new GroovyShell(binding, conf)
Source: http://hamletdarcy.blogspot.com/2010/02/groovy-antlr-plugins-for-better-dsls.html
DSLs 2010 - 125
But use with restraint as error messages will be with
respect to converted source code not original.
GParsec...
• Miniature parsers
• Parser Combinators
def isLetter = { ch -> return Character.isLetter(ch) }
def isDigit = { ch -> return Character.isDigit(ch) }
def letter = satisfyC(isLetter)
def digit = satisfyC(isDigit)
def letterAndDigit = seqC(letter, digit)
assert letterAndDigit('a123###') == ['a', '1']
def identifier = seqC(identifierStart,
noneOrMoreC(identifierRest))
DSLs 2010 - 126
...GParsec...
• BNF
• Groovy definitions
variableDeclarator :
identifier ( '=' expression )?
variableDefinitions :
variableDeclarator ( ',' variableDeclarator)*
def variableDeclarator =
seqC(identifier, optionalC(seqC(assign, expression)))
def variableDefinitions =
seqC(variableDeclarator,
noneOrMoreC(seqC(comma, variableDeclarator)))
DSLs 2010 - 127
...GParsec
• satisfyC: combinator consumes a single input when its
predicate succeeds
• altC (alt3C, altCs): is the choice combinator. Given two
parsers it only looks at its second alternative if the first has
not consumed any input - regardless of the final value
• seqC (seq3C, seqCs): is the sequencing combinator. It runs
two parsers in succession and if successful, returns the result
of the two parsers
• noneOrMoreC, oneOrMoreC: applies a parser zero or more
times to an input stream. The result from each application of
the parser are returned in a list
• optionalC: The combinator optionalC may succeed in
parsing some input. It always returns success.
DSLs 2010 - 128
Using External Parsers
DSLs 2010 - 129
©ASERT2006-2010
import static com.mdimension.jchronic.Chronic.parse
def span = parse("tomorrow at 5pm")
println span.endCalendar.format('yyyy-MM-dd HH:mm')
// => 2010-05-19 17:00
Topics
• Introduction
• Groovy DSL Features
A Week In The Life Of DSL INC
• Summary
• More Info
DSLs 2010 - 130
©ASERT2006-2010
MONDAY
DSLs 2010 - 131
©ASERT2006-2010
Currency DSL…
DSLs 2010 - 132
©ASERT2006-2010
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 Customer
…Currency DSL...
DSLs 2010 - 133
©ASERT2006-2010
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 == 57
…Currency DSL…
DSLs 2010 - 134
©ASERT2006-2010
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 Customer
...Currency DSL...
DSLs 2010 - 135
©ASERT2006-2010
class CoinMath {
static multiply(Integer self, Coin c) {
self * c.value
}
}
use (CoinMath) {
assert 2 * quarter +
1 * nickel +
2 * penny == 57
}
// EMC equivalent
Integer.metaClass.multiply = {
Coin c -> delegate * c.value
}
assert 2 * quarter + 1 * nickel + 2 * penny == 57
…Currency DSL…
DSLs 2010 - 136
©ASERT2006-2010
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 Customer
... Currency DSL
DSLs 2010 - 137
©ASERT2006-2010
class 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 == 57
TUESDAY
DSLs 2010 - 138
©ASERT2006-2010
http://www.google.com/imgres?imgurl=http://i733.photobucket.com/albums/ww336/snake1953/Day_of_Week/Tue/HappyTuesda
Business logic DSL…
DSLs 2010 - 139
©ASERT2006-2010
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.
…Business logic DSL…
• Options
– Embedded Groovy
– Regex parser
– Antlr parser
– Split / StringTokenizer
– Parser combinators
• … Many options, details not important …
DSLs 2010 - 140
©ASERT2006-2010
… Business logic DSL …
DSLs 2010 - 141
©ASERT2006-2010
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.
… Business logic DSL …
DSLs 2010 - 142
©ASERT2006-2010
def shell = new GroovyShell()
shell.evaluate('''
price = 3
quantity = 10
total = price * quantity
assert total == 30
''')
… Business logic DSL …
DSLs 2010 - 143
©ASERT2006-2010
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.
… Business logic DSL …
DSLs 2010 - 144
©ASERT2006-2010
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.")
}
// ...
}
Please also see the ArithmeticShell which is included under examples in the Groovy distribution
… Business logic DSL
DSLs 2010 - 145
©ASERT2006-2010
def shell = new CustomerShell()
shell.evaluate('''
price = 3
quantity = 10
total = price * quantity
assert total == 30
''')
WEDNESDAY
DSLs 2010 - 146
©ASERT2006-2010
http://www.crystalscomments.com/1/days/wednesday/animals/006.jpg
Stock Exchange Order DSL…
DSLs 2010 - 147
©ASERT2006-2010
Example inspired from DSLs in Action: http://www.manning.com/ghosh/
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 Customer
… Stock Exchange Order DSL…
DSLs 2010 - 148
©ASERT2006-2010
// ----- Implementation of the Fluent API -----
enum Action { Buy, Sell }
class Order {
def security
def quantity, limitPrice
boolean allOrNone
def valueCalculation
Action action
def buy(Integer quantity, String security) {
this.quantity = quantity
this.security = security
this.action = Action.Buy
return this
}
def sell(Integer quantity, String security) {
this.quantity = quantity
this.security = security
this.action = Action.Sell
return this
}
def limitPrice(Integer limit) {
this.limitPrice = limit; return this
}
def allOrNone(boolean allOrNone) {
this.allOrNone = allOrNone; return this
}
def valueAs(Closure valueCalculation) {
this.valueCalculation = valueCalculation; return this
}
String toString() {
"$action $quantity shares of $security at valuation of ${valueCalculation(quantity, limitPrice)}"
}
}
println new Order()
.sell(150, "IBM")
.limitPrice(300)
.allOrNone(true)
.valueAs{ qty, unitPrice ->
qty * unitPrice - 100 }
println new Order()
.buy(200, "GOOG")
.limitPrice(200)
.allOrNone(true)
.valueAs{ qty, unitPrice ->
qty * unitPrice - 500 }
Sell 150 shares of IBM at valuation of 44900
Buy 200 shares of GOOG at valuation of 39500
…Stock Exchange Order DSL…
DSLs 2010 - 149
©ASERT2006-2010
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 Customer
…Stock Exchange Order DSL…
DSLs 2010 - 150
©ASERT2006-2010
class Order {
def security
def quantity
def limitPrice
def allOrNone
def value
def 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]
}
...
…Stock Exchange Order DSL…
DSLs 2010 - 151
©ASERT2006-2010
...
def getTo() { this }
def valueAs(closure) {
value = closure(quantity, limitPrice)
}
}
class Security {
String name
}
class Quantity {
Security security
Integer quantity
}
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, it's 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()
newOrder.to.buy(100.shares.of(IBM)) {
limitPrice 300
allOrNone true
valueAs { qty, unitPrice ->
qty * unitPrice - 200 }
}
newOrder.to.sell 200.shares.of(GOOG), {
limitPrice 200
allOrNone false
valueAs { qty, unitPrice ->
qty * unitPrice - 500 }
}
…Stock Exchange Order DSL…
DSLs 2010 - 152
©ASERT2006-2010
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 Customer
…Stock Exchange Order DSL…
DSLs 2010 - 153
©ASERT2006-2010
class Order {
...
def allOrNone
def valueCalculation
def bs
...
private buySell(securityQuantity,
closure) {
...
valueCalculation = c()
// debug print resulting order
println toString()
return this
}
...
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
}
String toString() {
"$bs $quantity shares of $security.name and valuation of
${valueCalculation(quantity, limitPrice)}"
}
}
newOrder.to.buy(100.shares.of(IBM)) {
limitPrice 300
allOrNone true
valueAs { qty * unitPrice - 500 }
}
newOrder.to.sell 200.shares.of(GOOG), {
limitPrice 200
allOrNone false
valueAs { qty * unitPrice - 100 }
}
…Stock Exchange Order DSL…
DSLs 2010 - 154
©ASERT2006-2010
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 Customer
…Stock Exchange Order DSL…
DSLs 2010 - 155
©ASERT2006-2010
// 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
}
String toString() {
"$action $quantity shares of $security.name at limit price of $limitPrice"
}
}
// ...
http://groovyconsole.appspot.com/script/226001 by glaforge
…Stock Exchange Order DSL…
DSLs 2010 - 156
©ASERT2006-2010
// ...
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
// don't thrown 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 }
…Stock Exchange Order DSL…
DSLs 2010 - 157
©ASERT2006-2010
// ---- Stock exchange orders 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 }
}
http://groovyconsole.appspot.com/script/226001 by glaforge
…Stock Exchange Order DSL
DSLs 2010 - 158
©ASERT2006-2010
From: customer@finance-broker.org
To: Guillaume
Subject: Project Request
Date: Wednesday evening
Dear Guillaume,
Brilliant! Even our CEO could write orders!
Thanks, Happy Customer
THURSDAY
DSLs 2010 - 159
©ASERT2006-2010
http://t1.gstatic.com/images?q=tbn:_VBloEP8YC-6IM:http://msp248.photobucket.com/albums/gg171/ingrid2002/TextPL252028
Einstein‟s Riddle DSL …
DSLs 2010 - 160
©ASERT2006-2010
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 Customer
Einstein‟s Riddle…
• Wikipedia: The zebra puzzle is a well-
known logic puzzle
– It is often called Einstein's Puzzle or Einstein's 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 world's population can solve it.”
– The puzzle is also sometimes attributed to Lewis
Carroll. However, there is no known evidence for
Einstein's or Carroll's authorship; and the original
puzzle cited below mentions brands of cigarette, such
as Kools, that did not exist during Carroll's lifetime or
Einstein's boyhood
DSLs 2010 - 161
©ASERT2006-2010
…Einstein‟s Riddle
DSLs 2010 - 162
©ASERT2006-2010
• 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 house
– The man who smokes Blend has a neighbor who drinks water
• And a question:
– Who owns the fish?
Einstein‟s Riddle : Prolog
©ASERT2006-2010
% from http://www.baptiste-wicht.com/2010/09/solve-einsteins-riddle-using-prolog
% Preliminary definitions
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).
% The Brit lives in a red house
hint1([(brit,red,_, _, _)|_]).
hint1([_|T]) :- hint1(T).
% The Swede keeps dogs as pets
hint2([(swede,_,_,_,dog)|_]).
hint2([_|T]) :- hint2(T).
% The Dane drinks tea
hint3([(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).
...
DSLs 2010 - 163
Einstein‟s Riddle DSL …
DSLs 2010 - 164
©ASERT2006-2010
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 Customer
Einstein‟s Riddle : Polyglot
©ASERT2006-2010
@GrabResolver('http://dev.inf.unideb.hu:8090/archiva/repository/internal')
//@Grab('jlog:jlogic-debug:1.3.6')
@Grab('org.prolog4j:prolog4j-api:0.2.0')
// uncomment one of the next three lines
//@Grab('org.prolog4j:prolog4j-jlog:0.2.0')
@Grab('org.prolog4j:prolog4j-tuprolog:0.2.0')
//@Grab('org.prolog4j:prolog4j-jtrolog:0.2.0')
import org.prolog4j.*
def p = ProverFactory.prover
p.addTheory(new File('/GroovyExamples/tuProlog/src/einstein.pl').text)
def sol = p.solve("solution(Persons).")
//println sol.solution.get('Persons') // jlog to avoid converter
println sol.get('Persons') // jtrolog/tuProlog
DSLs 2010 - 165
Einstein‟s Riddle DSL …
DSLs 2010 - 166
©ASERT2006-2010
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 Customer
Einstein‟s Riddle : Polyglot w/ DSL…
©ASERT2006-2010
// 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'
DSLs 2010 - 167
// 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).
''')
…Einstein‟s Riddle : Polyglot w/ DSL…
©ASERT2006-2010
DSLs 2010 - 168
// 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++
}
...
…Einstein‟s Riddle : Polyglot w/ DSL…
©ASERT2006-2010
DSLs 2010 - 169
// 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]) } ]
}
}]}
]
}
...
…Einstein‟s Riddle : Polyglot w/ DSL…
©ASERT2006-2010
DSLs 2010 - 170
// now define the DSL
the man from the centre house drinks milk
the Norwegian owns the first house
the Dane drinks tea
the German smokes prince
the Swede keeps dogs // alternate ending: has a pet dog
the Brit has a red house // alternate ending: red abode
the owner of the green house drinks coffee
the owner of the yellow house smokes dunhill
the person known to smoke pallmall rears birds // alt’n8 end: keeps birds
the man known to smoke bluemaster drinks beer
the green house is on the left side of the white house
the man known to smoke blends lives next to the one who keeps cats
the man known to keep horses lives next to the man who smokes dunhill
the man known to smoke blends lives next to the one who drinks water
the Norwegian lives next to the blue house
Einstein‟s Riddle DSL …
DSLs 2010 - 171
©ASERT2006-2010
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 Customer
…Einstein‟s Riddle : Polyglot w/ DSL…
©ASERT2006-2010
DSLs 2010 - 172
// 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 = German
ctx = [from: German]
[drinks: …,
smokes: { s -> addPairHint([from: German, smoke: s]) },
keeps: …,
…
]
addPairHint([from: German, smoke: prince])
…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:
DSLs 2010 - 173
©ASERT2006-2010
„house‟ is key for a Map and could be any value
…Einstein‟s Riddle : Polyglot w/ DSL
©ASERT2006-2010
DSLs 2010 - 174
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) }
def the(Color c1) {[
house: { _is -> [on: { _the -> [left: { _side -> [of: { __the ->
Color.values().collectEntries{ c2 -> [c2.toString(), { _dummy ->
addToLeftHint([color:c1], [color:c2])
}]}
}]}]}]}
]}
„house‟ is now understood
Einstein‟s Riddle DSL …
DSLs 2010 - 175
©ASERT2006-2010
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 Customer
Einstein‟s Riddle : Choco w/ DSL…
©ASERT2006-2010
DSLs 2010 - 176
@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)
}
...
…Einstein‟s Riddle : Choco w/ DSL…
©ASERT2006-2010
DSLs 2010 - 177
// 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)}] },
...
…Einstein‟s Riddle : Choco w/ DSL…
©ASERT2006-2010
DSLs 2010 - 178
// 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
...
…Einstein‟s Riddle : Choco w/ DSL
• Output:
DSLs 2010 - 179
©ASERT2006-2010
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 Einstein's 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 house
FRIDAY
DSLs 2010 - 180
©ASERT2006-2010
http://t3.gstatic.com/images?q=tbn:mgkj1lXpQ2-uWM:http://i298.photobucket.com/albums/mm269/bearyjuicylicious/comments
Important business meetings all afternoon!
DSLs 2010 - 181
©ASERT2006-2010
http://www.flickr.com/photos/bobswanson/5093775403/sizes/l/in/pool-52240148330@N01/
Topics
• Introduction
• Groovy DSL Features
• A Week In The Life Of DSL INC
Summary
• More Info
DSLs 2010 - 182
©ASERT2006-2010
Some Finishing Guidelines
• Language and program evolve together …
In the end your program will look as if the
language had been designed for it … you
end up with code which is clear, small,
and efficient.
– Paul Graham
• Don‟t think just about Coding up your DSL
– But also about its target audience, its evolution,
required tool support and its testability
DSLs 2010 - 183
Start small, with key concepts
Beware over-engineering!
Grow your language
progressively
Get your hands dirty
Play with the end-users
Let your DSL fly,
it‟s not yours, it‟s theirs!
Tight feedback loop
Iterative process
Stay humble,
You can‟t get it right the 1st time.
Don‟t design alone at your desk
Involve the end users from the start
Playing it safe in a sandbox
Topics
• Introduction
• Groovy DSL Features
• A Week In The Life Of DSL INC
• Summary
More Info
DSLs 2010 - 191
©ASERT2006-2010
Further Info…
DSLs 2010 - 192
©ASERT2006-2010
• Compilers : Principles, Techniques,
and Tools/ Edition 2, Alfred Aho, Ravi
Sethi, Jeffrey Ullman, Monica Lam
• Practical API Design : Confessions of
a Java Framework Architect, Jaroslav
Tulach
• Language Implementation Patterns :
Create Your Own Domain-Specific and
General Programming Languages,
Terence Parr
…Further Info
DSLs 2010 - 193
©ASERT2006-2010
• A model-driven framework for domain
specific languages, Martin Karlsch
• Design Guidelines for Domain Specific
Languages, Gabor Karsai et al
• Program Comprehension for Domain-
Specific Languages, Pereira et al
• Diagrammatic Representations in Domain-
Specific Languages, Konstantinos Tourlas
• DSLs in Action, Debasish Ghosh
• Domain-Specific Languages, Martin Fowler
• DSLs in Boo : Domain Specific Languages
in .NET, Ayende Rahien
GinA 2ed “ReGinA” is coming!
DSLs 2010 - 194
Contains a
chapter on
DSLs!

More Related Content

What's hot

Why Extension Programmers Should Stop Worrying About Parsing and Start Thinki...
Why Extension Programmers Should Stop Worrying About Parsing and Start Thinki...Why Extension Programmers Should Stop Worrying About Parsing and Start Thinki...
Why Extension Programmers Should Stop Worrying About Parsing and Start Thinki...David Beazley (Dabeaz LLC)
 
DSL Construction rith Ruby
DSL Construction rith RubyDSL Construction rith Ruby
DSL Construction rith RubyThoughtWorks
 
Practical Groovy Domain-Specific Languages
Practical Groovy Domain-Specific LanguagesPractical Groovy Domain-Specific Languages
Practical Groovy Domain-Specific LanguagesGuillaume Laforge
 
Gemification for Ruby 2.5/3.0
Gemification for Ruby 2.5/3.0Gemification for Ruby 2.5/3.0
Gemification for Ruby 2.5/3.0Hiroshi SHIBATA
 
SWIG : An Easy to Use Tool for Integrating Scripting Languages with C and C++
SWIG : An Easy to Use Tool for Integrating Scripting Languages with C and C++SWIG : An Easy to Use Tool for Integrating Scripting Languages with C and C++
SWIG : An Easy to Use Tool for Integrating Scripting Languages with C and C++David Beazley (Dabeaz LLC)
 
Modern Black Mages Fighting in the Real World
Modern Black Mages Fighting in the Real WorldModern Black Mages Fighting in the Real World
Modern Black Mages Fighting in the Real WorldSATOSHI TAGOMORI
 
Gaelyk - JFokus 2011 - Guillaume Laforge
Gaelyk - JFokus 2011 - Guillaume LaforgeGaelyk - JFokus 2011 - Guillaume Laforge
Gaelyk - JFokus 2011 - Guillaume LaforgeGuillaume Laforge
 
The Year of JRuby - RubyC 2018
The Year of JRuby - RubyC 2018The Year of JRuby - RubyC 2018
The Year of JRuby - RubyC 2018Charles Nutter
 
Deep dive into Xtext scoping local and global scopes explained
Deep dive into Xtext scoping local and global scopes explainedDeep dive into Xtext scoping local and global scopes explained
Deep dive into Xtext scoping local and global scopes explainedHolger Schill
 
Xtext beyond the defaults - how to tackle performance problems
Xtext beyond the defaults -  how to tackle performance problemsXtext beyond the defaults -  how to tackle performance problems
Xtext beyond the defaults - how to tackle performance problemsHolger Schill
 
The Return of the Living Datalog
The Return of the Living DatalogThe Return of the Living Datalog
The Return of the Living DatalogMike Fogus
 
Redesigning Common Lisp
Redesigning Common LispRedesigning Common Lisp
Redesigning Common Lispfukamachi
 
Mongodb and Totsy - E-commerce Case Study
Mongodb and Totsy - E-commerce Case StudyMongodb and Totsy - E-commerce Case Study
Mongodb and Totsy - E-commerce Case StudyMitch Pirtle
 

What's hot (20)

Why Extension Programmers Should Stop Worrying About Parsing and Start Thinki...
Why Extension Programmers Should Stop Worrying About Parsing and Start Thinki...Why Extension Programmers Should Stop Worrying About Parsing and Start Thinki...
Why Extension Programmers Should Stop Worrying About Parsing and Start Thinki...
 
DSL Construction rith Ruby
DSL Construction rith RubyDSL Construction rith Ruby
DSL Construction rith Ruby
 
Google Protocol Buffers
Google Protocol BuffersGoogle Protocol Buffers
Google Protocol Buffers
 
Python in Action (Part 2)
Python in Action (Part 2)Python in Action (Part 2)
Python in Action (Part 2)
 
Practical Groovy Domain-Specific Languages
Practical Groovy Domain-Specific LanguagesPractical Groovy Domain-Specific Languages
Practical Groovy Domain-Specific Languages
 
Gemification for Ruby 2.5/3.0
Gemification for Ruby 2.5/3.0Gemification for Ruby 2.5/3.0
Gemification for Ruby 2.5/3.0
 
SWIG : An Easy to Use Tool for Integrating Scripting Languages with C and C++
SWIG : An Easy to Use Tool for Integrating Scripting Languages with C and C++SWIG : An Easy to Use Tool for Integrating Scripting Languages with C and C++
SWIG : An Easy to Use Tool for Integrating Scripting Languages with C and C++
 
Modern Black Mages Fighting in the Real World
Modern Black Mages Fighting in the Real WorldModern Black Mages Fighting in the Real World
Modern Black Mages Fighting in the Real World
 
Gaelyk - JFokus 2011 - Guillaume Laforge
Gaelyk - JFokus 2011 - Guillaume LaforgeGaelyk - JFokus 2011 - Guillaume Laforge
Gaelyk - JFokus 2011 - Guillaume Laforge
 
Perl-C/C++ Integration with Swig
Perl-C/C++ Integration with SwigPerl-C/C++ Integration with Swig
Perl-C/C++ Integration with Swig
 
The Year of JRuby - RubyC 2018
The Year of JRuby - RubyC 2018The Year of JRuby - RubyC 2018
The Year of JRuby - RubyC 2018
 
Understanding the Python GIL
Understanding the Python GILUnderstanding the Python GIL
Understanding the Python GIL
 
C# Is The Future
C# Is The FutureC# Is The Future
C# Is The Future
 
Mastering Python 3 I/O
Mastering Python 3 I/OMastering Python 3 I/O
Mastering Python 3 I/O
 
Deep dive into Xtext scoping local and global scopes explained
Deep dive into Xtext scoping local and global scopes explainedDeep dive into Xtext scoping local and global scopes explained
Deep dive into Xtext scoping local and global scopes explained
 
Intro to J Ruby
Intro to J RubyIntro to J Ruby
Intro to J Ruby
 
Xtext beyond the defaults - how to tackle performance problems
Xtext beyond the defaults -  how to tackle performance problemsXtext beyond the defaults -  how to tackle performance problems
Xtext beyond the defaults - how to tackle performance problems
 
The Return of the Living Datalog
The Return of the Living DatalogThe Return of the Living Datalog
The Return of the Living Datalog
 
Redesigning Common Lisp
Redesigning Common LispRedesigning Common Lisp
Redesigning Common Lisp
 
Mongodb and Totsy - E-commerce Case Study
Mongodb and Totsy - E-commerce Case StudyMongodb and Totsy - E-commerce Case Study
Mongodb and Totsy - E-commerce Case Study
 

Similar to groovy DSLs from beginner to expert

Domain Specific Languages
Domain Specific LanguagesDomain Specific Languages
Domain Specific LanguagesLakshan Perera
 
DSL Construction with Ruby - ThoughtWorks Masterclass Series 2009
DSL Construction with Ruby - ThoughtWorks Masterclass Series 2009DSL Construction with Ruby - ThoughtWorks Masterclass Series 2009
DSL Construction with Ruby - ThoughtWorks Masterclass Series 2009Harshal Hayatnagarkar
 
A Survey on Domain-Specific Languages for Machine.pdfA Sur.docx
A Survey on Domain-Specific Languages for Machine.pdfA Sur.docxA Survey on Domain-Specific Languages for Machine.pdfA Sur.docx
A Survey on Domain-Specific Languages for Machine.pdfA Sur.docxbartholomeocoombs
 
Building DSLs On CLR and DLR (Microsoft.NET)
Building DSLs On CLR and DLR (Microsoft.NET)Building DSLs On CLR and DLR (Microsoft.NET)
Building DSLs On CLR and DLR (Microsoft.NET)Vitaly Baum
 
Concepts of JetBrains MPS
Concepts of JetBrains MPSConcepts of JetBrains MPS
Concepts of JetBrains MPSVaclav Pech
 
Realizing the promise of portable data processing with Apache Beam
Realizing the promise of portable data processing with Apache BeamRealizing the promise of portable data processing with Apache Beam
Realizing the promise of portable data processing with Apache BeamDataWorks Summit
 
Creating a textual domain specific language
Creating a textual domain specific languageCreating a textual domain specific language
Creating a textual domain specific languageVicente García Díaz
 
Presentation1 for IRTVHEGCCUICUIDCFICFJIFCI.PPTX
Presentation1 for IRTVHEGCCUICUIDCFICFJIFCI.PPTXPresentation1 for IRTVHEGCCUICUIDCFICFJIFCI.PPTX
Presentation1 for IRTVHEGCCUICUIDCFICFJIFCI.PPTXEromoborOnobun
 
MozillaPH Rust Hack & Learn Session 1
MozillaPH Rust Hack & Learn Session 1MozillaPH Rust Hack & Learn Session 1
MozillaPH Rust Hack & Learn Session 1Robert 'Bob' Reyes
 
Bdd and dsl как способ построения коммуникации на проекте
Bdd and dsl как способ построения коммуникации на проектеBdd and dsl как способ построения коммуникации на проекте
Bdd and dsl как способ построения коммуникации на проектеISsoft
 
BDD or DSL как способ построения коммуникации на проекте - опыт комплексного ...
BDD or DSL как способ построения коммуникации на проекте - опыт комплексного ...BDD or DSL как способ построения коммуникации на проекте - опыт комплексного ...
BDD or DSL как способ построения коммуникации на проекте - опыт комплексного ...SQALab
 
AestasIT - Internal DSLs in Scala
AestasIT - Internal DSLs in ScalaAestasIT - Internal DSLs in Scala
AestasIT - Internal DSLs in ScalaDmitry Buzdin
 
Source-to-source transformations: Supporting tools and infrastructure
Source-to-source transformations: Supporting tools and infrastructureSource-to-source transformations: Supporting tools and infrastructure
Source-to-source transformations: Supporting tools and infrastructurekaveirious
 
2015 bioinformatics python_introduction_wim_vancriekinge_vfinal
2015 bioinformatics python_introduction_wim_vancriekinge_vfinal2015 bioinformatics python_introduction_wim_vancriekinge_vfinal
2015 bioinformatics python_introduction_wim_vancriekinge_vfinalProf. Wim Van Criekinge
 

Similar to groovy DSLs from beginner to expert (20)

GroovyDSLs
GroovyDSLsGroovyDSLs
GroovyDSLs
 
Metamorphic Domain-Specific Languages
Metamorphic Domain-Specific LanguagesMetamorphic Domain-Specific Languages
Metamorphic Domain-Specific Languages
 
Domain Specific Languages
Domain Specific LanguagesDomain Specific Languages
Domain Specific Languages
 
KIRANKUMAR_MV
KIRANKUMAR_MVKIRANKUMAR_MV
KIRANKUMAR_MV
 
A Common Backend for Hardware Acceleration of DSLs on FPGA
A Common Backend for Hardware Acceleration of DSLs on FPGAA Common Backend for Hardware Acceleration of DSLs on FPGA
A Common Backend for Hardware Acceleration of DSLs on FPGA
 
DSL Construction with Ruby - ThoughtWorks Masterclass Series 2009
DSL Construction with Ruby - ThoughtWorks Masterclass Series 2009DSL Construction with Ruby - ThoughtWorks Masterclass Series 2009
DSL Construction with Ruby - ThoughtWorks Masterclass Series 2009
 
A Survey on Domain-Specific Languages for Machine.pdfA Sur.docx
A Survey on Domain-Specific Languages for Machine.pdfA Sur.docxA Survey on Domain-Specific Languages for Machine.pdfA Sur.docx
A Survey on Domain-Specific Languages for Machine.pdfA Sur.docx
 
Building DSLs On CLR and DLR (Microsoft.NET)
Building DSLs On CLR and DLR (Microsoft.NET)Building DSLs On CLR and DLR (Microsoft.NET)
Building DSLs On CLR and DLR (Microsoft.NET)
 
Resume
ResumeResume
Resume
 
Concepts of JetBrains MPS
Concepts of JetBrains MPSConcepts of JetBrains MPS
Concepts of JetBrains MPS
 
Realizing the promise of portable data processing with Apache Beam
Realizing the promise of portable data processing with Apache BeamRealizing the promise of portable data processing with Apache Beam
Realizing the promise of portable data processing with Apache Beam
 
Creating a textual domain specific language
Creating a textual domain specific languageCreating a textual domain specific language
Creating a textual domain specific language
 
Presentation1 for IRTVHEGCCUICUIDCFICFJIFCI.PPTX
Presentation1 for IRTVHEGCCUICUIDCFICFJIFCI.PPTXPresentation1 for IRTVHEGCCUICUIDCFICFJIFCI.PPTX
Presentation1 for IRTVHEGCCUICUIDCFICFJIFCI.PPTX
 
MozillaPH Rust Hack & Learn Session 1
MozillaPH Rust Hack & Learn Session 1MozillaPH Rust Hack & Learn Session 1
MozillaPH Rust Hack & Learn Session 1
 
Bdd and dsl как способ построения коммуникации на проекте
Bdd and dsl как способ построения коммуникации на проектеBdd and dsl как способ построения коммуникации на проекте
Bdd and dsl как способ построения коммуникации на проекте
 
BDD or DSL как способ построения коммуникации на проекте - опыт комплексного ...
BDD or DSL как способ построения коммуникации на проекте - опыт комплексного ...BDD or DSL как способ построения коммуникации на проекте - опыт комплексного ...
BDD or DSL как способ построения коммуникации на проекте - опыт комплексного ...
 
Scam 08
Scam 08Scam 08
Scam 08
 
AestasIT - Internal DSLs in Scala
AestasIT - Internal DSLs in ScalaAestasIT - Internal DSLs in Scala
AestasIT - Internal DSLs in Scala
 
Source-to-source transformations: Supporting tools and infrastructure
Source-to-source transformations: Supporting tools and infrastructureSource-to-source transformations: Supporting tools and infrastructure
Source-to-source transformations: Supporting tools and infrastructure
 
2015 bioinformatics python_introduction_wim_vancriekinge_vfinal
2015 bioinformatics python_introduction_wim_vancriekinge_vfinal2015 bioinformatics python_introduction_wim_vancriekinge_vfinal
2015 bioinformatics python_introduction_wim_vancriekinge_vfinal
 

More from Paul King

awesome groovy
awesome groovyawesome groovy
awesome groovyPaul King
 
groovy databases
groovy databasesgroovy databases
groovy databasesPaul King
 
groovy transforms
groovy transformsgroovy transforms
groovy transformsPaul King
 
concurrency gpars
concurrency gparsconcurrency gpars
concurrency gparsPaul King
 
tictactoe groovy
tictactoe groovytictactoe groovy
tictactoe groovyPaul King
 
groovy rules
groovy rulesgroovy rules
groovy rulesPaul King
 
functional groovy
functional groovyfunctional groovy
functional groovyPaul King
 
Make Testing Groovy
Make Testing GroovyMake Testing Groovy
Make Testing GroovyPaul King
 
Agile Testing Practices
Agile Testing PracticesAgile Testing Practices
Agile Testing PracticesPaul King
 
concurrency with GPars
concurrency with GParsconcurrency with GPars
concurrency with GParsPaul King
 
groovy and concurrency
groovy and concurrencygroovy and concurrency
groovy and concurrencyPaul King
 
Dynamic Language Practices
Dynamic Language PracticesDynamic Language Practices
Dynamic Language PracticesPaul King
 
Make Your Builds More Groovy
Make Your Builds More GroovyMake Your Builds More Groovy
Make Your Builds More GroovyPaul King
 
Groovy Power Features
Groovy Power FeaturesGroovy Power Features
Groovy Power FeaturesPaul King
 
Make Your Testing Groovy
Make Your Testing GroovyMake Your Testing Groovy
Make Your Testing GroovyPaul King
 
Groovy Testing Sep2009
Groovy Testing Sep2009Groovy Testing Sep2009
Groovy Testing Sep2009Paul King
 
Craig Smith & Paul King Agile Tool Hacking Taking Your Agile Development ...
Craig Smith & Paul King   Agile Tool Hacking   Taking Your Agile Development ...Craig Smith & Paul King   Agile Tool Hacking   Taking Your Agile Development ...
Craig Smith & Paul King Agile Tool Hacking Taking Your Agile Development ...Paul King
 
Groovy Tutorial
Groovy TutorialGroovy Tutorial
Groovy TutorialPaul King
 
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Pau...
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Pau...Industrial Strength Groovy - Tools for the Professional Groovy Developer: Pau...
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Pau...Paul King
 
XML and Web Services with Groovy
XML and Web Services with GroovyXML and Web Services with Groovy
XML and Web Services with GroovyPaul King
 

More from Paul King (20)

awesome groovy
awesome groovyawesome groovy
awesome groovy
 
groovy databases
groovy databasesgroovy databases
groovy databases
 
groovy transforms
groovy transformsgroovy transforms
groovy transforms
 
concurrency gpars
concurrency gparsconcurrency gpars
concurrency gpars
 
tictactoe groovy
tictactoe groovytictactoe groovy
tictactoe groovy
 
groovy rules
groovy rulesgroovy rules
groovy rules
 
functional groovy
functional groovyfunctional groovy
functional groovy
 
Make Testing Groovy
Make Testing GroovyMake Testing Groovy
Make Testing Groovy
 
Agile Testing Practices
Agile Testing PracticesAgile Testing Practices
Agile Testing Practices
 
concurrency with GPars
concurrency with GParsconcurrency with GPars
concurrency with GPars
 
groovy and concurrency
groovy and concurrencygroovy and concurrency
groovy and concurrency
 
Dynamic Language Practices
Dynamic Language PracticesDynamic Language Practices
Dynamic Language Practices
 
Make Your Builds More Groovy
Make Your Builds More GroovyMake Your Builds More Groovy
Make Your Builds More Groovy
 
Groovy Power Features
Groovy Power FeaturesGroovy Power Features
Groovy Power Features
 
Make Your Testing Groovy
Make Your Testing GroovyMake Your Testing Groovy
Make Your Testing Groovy
 
Groovy Testing Sep2009
Groovy Testing Sep2009Groovy Testing Sep2009
Groovy Testing Sep2009
 
Craig Smith & Paul King Agile Tool Hacking Taking Your Agile Development ...
Craig Smith & Paul King   Agile Tool Hacking   Taking Your Agile Development ...Craig Smith & Paul King   Agile Tool Hacking   Taking Your Agile Development ...
Craig Smith & Paul King Agile Tool Hacking Taking Your Agile Development ...
 
Groovy Tutorial
Groovy TutorialGroovy Tutorial
Groovy Tutorial
 
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Pau...
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Pau...Industrial Strength Groovy - Tools for the Professional Groovy Developer: Pau...
Industrial Strength Groovy - Tools for the Professional Groovy Developer: Pau...
 
XML and Web Services with Groovy
XML and Web Services with GroovyXML and Web Services with Groovy
XML and Web Services with Groovy
 

Recently uploaded

Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Patryk Bandurski
 
Unraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfUnraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfAlex Barbosa Coqueiro
 
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostLeverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostZilliz
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxNavinnSomaal
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr BaganFwdays
 
Story boards and shot lists for my a level piece
Story boards and shot lists for my a level pieceStory boards and shot lists for my a level piece
Story boards and shot lists for my a level piececharlottematthew16
 
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticscarlostorres15106
 
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)Bun (KitWorks Team Study 노별마루 발표 2024.4.22)
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)Wonjun Hwang
 
Training state-of-the-art general text embedding
Training state-of-the-art general text embeddingTraining state-of-the-art general text embedding
Training state-of-the-art general text embeddingZilliz
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):comworks
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLScyllaDB
 
Beyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry InnovationBeyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry InnovationSafe Software
 
My Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationMy Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationRidwan Fadjar
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsMark Billinghurst
 
The Future of Software Development - Devin AI Innovative Approach.pdf
The Future of Software Development - Devin AI Innovative Approach.pdfThe Future of Software Development - Devin AI Innovative Approach.pdf
The Future of Software Development - Devin AI Innovative Approach.pdfSeasiaInfotech2
 
Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Scott Keck-Warren
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii SoldatenkoFwdays
 
Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfRankYa
 

Recently uploaded (20)

Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
 
Unraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfUnraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdf
 
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostLeverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptx
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan
 
Story boards and shot lists for my a level piece
Story boards and shot lists for my a level pieceStory boards and shot lists for my a level piece
Story boards and shot lists for my a level piece
 
DMCC Future of Trade Web3 - Special Edition
DMCC Future of Trade Web3 - Special EditionDMCC Future of Trade Web3 - Special Edition
DMCC Future of Trade Web3 - Special Edition
 
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
 
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)Bun (KitWorks Team Study 노별마루 발표 2024.4.22)
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)
 
Training state-of-the-art general text embedding
Training state-of-the-art general text embeddingTraining state-of-the-art general text embedding
Training state-of-the-art general text embedding
 
CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):CloudStudio User manual (basic edition):
CloudStudio User manual (basic edition):
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQL
 
Beyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry InnovationBeyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
 
My Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationMy Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 Presentation
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR Systems
 
The Future of Software Development - Devin AI Innovative Approach.pdf
The Future of Software Development - Devin AI Innovative Approach.pdfThe Future of Software Development - Devin AI Innovative Approach.pdf
The Future of Software Development - Devin AI Innovative Approach.pdf
 
Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko
 
Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdf
 

groovy DSLs from beginner to expert

  • 1. Chicago, October 19 - 22, 2010 Paul King @paulk_asert Groovy Committer ASERT, Australia Groovy.DSLs(from: beginner, to: expert) Guillaume Laforge @glaforge Groovy Project Manager SpringSource / VMWare
  • 2. Topics Introduction • Groovy DSL Features • A Week In The Life Of DSL INC • Summary • More Info DSLs 2010 - 2 ©ASERT2006-2010
  • 3. What is a DSL? • 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, general-purpose languages are created to solve problems in many domains – Somewhere between declarative data and a full blown general- purpose programming language (GPL) – AKA: fluent / human interfaces, language oriented programming, problem-oriented languages, little / mini languages, macros, business natural languages Sources: http://en.wikipedia.org/wiki/Domain-specific_language van Deursen, A., Klint, P., Visser, J.: Domain-specific languages: an annotated bibliography. ACM SIGPLAN Notices 35 (2000) 26–36 ©ASERT2006-2010
  • 4. 1. e4 e5 2. Nf3 Nc6 3. Bb5 a6
  • 5. L2 U F-1 B L2 F B -1 U L2
  • 7. Technical Examples Source: Applying minilanguages: http://www.faqs.org/docs/artu/ch08s02.html <?xml version="1.0"?> <xsl:stylesheetversion="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml"/> <xsl:template match="*"> <xsl:element name="{name()}"> <xsl:for-each select="@*"> <xsl:element name="{name()}"> <xsl:value-of select="."/> </xsl:element> </xsl:for-each> <xsl:apply-templates select="*|text()"/> </xsl:element> </xsl:template> </xsl:stylesheet> cat thesis.ms | chem | tbl | refer | grap | pic | eqn | groff -Tps > thesis.ps <?xml version="1.0"?> <GTK-Interface> <widget> <class>GtkWindow</class> <name>HelloWindow</name> <border_width>5</border_width> <Signal> <name>destroy</name> <handler>gtk_main_quit</handler> </Signal> <title>Hello</title> <type>GTK_WINDOW_TOPLEVEL</type> <position>GTK_WIN_POS_NONE</position> <allow_shrink>True</allow_shrink> <allow_grow>True</allow_grow> <auto_shrink>False</auto_shrink> <widget> <class>GtkButton</class> <name>Hello World</name> <can_focus>True</can_focus> <label>Hello World</label> </widget> </widget> </GTK-Interface> # Poll this site first each cycle. poll pop.provider.net proto pop3 user "jsmith" with pass "secret1" is "smith" here user jones with pass "secret2" is "jjones" here with options keep # Poll this site second, unless Lord Voldemort zaps us first. poll billywig.hogwarts.com with proto imap: user harry_potter with pass "floo" is harry_potter here # Poll this site third in the cycle. # Password will be fetched from ~/.netrc poll mailhost.net with proto imap: user esr is esr here Glade Troff XSLT fetchmail Regex "x.z?z{1,3}y" SQL SELECT * FROM TABLE WHERE NAME LIKE '%SMI' ORDER BY NAME DSLs 2010 - 7
  • 8. Origins • It has always been a holy grail of computer users to be able to speak directly to our computers • Different languages have pushed the boundaries further than others – APT for numerically controlled machine tools (1957) – BNF (1959), COBOL, 4GLs – LISP & Smalltalk – Unix "little languages" DSLs 2010 - 8 ©ASERT2006-2010
  • 9. Origins • It has always been a holy grail of computer users to be able to speak directly to our computers DSLs 2010 - 9 http://dsal.uchicago.edu/reference/gazetteer/pager.html?objectid=DS405.1.I34_V02_298.gif ©ASERT2006-2010
  • 10. Origins: LISP & Smalltalk “In Lisp, you don’t just write your program down toward the language, you also build the language up toward your program” - Paul Graham “When [Smalltalk] is used to describe an application system, the developer extends Smalltalk, creating a domain-specific language by adding a new vocabulary of language elements ...” - Adele Goldberg DSLs 2010 - 10 ©ASERT2006-2010
  • 11. Origins: Minilanguages... Source: The Art of Unix Programming:Taxonomy of languages: http://www.faqs.org/docs/artu/ch08s01.html %!PS-Adobe-3.0 %%Creator: groff version 1.20.1 ... 597.6 12 72 12 DL(increasing loopiness)297.71 8.2 Q(/etc/passwd)102.67 94.6 Q(.ne)110.715 106.6 Q(wsrc)-.25 E(SNG)195.2 100.6 Q(re)243.8 94.6 Q (ge)-.15 E(xps)-.15 E(Glade)247.26 106.6 Q(m4)306.81 58.6 Q -1(Ya)303.43 70.6 S(cc)1 E(Le)305.5 82.6 Q(x)-.15 E(mak)302.42 94.6 Q(e)-.1 E(XSL) 301.16 106.6 Q(T)-.92 E(pic)307.09 118.6 Q(tbl)307.92 130.6 Q(eqn)305.98 142.6 Q(fetchmail)344.715 82.6 Q -.15(aw)355.345 94.6 S(k).15 E(trof) 354.84 106.6 Q(f)-.25 E(Postscript)343.875 118.6 Q(dc)412.88 94.6 Q(bc) 412.88 106.6 Q(Emacs Lisp)462.53 94.6 Q(Ja)465.395 106.6 Q -.25(va)-.2 G (Script).25 E(sh)529.075 94.6 Q(tcl)528.52 106.6 Q(Perl)565.065 88.6 Q (Python)558.95 100.6 Q(Ja)564.46 112.6 Q -.25(va)-.2 G 0 Cg EP %%Trailer end %%EOF Input DSL: pic for above picture Output "DSL": Postscript for above picture # Minilanguage taxonomy # Base ellipses define smallellipse {ellipse width 3.0 height 1.5} M: ellipse width 3.0 height 1.8 fill 0.2 line from M.n to M.s dashed D: smallellipse() with .e at M.w + (0.8, 0) line from D.n to D.s dashed I: smallellipse() with .w at M.e - (0.8, 0) # Arrow headings arrow from D.w + (0.4, 0.8) to D.e + (-0.4, 0.8) "flat to structured" "" at last arrow.c ... # Interpreters "Emacs Lisp" "JavaScript" at 0.25 between M.e and I.e "sh" "tcl" at 0.55 between M.e and I.e "Perl" "Python" "Java" at 0.8 between M.e and I.e DSLs 2010 - 11
  • 12. ...Origins: Minilanguages PowerPoint DSL for previous slide import builder.PowerPointBuilder def name = 'minilanguages' assert new File("${name}.pic').exists() "groff -e -p ${name}.pic > ${name}.ps".execute() "gs -q -sDEVICE='ppmraw' -g2600x3500 -r300x300 -sOutputFile='-' -dBATCH ↵ –dNOPAUSE ${name}.ps | pnmcrop | ppmtogif > ${name}.gif".execute() def builder = new PowerPointBuilder() // Adapted from Erik Pragt builder.slideshow(filename: 'DSLsInGroovy_MiniLanguages.ppt') { slide(title: 'Origins: Minilanguages...') { image( origin: [0, 15], src: "${name}.gif", caption: 'Source: The Art of Unix Programming:Taxonomy...' ) textbox( origin: [5, 100], text: new File("${name}.pic").text, caption: 'Input DSL: pic for above picture' ) textbox( origin: [115, 100], text: new File("${name}.ps").text, caption: 'Output "DSL": Postscript for above picture' ) } } DSLs 2010 - 12
  • 14. HAI CAN HAS STDIO? I HAS A VAR IM IN YR LOOP UP VAR!!1 VISIBLE VAR IZ VAR BIGGER THAN 10? KTHXBYE IM OUTTA YR LOOP KTHXBYE Developer producing LOLCODE
  • 15. And in the end... nobody understands each other
  • 16. DSL: a potential solution? 1616 • 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 too much boilerplate technical code • Cleanly separate business logic from application code • Let business rules have their own lifecycle
  • 17. 17 Copyright 2010 SpringSource. Copying, publishing or distributing without express written permission is prohibited. Towards more readibility (1) 20% 17
  • 18. 18 Copyright 2010 SpringSource. Copying, publishing or distributing without express written permission is prohibited. Towards more readibility (2) 80%
  • 19. Why a DSL? • Advantages: – Domain experts can understand, validate, modify, and often even develop DSL programs – Somewhat self-documenting – Enhance quality, productivity, reliability, maintainability, portability and reusability – Safety; as long as the language constructs are safe any sentence written with them can be considered safe • Disadvantages: – Learning cost vs. limited applicability – Cost of designing, implementing & maintaining DSL as well as the tools/IDEs – Attaining proper scope – Trade-offs between domain- specificity and general- purpose programming language constructs – Efficiency costs – Proliferation of similar non- standard DSLs, i.e. different but similar DSLs used within two insurance companies Source: http://en.wikipedia.org/wiki/Domain-specific_language DSLs 2010 - 19 ©ASERT2006-2010
  • 20. DSL usage patterns... • Internal (uses a general purpose language) – AKA embedded – Can be a subset or superset – Can be generated/converted from other "language" – Numerous mechanisms to make as close to the domain as possible • External (custom language) – May involve writing your own traditional parser – Using parser combinators – Interpreting – String grepping • Other characteristics – Functional vs OO DSLs 2010 - 20 ©ASERT2006-2010
  • 21. ...DSL usage patterns... Source: DSLs in Action, Debasish Ghosh, Manning now in MEAP DSLs 2010 - 21 ©ASERT2006-2010
  • 22. ...DSL usage patterns Source: DSLs in Action, Debasish Ghosh, Manning now in MEAP DSLs 2010 - 22 ©ASERT2006-2010
  • 23. DSL usage patterns (Advanced) Source: http://www.spinellis.gr/pubs/jrnl/2000-JSS-DSLPatterns/html/dslpat.html See: Diomidis Spinellis. Notable design patterns for domain specific languages. Journal of Systems and Software, 56(1):91–99, February 2001. ©ASERT2006-2010
  • 24. DSLs 2010 - 24 ©ASERT2006-2010 What is Groovy? • “Groovy is like a super version of Java. It can leverage Java's enterprise capabilities but also has cool productivity features like closures, DSL support, builders and dynamic typing.” Groovy = Java – boiler plate code + optional dynamic typing + closures + domain specific languages + builders + metaprogramming
  • 25. DSLs 2010 - 25 ©ASERT2006-2010 Groovy Goodies Overview • Fully object oriented • Closures: reusable and assignable pieces of code • Operators can be overloaded • Multimethods • Literal declaration for lists (arrays), maps, ranges and regular expressions • GPath: efficient object navigation • GroovyBeans • grep and switch • Templates, builder, swing, Ant, markup, XML, SQL, XML-RPC, Scriptom, Grails, tests, Mocks
  • 26. Growing Acceptance … A slow and steady start but now gaining in momentum, maturity and mindshare Now free
  • 27. DSLs 2010 - 27 ©ASERT2006-2010 The Landscape of JVM Languages Java bytecode calls for static types Dynamic features call for dynamic types optional static types The terms “Java Virtual Machine” and “JVM” mean a Virtual Machine for the Java™ platform.
  • 28. DSLs 2010 - 28 ©ASERT2006-2010 Groovy Starter System.out.println("Hello, World!"); // supports Java syntax println 'Hello, World!' // but can remove some syntax String name = 'Guillaume' // Explicit typing/awareness println "$name, I'll get the car." // Gstring (interpolation) def longer = """${name}, the car is in the next row.""" // multi-line, implicit type assert 0.5 == 1/2 // BigDecimal equals() assert 0.1 + 0.2 == 0.3 // and arithmetic def printSize(obj) { // implicit/duck typing print obj?.size() // safe dereferencing } def pets = ['ant', 'bee', 'cat'] // native list syntax pets.each { pet -> // closure support assert pet < 'dog' // overloading '<' on String } // or: for (pet in pets)...
  • 29. A Better Java... DSLs 2010 - 29 ©ASERT2006-2010 import java.util.List; import java.util.ArrayList; class Erase { private List removeLongerThan(List strings, int length) { List result = new ArrayList(); for (int i = 0; i < strings.size(); i++) { String s = (String) strings.get(i); if (s.length() <= length) { result.add(s); } } return result; } public static void main(String[] args) { List names = new ArrayList(); names.add("Ted"); names.add("Fred"); names.add("Jed"); names.add("Ned"); System.out.println(names); Erase e = new Erase(); List shortNames = e.removeLongerThan(names, 3); System.out.println(shortNames.size()); for (int i = 0; i < shortNames.size(); i++) { String s = (String) shortNames.get(i); System.out.println(s); } } } This code is valid Java and valid Groovy Based on an example by Jim Weirich & Ted Leung
  • 30. ...A Better Java... DSLs 2010 - 30 ©ASERT2006-2010 import java.util.List; import java.util.ArrayList; class Erase { private List removeLongerThan(List strings, int length) { List result = new ArrayList(); for (int i = 0; i < strings.size(); i++) { String s = (String) strings.get(i); if (s.length() <= length) { result.add(s); } } return result; } public static void main(String[] args) { List names = new ArrayList(); names.add("Ted"); names.add("Fred"); names.add("Jed"); names.add("Ned"); System.out.println(names); Erase e = new Erase(); List shortNames = e.removeLongerThan(names, 3); System.out.println(shortNames.size()); for (int i = 0; i < shortNames.size(); i++) { String s = (String) shortNames.get(i); System.out.println(s); } } } Do the semicolons add anything? And shouldn‟t we us more modern list notation? Why not import common libraries?
  • 31. ...A Better Java... DSLs 2010 - 31 ©ASERT2006-2010 class Erase { private List removeLongerThan(List strings, int length) { List result = new ArrayList() for (String s in strings) { if (s.length() <= length) { result.add(s) } } return result } public static void main(String[] args) { List names = new ArrayList() names.add("Ted"); names.add("Fred") names.add("Jed"); names.add("Ned") System.out.println(names) Erase e = new Erase() List shortNames = e.removeLongerThan(names, 3) System.out.println(shortNames.size()) for (String s in shortNames) { System.out.println(s) } } }
  • 32. ...A Better Java... DSLs 2010 - 32 ©ASERT2006-2010 class Erase { private List removeLongerThan(List strings, int length) { List result = new ArrayList() for (String s in strings) { if (s.length() <= length) { result.add(s) } } return result } public static void main(String[] args) { List names = new ArrayList() names.add("Ted"); names.add("Fred") names.add("Jed"); names.add("Ned") System.out.println(names) Erase e = new Erase() List shortNames = e.removeLongerThan(names, 3) System.out.println(shortNames.size()) for (String s in shortNames) { System.out.println(s) } } } Do we need the static types? Must we always have a main method and class definition? How about improved consistency?
  • 33. ...A Better Java... DSLs 2010 - 33 ©ASERT2006-2010 def removeLongerThan(strings, length) { def result = new ArrayList() for (s in strings) { if (s.size() <= length) { result.add(s) } } return result } names = new ArrayList() names.add("Ted") names.add("Fred") names.add("Jed") names.add("Ned") System.out.println(names) shortNames = removeLongerThan(names, 3) System.out.println(shortNames.size()) for (s in shortNames) { System.out.println(s) }
  • 34. ...A Better Java... DSLs 2010 - 34 ©ASERT2006-2010 def removeLongerThan(strings, length) { def result = new ArrayList() for (s in strings) { if (s.size() <= length) { result.add(s) } } return result } names = new ArrayList() names.add("Ted") names.add("Fred") names.add("Jed") names.add("Ned") System.out.println(names) shortNames = removeLongerThan(names, 3) System.out.println(shortNames.size()) for (s in shortNames) { System.out.println(s) } Shouldn‟t we have special notation for lists? And special facilities for list processing? Is „return‟ needed at end?
  • 35. ...A Better Java... DSLs 2010 - 35 ©ASERT2006-2010 def removeLongerThan(strings, length) { strings.findAll{ it.size() <= length } } names = ["Ted", "Fred", "Jed", "Ned"] System.out.println(names) shortNames = removeLongerThan(names, 3) System.out.println(shortNames.size()) shortNames.each{ System.out.println(s) }
  • 36. ...A Better Java... DSLs 2010 - 36 ©ASERT2006-2010 def removeLongerThan(strings, length) { strings.findAll{ it.size() <= length } } names = ["Ted", "Fred", "Jed", "Ned"] System.out.println(names) shortNames = removeLongerThan(names, 3) System.out.println(shortNames.size()) shortNames.each{ System.out.println(s) } Is the method now needed? Easier ways to use common methods? Are brackets required here?
  • 37. ...A Better Java... DSLs 2010 - 37 ©ASERT2006-2010 names = ["Ted", "Fred", "Jed", "Ned"] println names shortNames = names.findAll{ it.size() <= 3 } println shortNames.size() shortNames.each{ println it }
  • 38. ...A Better Java DSLs 2010 - 38 ©ASERT2006-2010 names = ["Ted", "Fred", "Jed", "Ned"] println names shortNames = names.findAll{ it.size() <= 3 } println shortNames.size() shortNames.each{ println it } ["Ted", "Fred", "Jed", "Ned"] 3 Ted Jed Ned
  • 39. Grapes / Grab DSLs 2010 - 39 ©ASERT2006-2010 // Google Collections example @Grab('com.google.collections:google-collections:1.0') import com.google.common.collect.HashBiMap HashBiMap fruit = [grape:'purple', lemon:'yellow', lime:'green'] assert fruit.lemon == 'yellow' assert fruit.inverse().yellow == 'lemon'
  • 40. Malleable Syntax DSLs 2010 - 40 ©ASERT2006-2010 order to buy 200.shares of GOOG { limitPrice 500 allOrNone false at the value of { qty * unitPrice - 100 } } take 2.pills of chloroquinine after 6.hours Groovy 1.8+ def "length of Spock's & his friends' names"() { expect: name.size() == length where: name | length "Spock" | 5 "Kirk" | 4 "Scotty" | 6 }
  • 41. 41 Copyright 2010 SpringSource. Copying, publishing or distributing without express written permission is prohibited. Real-life Groovy DSL examples • Anti-malaria drug resistance simulation • Human Resources employee skills representation • Insurance policies risk calculation engine • Loan acceptance rules engine for a financial platform • Mathematica-like lingua for nuclear safety simulations • Market data feeds evolution scenarios • and more...
  • 42. 42 Copyright 2010 SpringSource. Copying, publishing or distributing without express written permission is prohibited. DSLs within the Groovy Ecosystem • Testing DSLs – EasyB, Spock, JBehave, … • Builders – MarkupBuilder, SwingBuilder • Build Tool DSLs – AntBuilder, Gant, Gradle • Grails – BeanBuilder, Criteria, ConfigSlurper • GPars – DSL for concurrent activities • and more...
  • 44. ...Testing DSLs: Spock... ©ASERT2006-2010 • Testing framework for Java and Groovy • Highly expressive specification language – No assertion API – No record & replay mocking API – No superfluous annotations – Meaningful assert error messages – Extensible – Compatible with JUnit reportingwise @Speck @RunWith(Sputnik) class PublisherSubscriberSpeck { def "events are received by all subscribers"() { def pub = new Publisher() def sub1 = Mock(Subscriber) def sub2 = Mock(Subscriber) pub.subscribers << sub1 << sub2 when: pub.send("event") then: 1 * sub1.receive("event") 1 * sub2.receive("event") } } DSLs 2010 - 44
  • 45. ...Testing DSLs: Spock... ©ASERT2006-2010 import com.gargoylesoftware.htmlunit.WebClient import spock.lang.* import org.junit.runner.RunWith @Speck () @RunWith (Sputnik) class TestSimpBlogSpock { def page, subheadings, para, form, result @Unroll("When #author posts a #category blog with content '#content' it sho def "when creating a new blog entry"() { given: page = new WebClient().getPage('http://localhost:8080/postForm') form = page.getFormByName('post') when: form.getInputByName('title').setValueAttribute("$author was here (and s form.getSelectByName('category').getOptions().find { it.text == categor form.getSelectByName('author').getOptions().find { it.text == author }. form.getTextAreaByName('content').setText(content) result = form.getInputByName('btnPost').click() subheadings = result.getElementsByTagName('h3') para = result.getByXPath('//TABLE//TR/TD/P')[0] ... DSLs 2010 - 45
  • 46. ... Testing DSLs: Spock ©ASERT2006-2010 ... then: page.titleText == 'Welcome to SimpBlog' result.getElementsByTagName('h1').item(0).textContent.matches("Post.*: $au subheadings.item(1).textContent == "Category: $category" subheadings.item(2).textContent == "Author: $author" and: para.textContent == content where: author << ['Bart', 'Homer', 'Lisa'] category << ['Home', 'Work', 'Food'] content << ['foo', 'bar', 'baz'] } } // Optional use of 'and:' DSLs 2010 - 46
  • 47. ©ASERT2006-2010 Testing DSLs: EasyB… • Description: BDD, Rspec-like testing library narrative 'segment flown', { as_a 'frequent flyer' i_want 'to accrue rewards points for every segment I fly' so_that 'I can receive free flights for my dedication to the airline' } scenario 'segment flown', { given 'a frequent flyer with a rewards balance of 1500 points' when 'that flyer completes a segment worth 500 points' then 'that flyer has a new rewards balance of 2000 points' } scenario 'segment flown', { given 'a frequent flyer with a rewards balance of 1500 points', { flyer = new FrequentFlyer(1500) } when 'that flyer completes a segment worth 500 points', { flyer.fly(new Segment(500)) } then 'that flyer has a new rewards balance of 2000 points', { flyer.pointsBalance.shouldBe 2000 } } DSLs 2010 - 47
  • 48. …Testing DSLs: EasyB • When run will be marked as pending – perfect for ATDD ©ASERT2006-2010 scenario "Bart posts a new blog entry", { given "we are on the create blog entry page" when "I have entered 'Bart was here' as the title" and "I have entered 'Cowabunga Dude!' into the content" and "I have selected 'Home' as the category" and "I have selected 'Bart' as the author" and "I click the 'Create Post' button" then "I expect the entry to be posted" } DSLs 2010 - 48
  • 49. Testing DSLS: Cucumber... ©ASERT2006-2010 # language: en @newpost Feature: New Blog Post In order to create a new blog entry Bloggers should be able to select their name and category and enter text Scenario: New Posting Given we are on the create blog entry page When I have entered "Bart was here" as the title And I have entered "Cowabunga Dude!" as the content And I have selected "Home" from the "category" dropdown And I have selected "Bart" from the "author" dropdown And I click the 'Create Post' button Then I should see a heading message matching "Post.*: Bart was here.*" DSLs 2010 - 49
  • 50. ... Testing DSLS: Cucumber... ©ASERT2006-2010 DSLs 2010 - 50
  • 51. ...Testing DSLS: Cucumber ©ASERT2006-2010 import com.gargoylesoftware.htmlunit.WebClient this.metaClass.mixin(cuke4duke.GroovyDsl) Given ~/we are on the create blog entry page/, { -> page = new WebClient().getPage('http://localhost:8080/postForm') } When(~/I have entered "(.*)" as the title/) {String title -> form = page.getFormByName('post') form.getInputByName('title').setValueAttribute(title + ' (and so was Cucumber)') } When(~'I have entered "(.*)" as the content') {String content -> form.getTextAreaByName('content').setText(content) } When(~'I have selected "(.*)" from the "(.*)" dropdown') {String option, String name -> form.getSelectByName(name).getOptions().find { it.text == option }.setSelected(true) } When(~"I click the 'Create Post' button") { -> result = form.getInputByName('btnPost').click() } Then(~'I should see a heading message matching "(.*)"') {String pattern -> // ensureThat result.getElementsByTagName('h1').item(0).textContent.matches(pattern) assert result.getElementsByTagName('h1').item(0).textContent.matches(pattern) } DSLs 2010 - 51
  • 52. Grails Criteria DSLs 2010 - 52 ©ASERT2006-2010 // Account is a POJO in our domain/model def c = Account.createCriteria() def results = c { like("holderFirstName", "Fred%") and { between("balance", 500, 1000) eq("branch", "London") } maxResults(10) order("holderLastName", "desc") } // source: Grails doco: 5. Object Relational Mapping (GORM): 5.4.2 Criteria
  • 53. Grails Criteria Example DSLs 2010 - 53 ©ASERT2006-2010 // Book is a POJO in our domain/model def book = Book.findByTitle("The Stand") book = Book.findByTitleLike("Harry Pot%") book = Book.findByReleaseDateBetween( firstDate, secondDate ) book = Book.findByReleaseDateGreaterThan( someDate ) book = Book.findByTitleLikeOrReleaseDateLessThan( "%Something%", someDate ) books = Book.findAllByTitleLikeAndReleaseDateGreaterThan( "%Java%", new Date()-30) // source: Grails doco: 5. Object Relational Mapping (GORM): 5.4.1 Dynamic Finders
  • 54. Grails Bean Builder Example DSLs 2010 - 54 ©ASERT2006-2010 bb.beans { marge(Person) { name = "marge" husband = { Person p -> name = "homer" age = 45 props = [overweight:true, height:"1.8m"] } children = [bart, lisa] } bart(Person) { name = "Bart" age = 11 } lisa(Person) { name = "Lisa" age = 9 } } // source: 14. Grails and Spring: 14.3 Runtime Spring with the Beans DSL
  • 55. GPars... import static org.gparallelizer.actors.pooledActors.PooledActors.* // create a new actor that prints out received messages def console = actor { loop { react {message -> println message } } } // start the actor and send it a message console.start() console.send('Message') DSLs 2010 - 55
  • 56. ...GPars import static org.gparallelizer.dataflow.DataFlow.thread final def x = new DataFlowVariable() final def y = new DataFlowVariable() final def z = new DataFlowVariable() thread { z << ~x + ~y println "Result: ${~z}" } thread { x << 10 } thread { y << 5 } No race-conditions No deadlocks No live-locks Completely deterministic programs BEAUTIFUL code DSLs 2010 - 56
  • 57. Topics • Introduction Groovy DSL Features • A Week In The Life Of DSL INC • Summary • More Info DSLs 2010 - 57 ©ASERT2006-2010
  • 58. Groovy DSL Features DSLs 2010 - 58 ©ASERT2006-2010
  • 59. Import / Import Static • Imports • Static Imports DSLs 2010 - 59 ©ASERT2006-2010 @Grab('com.google.collections:google-collections:1.0') import com.google.common.collect.HashBiMap as HashMap def m = new HashMap() m.key = 'value' assert m.inverse().value == 'key' import static java.util.Calendar.getInstance as now println now().format('yyyy/MMM/dd') What Java gives us plus aliases
  • 60. Static Imports... DSLs 2010 - 60 ©ASERT2006-2010 import groovy.swing.SwingXBuilder import static java.awt.Color.* import static java.lang.Math.* def swing = new SwingXBuilder() def frame = swing.frame(size: [300, 300]) { graph(plots: [ [GREEN, {value -> sin(value)}], [BLUE, {value -> cos(value)}], [RED, {value -> tan(value)}] ]) }.show()  Java Static Imports Discussed later:  Builders  Closures
  • 61. ...Static Imports... DSLs 2010 - 61 ©ASERT2006-2010 import groovy.swing.SwingXBuilder import static java.awt.Color.* import static java.lang.Math.* def swing = new SwingXBuilder() def frame = swing.frame(size: [300, 300]) { graph(plots: [ [GREEN, {value -> sin(value)}], [BLUE, {value -> cos(value)}], [RED, {value -> tan(value)}] ]) }.show() Java gives us this
  • 62. ...Static Imports... DSLs 2010 - 62 ©ASERT2006-2010 import groovy.swing.SwingXBuilder import static java.awt.Color.* import static java.lang.Math.* def swing = new SwingXBuilder() def frame = swing.frame(size: [300, 300]) { graph(plots: [ [GREEN, {value -> sin(value)}], [BLUE, {value -> cos(value)}], [RED, {value -> tan(value)}] ]) }.show() Java gives us this
  • 63. ...Static Imports... DSLs 2010 - 63 ©ASERT2006-2010 Source: http://www.thedoghousediaries.com/?p=1406
  • 64. ...Static Imports DSLs 2010 - 64 ©ASERT2006-2010 import groovy.swing.SwingXBuilder import static java.lang.Math.* import static java.awt.Color.GREEN as Lime import static java.awt.Color.BLUE as Sky import static java.awt.Color.RED as Maraschino def swing = new SwingXBuilder() def frame = swing.frame(size: [300, 300]) { graph(plots: [ [Lime, {value -> sin(value)}], [Sky, {value -> cos(value)}], [Maraschino, {value -> tan(value)}] ]) }.show() Java doesn‟t give us this!
  • 65. Literal Syntax Conventions • Lists – Special syntax for list literals – Additional common methods (operator overloading) • Maps – Special syntax for map literals – Additional common methods • Ranges – Special syntax for various kinds of ranges DSLs 2010 - 65 ©ASERT2006-2010 def list = [3, new Date(), 'Jan'] assert list + list == list * 2 def map = [a: 1, b: 2] assert map['a'] == 1 && map.b == 2 def letters = 'a'..'z' def numbers = 0..<10
  • 66. Literal Syntax Conventions in DSLs DSLs 2010 - 66 ©ASERT2006-2010 import static java.util.Calendar.getInstance as getNow import static java.util.Calendar.* def discount = [normal:0, silver:5, gold:10] def roomrate = [weekday:150, weekend:95] for (level in ['normal', 'silver', 'gold']) { def multiplier = 1 - discount[level] / 100 print 'Your room rate is: ' if (now[DAY_OF_WEEK] in MONDAY..FRIDAY) println roomrate.weekday * multiplier else println roomrate.weekend * multiplier } Your room rate is: 150 Your room rate is: 142.50 Your room rate is: 135.0  Literal list & map syntax  Ranges Also  Static import aliases  BigDecimal arithmetic
  • 67. Compact Syntax... • Java • Groovy DSLs 2010 - 67 ©ASERT2006-2010 public class BuySharesJava { public static void buyShares(int qty, String name) { // business logic here ... System.out.println("buying " + qty + " shares of " + name); } public static void main(String[] args) { buyShares(3, "BHP"); } } def buyShares(qty, name) { // business logic here ... println "buying $qty shares of $name" } buyShares 3, 'BHP'  Script syntax  Conventions for visibility  GDK methods: println  Implicit typing  GString interpolation  Optional brackets & „;‟
  • 68. ...Compact Syntax... • Java • Groovy DSLs 2010 - 68 ©ASERT2006-2010 import java.util.Date; import java.util.HashMap; import java.util.Map; public class ProcessCustomerJava { public static void printDetails(Map<String, String> cust) { System.out.println("Details as at: " + new Date()); String first = cust.get("first"); System.out.println("First name: " + first); String last = cust.get("last"); System.out.println("Last name: " + (last != null ? last : "unknown")); } public static void main(String[] args) { Map<String, String> details = new HashMap<String, String>(); details.put("first", "John"); details.put("last", "Smith"); printDetails(details); } } def printDetails(cust) { println """Details as at: ${new Date()} First name: $cust.first Last name: ${cust.last ?: 'unknown'}""" } printDetails first: 'John', last: 'Smith'  Script syntax, Named params, Extra imports  Conventions for visibility, GDK methods  Implicit typing, Multi-line GString  Elvis operator, Optional brackets & „;‟
  • 69. ...Compact Syntax • Java • Groovy DSLs 2010 - 69 ©ASERT2006-2010 import java.util.Date; public class ProcessStrongCustomerJava { public static void printDetails(CustomerJ cust) { System.out.println("Details as at: " + new Date()); String first = cust.getFirst(); System.out.println("First name: " + first); String last = cust.getLast(); System.out.println("Last name: " + (last != null ? last : "unknown")); } public static void main(String[] args) { CustomerJ c = new CustomerJ(); c.setFirst("John"); c.setLast("Smith"); printDetails(c); } } class CustomerJ { private String first; private String last; public String getFirst() { return first; } public void setFirst(String first) { this.first = first; } public String getLast() { return last; } public void setLast(String last) { this.last = last; } } class CustomerG { String first, last } def printDetails(cust) { println """Details as at: ${new Date()} First name: $cust.first Last name: ${cust.last ?: 'unknown'}""" } printDetails new CustomerG(first: 'John', last: 'Smith') Plus:  Named params for constructors  JavaBean conventions  Leverage Duck Typing
  • 70. Using With Example DSLs 2010 - 70 ©ASERT2006-2010 letters = ['a', 'b', 'c'] range = 'b'..'d' letters.with { add 'd' remove 'a' } assert letters == range map = [a:10, b:4, c:7] map.with { assert (a + b) / c == 2 } Just normal methods for ArrayList here
  • 71. Closures... • Traditional mainstream languages – Data can be stored in variables, passed around, combined in structured ways to form more complex data; code stays put where it is defined • Languages supporting closures – Data and code can be stored in variables, passed around, combined in structured ways to form more complex algorithms and data; functional coding style DSLs 2010 - 71 ©ASERT2006-2010 doubleNum = { num -> num * 2 } println doubleNum(3) // => 6 processThenPrint = { num, closure -> num = closure(num); println "num is $num" } processThenPrint(3, doubleNum) // => num is 6 processThenPrint(10) { it / 2 } // => num is 5
  • 72. ...Closures... DSLs 2010 - 72 ©ASERT2006-2010 int myConst = 4 def multiplier = { number -> number * myConst } assert multiplier(10) == 40 Closure other = { it + myConst } assert other.call(10) == 14 def twice = { it * 2 } // anonymous functions assert twice(10) == 20 // implemented as Closures Name
  • 73. ...Closures... DSLs 2010 - 73 ©ASERT2006-2010 int myConst = 4 def multiplier = { number -> number * myConst } assert multiplier(10) == 40 Closure other = { it + myConst } assert other.call(10) == 14 def twice = { it * 2 } // anonymous functions assert twice(10) == 20 // implemented as Closures Code
  • 74. ...Closures... DSLs 2010 - 74 ©ASERT2006-2010 int myConst = 4 def multiplier = { number -> number * myConst } assert multiplier(10) == 40 Closure other = { it + myConst } assert other.call(10) == 14 def twice = { it * 2 } // anonymous functions assert twice(10) == 20 // implemented as Closures Parameter(s) Default parameter
  • 75. ...Closures... DSLs 2010 - 75 ©ASERT2006-2010 int myConst = 4 def multiplier = { number -> number * myConst } assert multiplier(10) == 40 Closure other = { it + myConst } assert other.call(10) == 14 def twice = { it * 2 } // anonymous functions assert twice(10) == 20 // implemented as Closures Call closure Alternative syntax
  • 76. ...Closures... DSLs 2010 - 76 ©ASERT2006-2010 int myConst = 4 def multiplier = { number -> number * myConst } assert multiplier(10) == 40 Closure other = { it + myConst } assert other.call(10) == 14 def twice = { it * 2 } // anonymous functions assert twice(10) == 20 // implemented as Closures Free variable
  • 77. ...Closures... DSLs 2010 - 77 ©ASERT2006-2010 int myConst = 4 def multiplier = { number -> number * myConst } assert multiplier(10) == 40 Closure other = { it + myConst } assert other.call(10) == 14 def twice = { it * 2 } // anonymous functions assert twice(10) == 20 // implemented as Closures Bound to environment/context when called
  • 78. Closures in DSLs... DSLs 2010 - 78 ©ASERT2006-2010 import static java.util.Calendar.* def cart1 = [ [qty: 3, unitPrice: 50.0, gift: true, name: 'Groovy in Action Book'], [qty: 1, unitPrice: 40.0, gift: false, name: 'Grails in Action eBook'], [qty: 2, unitPrice: 25.0, gift: true, name: 'Avatar DVD'] ] def cart2 = [ [qty: 1, unitPrice: 50.0, gift: true, name: 'Groovy in Action Book'], [qty: 2, unitPrice: 30.0, gift: false, name: 'Avatar 3D DVD'] ] // ... Nothing special here. Just a list of maps.
  • 79. ... Closures in DSLs... DSLs 2010 - 79 ©ASERT2006-2010 // ... def noDiscount = { it.unitPrice * it.qty } def tenPercentOffAll = { it.unitPrice * it.qty * 0.9 } def tenPercentOffGifts = { it.with { unitPrice * qty * (gift ? 0.9 : 1.0) } } def tenPercentOffOverForty = { it.with { unitPrice * qty * (unitPrice > 40 ? 0.9 : 1.0) } } def tenPercentOffBooks = { it.with { unitPrice * qty * (name.contains('Book') ? 0.9 : 1.0) } } def tenPercentOffDVDs = { it.with { unitPrice * qty * (name.contains('DVD') ? 0.9 : 1.0) } } def buyThreeGetOneFree = { it.with { unitPrice * (qty % 3 + 2 * qty.intdiv(3)) } } // ... Closure Name
  • 80. ... Closures in DSLs... DSLs 2010 - 80 ©ASERT2006-2010 // ... def noDiscount = { it.unitPrice * it.qty } def tenPercentOffAll = { it.unitPrice * it.qty * 0.9 } def tenPercentOffGifts = { it.with { unitPrice * qty * (gift ? 0.9 : 1.0) } } def tenPercentOffOverForty = { it.with { unitPrice * qty * (unitPrice > 40 ? 0.9 : 1.0) } } def tenPercentOffBooks = { it.with { unitPrice * qty * (name.contains('Book') ? 0.9 : 1.0) } } def tenPercentOffDVDs = { it.with { unitPrice * qty * (name.contains('DVD') ? 0.9 : 1.0) } } def buyThreeGetOneFree = { it.with { unitPrice * (qty % 3 + 2 * qty.intdiv(3)) } } // ... Closure Code
  • 81. ... Closures in DSLs DSLs 2010 - 81 ©ASERT2006-2010 // ... def specials = [noDiscount, tenPercentOffAll, tenPercentOffGifts, tenPercentOffOverForty, tenPercentOffBooks, tenPercentOffDVDs, buyThreeGetOneFree] [ SUNDAY..SATURDAY, [cart1, cart2] ].combinations().each { day, cart -> printf "%d %2.2fn", day, cart.sum(specials[day-1]) } 1 240.00 2 216.00 3 220.00 4 225.00 5 221.00 6 235.00 7 190.00 1 110.00 2 99.00 3 105.00 4 105.00 5 105.00 6 104.00 7 110.00
  • 82. Operator Overloading Example DSLs 2010 - 82 ©ASERT2006-2010 BigDecimal a = new BigDecimal(3.5d); BigDecimal b = new BigDecimal(4.0d); assert a.multiply(b).compareTo(new BigDecimal(14.0d)) == 0; assert a.multiply(b).equals(new BigDecimal(14.0d).setScale(1)); def c = 3.5, d = 4.0 assert c * d == 14.0 • Java • Groovy
  • 83. Groovy Lab Example DSLs 2010 - 83 ©ASERT2006-2010 // require GroovyLab import static org.math.array.Matrix.* import static org.math.plot.Plot.* def A = rand(10,3) // random Matrix of 10 rows and 3 columns def B = fill(10,3,1.0) // one Matrix of 10 rows and 3 columns def C = A + B // support for matrix addition with "+" or "-" def D = A - 2.0 // support for number addition with "+" or "-" def E = A * B // support for matrix multiplication or division def F = rand(3,3) def G = F**(-1) // support for matrix power (with integers only) println A // display Matrix content plot("A",A,"SCATTER") // plot Matrix values as ScatterPlot def M = rand(5,5) + id(5) // Eigenvalues decomposition println "M=n" + M println "V=n" + V(M) println "D=n" + D(M) println "M~n" + (V(M) * D(M) * V(M)**(-1))
  • 84. ©ASERT2006-2010 Better Control Structures: Switch Poker… suits = 'SHDC' ranks = '23456789TJQKA' suit = { String card -> suits.indexOf(card[1]) } rank = { String card -> ranks.indexOf(card[0]) } rankSizes = { List cards -> cards.groupBy(rank).collect{ k, v -> v.size() }.sort() } rankValues = { List cards -> cards.collect{ rank(it) }.sort() } // ... 8C TS KC 9H 4S 7D 2S 5D 3S AC hand1 hand2 println rankSizes(["7S", "7H", "2H", "7D", "AH"]) // => [1, 1, 3] DSLs 2010 - 84
  • 85. ©ASERT2006-2010 …Better Control Structures: Switch Poker… // ... flush = { List cards -> cards.groupBy(suit).size() == 1 } straight = { def v = rankValues(it); v == v[0]..v[0]+4 } straightFlush = { List cards -> straight(cards) && flush(cards) } fourOfAKind = { List cards -> rankSizes(cards) == [1, 4] } fullHouse = { List cards -> rankSizes(cards) == [2, 3] } threeOfAKind = { List cards -> rankSizes(cards) == [1, 1, 3] } twoPair = { List cards -> rankSizes(cards) == [1, 2, 2] } pair = { List cards -> rankSizes(cards) == [1, 1, 1, 2] } // ... DSLs 2010 - 85
  • 86. ©ASERT2006-2010 … Better Control Structures: Switch Poker // ... def rankHand(List cards) { switch (cards) { case straightFlush : return 9 case fourOfAKind : return 8 case fullHouse : return 7 case flush : return 6 case straight : return 5 case threeOfAKind : return 4 case twoPair : return 3 case pair : return 2 default : return 1 } } // ... DSLs 2010 - 86
  • 87. Type Transformation Example DSLs 2010 - 87 ©ASERT2006-2010 class InventoryItem { def weight, name InventoryItem(Map m) { this.weight = m.weight; this.name = m.name } InventoryItem(weight, name) { this.weight = weight; this.name = name } InventoryItem(String s) { s.find(/weight=(d*)/) { all, w -> this.weight = w } s.find(/name=(.*)/) { all, n -> this.name = n } } } def room = [:] def gold = [weight:50, name:'Gold'] as InventoryItem def emerald = [10, 'Emerald'] as InventoryItem def dagger = ['weight=5, name=Dagger'] as InventoryItem room.contents = [gold, emerald, dagger] room.contents.each{ println it.dump() }
  • 88. DSLs 2010 - 88 Groovy DSL Features ©ASERT2006-2010
  • 89. Metaprogramming • Runtime Metaprogramming – Categories including DGM – invokeMethod, methodMissing, getProperty – GroovyInterceptable – ExpandoMetaClass DSLs 2010 - 89 ©ASERT2006-2010
  • 90. DSLs 2010 - 90 ©ASERT2006-2010 Builders… <html> <head> <title>Hello</title> </head> <body> <ul> <li>world 1</li> <li>world 2</li> <li>world 3</li> <li>world 4</li> <li>world 5</li> </ul> </body> </html> import groovy.xml.* def page = new MarkupBuilder() page.html { head { title 'Hello' } body { ul { for (count in 1..5) { li "world $count" } } } } • MarkupBuilder
  • 91. ...Builders… DSLs 2010 - 91 ©ASERT2006-2010 def colors = ['red','darkOrange','blue','darkGreen'] (0..3).each { index -> star( cx: 50 + (index*110), cy: 50, or: 40, ir: 15, borderColor: 'black', count: 2+index, fill: colors[index] ) star( cx: 50 + (index*110), cy: 140, or: 40, ir: 15, borderColor: 'black‘, count: 7+index, fill: colors[index] ) } • GraphicsBuilder
  • 92. …Builders new AntBuilder().with { echo(file:'Temp.java', ''' class Temp { public static void main(String[] args) { System.out.println("Hello"); } } ''') javac(srcdir:'.', includes:'Temp.java', fork:'true') java(classpath:'.', classname:'Temp', fork:'true') echo('Done') } // => // [javac] Compiling 1 source file // [java] Hello // [echo] Done ©ASERT2006-2010 DSLs 2010 - 92
  • 93. 93 Copyright 2010 SpringSource. Copying, publishing or distributing without express written permission is prohibited. A builder for HR
  • 94. DSLs 2010 - 94 ©ASERT2006-2010 ExpandoMetaClass… import static java.lang.Character.isUpperCase String.metaClass.swapCase = { delegate.collect { ch -> isUpperCase(ch as char) ? ch.toLowerCase() : ch.toUpperCase() }.join() } println new Date().toString().swapCase() // => sUN nOV 09 17:30:06 est 2008 List.metaClass.sizeDoubled = {-> delegate.size() * 2 } LinkedList list = [] list << 1 list << 2 assert 4 == list.sizeDoubled()
  • 95. DSLs 2010 - 95 ©ASERT2006-2010 …ExpandoMetaClass class Person { String name } class MortgageLender { def borrowMoney() { "buy house" } } def lender = new MortgageLender() Person.metaClass.buyHouse = lender.&borrowMoney def p = new Person() assert "buy house" == p.buyHouse()
  • 96. Adding your own control structures • Thread enhancement DSLs 2010 - 96 ROCK! . 25 . 33 . 34 . 35 . 36 .. 131 .. 134 .. 137 .. 138 .. 139 ... 232 ... 234 ... 237 ... 238 ... 239 .... 334 .... 336 .... 337 .... 338 .... 339 import java.util.concurrent.locks.ReentrantLock import static System.currentTimeMillis as now def startTime = now() ReentrantLock.metaClass.withLock = { critical -> lock() try { critical() } finally { unlock() } } def lock = new ReentrantLock() def worker = { threadNum -> 4.times { count -> lock.withLock { print " " * threadNum print "." * (count + 1) println " ${now() - startTime}" } Thread.sleep 100 } } 5.times { Thread.start worker.curry(it) } println "ROCK!" Source: http://chrisbroadfoot.id.au/articles/2008/08/06/groovy-threads
  • 97. EMC DSL… DSLs 2010 - 97 ©ASERT2006-2010
  • 98. ...EMC Neo4j DSL… DSLs 2010 - 98 ©ASERT2006-2010 @GrabResolver(name= 'neo4j-public-repo', root= 'http://m2.neo4j.org') @Grab('org.neo4j:neo4j-kernel:1.1.1') import org.neo4j.kernel.EmbeddedGraphDatabase import org.neo4j.graphdb.* // an enum helper enum MyRelationships implements RelationshipType { knows } // some optional syntactic sugar using EMC DSL Node.metaClass { propertyMissing { String name, val -> delegate.setProperty(name, val) } propertyMissing { String name -> delegate.getProperty(name) } methodMissing { String name, args -> delegate.createRelationshipTo(args[0], MyRelationships."$name") } } Relationship.metaClass { propertyMissing { String name, val -> delegate.setProperty(name, val) } propertyMissing { String name -> delegate.getProperty(name) } } // ...
  • 99. …EMC Neo4j DSL DSLs 2010 - 99 ©ASERT2006-2010 // ... def graphDb = new EmbeddedGraphDatabase("graphdb") def tx = graphDb.beginTx() def firstNode, secondNode, relationship try { firstNode = graphDb.createNode() secondNode = graphDb.createNode() relationship = firstNode.knows(secondNode) firstNode.message = "Hello," secondNode.message = "world!" relationship.message = "brave Neo4j" tx.success() } finally { tx.finish() println "$firstNode.message $relationship.message $secondNode.message" // => Hello, brave Neo4j world! graphDb.shutdown() }
  • 100. Game example... DSLs 2010 - 100 ©ASERT2006-2010 // Trying out the game DSL idea by Sten Anderson from: // http://blogs.citytechinc.com/sanderson/?p=92 class GameUtils { static VOWELS = ['a', 'e', 'i', 'o', 'u'] static listItems(things) { def result = '' things.eachWithIndex{ thing, index -> if (index > 0) { if (index == things.size() - 1) result += ' and ' else if (index < things.size() - 1) result += ', ' } result += "${thing.toLowerCase()[0] in VOWELS ? 'an' : 'a'} $thing } result ?: 'nothing' } } import static GameUtils.* ...
  • 101. ...Game example... DSLs 2010 - 101 ©ASERT2006-2010 ... class Room { def description def contents = [] } ...
  • 102. ...Game example... DSLs 2010 - 102 ©ASERT2006-2010 ... class Player { def currentRoom def inventory = [] void look() { println "You are in ${currentRoom?.description?:'the void'} which contains ${listItems(currentRoom?.contents)}" } void inv() { println "You are holding ${listItems(inventory)}" } void take(item) { if (currentRoom?.contents?.remove(item)) { inventory << item println "You took the $item" } else { println "I see no $item here" } } ...
  • 103. ...Game example... DSLs 2010 - 103 ©ASERT2006-2010 ... void drop(item) { if (inventory?.remove(item)) { currentRoom?.contents << item println "You dropped the $item" } else { println "You don't have the $item" } } def propertyMissing(String name) { if (metaClass.respondsTo(this, name)) { this."$name"() } name } } ...
  • 104. ...Game example... DSLs 2010 - 104 ©ASERT2006-2010 ... Room plainRoom = new Room(description:'a plain white room', contents:['dagger', 'emerald', 'key']) Player player = new Player(currentRoom:plainRoom) player.with { inv look take dagger inv look take emerald inv look take key drop emerald inv look } assert player.inventory == ['dagger', 'key'] ...
  • 105. ...Game example DSLs 2010 - 105 ©ASERT2006-2010 ... // now try some error conditions plainRoom.description = null player.with { drop gold take gold drop emerald take emerald take emerald look }
  • 106. GEP3 example... DSLs 2010 - 106 ©ASERT2006-2010 Object.metaClass.please = { clos -> clos(delegate) } Object.metaClass.the = { clos -> delegate[1](clos(delegate[0])) } show = { thing -> [thing, { println it }] } square_root = { Math.sqrt(it) } given = { it } given 100 please show the square_root // ==> 10.0
  • 107. ...GEP3 example... DSLs 2010 - 107 ©ASERT2006-2010 Object.metaClass.of = { delegate[0](delegate[1](it)) } Object.metaClass.the = { clos -> [delegate[0], clos] } show = [{ println it }] square_root = { Math.sqrt(it) } please = { it } please show the square_root of 100 // ==> 10.0
  • 108. ...GEP3 example... DSLs 2010 - 108 ©ASERT2006-2010 show = { println it } square_root = { Math.sqrt(it) } def please(action) { [the: { what -> [of: { n -> action(what(n)) }] }] } please show the square_root of 100 // ==> 10.0 Inspiration for this example came from …
  • 109. ...GEP3 example... DSLs 2010 - 109 ©ASERT2006-2010 // Japanese DSL using GEP3 rules Object.metaClass.を = Object.metaClass.の = { clos -> clos(delegate) } まず = { it } 表示する = { println it } 平方根 = { Math.sqrt(it) } まず 100 の 平方根 を 表示する // First, show the square root of 100 // => 10.0 // source: http://d.hatena.ne.jp/uehaj/20100919/1284906117 // http://groovyconsole.appspot.com/edit/241001
  • 110. ...GEP3 example DSLs 2010 - 110 ©ASERT2006-2010 // source: http://d.hatena.ne.jp/uehaj/20100919/1284906117 // http://groovyconsole.appspot.com/edit/241001
  • 111. Medical Prescription DSL… DSLs 2010 - 111 ©ASERT2006-2010 class Drug { String name String toString() { name } } class Measure { Number number String unit, units String toString() { number == 1 ? "1 $unit" : "$number $units" } } class Quantity extends Measure {} class Duration extends Measure {} // ... http://groovyconsole.appspot.com/script/240001 by glaforge/Mihai Cazacu
  • 112. …Medical Prescription DSL… DSLs 2010 - 112 ©ASERT2006-2010 // ... // ---- DSL implementation ------------------------------ Integer.metaClass.getHour = {-> 1.hours } Integer.metaClass.getPill = {-> 1.pills } Integer.metaClass.getHours = {-> new Duration(number: delegate, unit: 'hour', units: 'hours') } Integer.metaClass.getPills = {-> new Quantity(number: delegate, unit: 'pill', units: 'pills') } // use the script binding for storing new drugs available as variables binding = new Binding() { def getVariable(String drug) { new Drug(name: drug) } } take = {Quantity quantity -> ['of': {Drug drug -> ['after': {Duration duration -> println "Take $quantity of $drug afer $duration" }] }] } // ...
  • 113. …Medical Prescription DSL DSLs 2010 - 113 ©ASERT2006-2010 // ... // ---- DSL ------------------------------- take 2.pills of chloroquinine after 6.hours http://groovyconsole.appspot.com/script/240001 by glaforge/Mihai Cazacu
  • 114. Groovy DSL Features DSLs 2010 - 114 ©ASERT2006-2010 Transformation
  • 115. AST Transformations • With metaprogramming, Groovy‟s able to modify the behaviour of programs... at runtime • AST Transformations provide a compile- time approach to modify programs – AST: Abstract Syntax Tree – Ability to change what‟s being compiled at compile- time! • No runtime impact! • Lets you change the semantics of your programs! • Nice way of implementing patterns and removing boiler-plate technical code • Two kinds of transformations: global & local DSLs 2010 - 115
  • 116. Better Design Patterns: Immutable... • Java Immutable Class – As per Joshua Bloch Effective Java DSLs 2010 - 116 ©ASERT2006-2010 public final class Punter { private final String first; private final String last; public String getFirst() { return first; } public String getLast() { return last; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((first == null) ? 0 : first.hashCode()); result = prime * result + ((last == null) ? 0 : last.hashCode()); return result; } public Punter(String first, String last) { this.first = first; this.last = last; } // ... // ... @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Punter other = (Punter) obj; if (first == null) { if (other.first != null) return false; } else if (!first.equals(other.first)) return false; if (last == null) { if (other.last != null) return false; } else if (!last.equals(other.last)) return false; return true; } @Override public String toString() { return "Punter(first:" + first + ", last:" + last + ")"; } }
  • 117. ...Better Design Patterns: Immutable... • Java Immutable Class – As per Joshua Bloch Effective Java DSLs 2010 - 117 ©ASERT2006-2010 public final class Punter { private final String first; private final String last; public String getFirst() { return first; } public String getLast() { return last; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((first == null) ? 0 : first.hashCode()); result = prime * result + ((last == null) ? 0 : last.hashCode()); return result; } public Punter(String first, String last) { this.first = first; this.last = last; } // ... // ... @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Punter other = (Punter) obj; if (first == null) { if (other.first != null) return false; } else if (!first.equals(other.first)) return false; if (last == null) { if (other.last != null) return false; } else if (!last.equals(other.last)) return false; return true; } @Override public String toString() { return "Punter(first:" + first + ", last:" + last + ")"; } } boilerplate
  • 118. ...Better Design Patterns: Immutable DSLs 2010 - 118 ©ASERT2006-2010 @Immutable class Punter { String first, last }
  • 119. Spock DSLs 2010 - 119 ©ASERT2006-2010 def "length of Spock's & his friends' names"() { expect: name.size() == length where: name | length "Spock" | 5 "Kirk" | 4 "Scotty" | 6 }
  • 120. AST Builder • Numerous approaches, still evolving. “From code” approach: • Produces: def result = new AstBuilder().buildFromCode { println "Hello World" } BlockStatement -> ReturnStatement -> MethodCallExpression -> VariableExpression("this") -> ConstantExpression("println") -> ArgumentListExpression -> ConstantExpression("Hello World") DSLs 2010 - 120
  • 121. ©ASERT2006-2010 EasyB… • Description: BDD, Rspec-like testing library narrative 'segment flown', { as_a 'frequent flyer' i_want 'to accrue rewards points for every segment I fly' so_that 'I can receive free flights for my dedication to the airline' } scenario 'segment flown', { given 'a frequent flyer with a rewards balance of 1500 points' when 'that flyer completes a segment worth 500 points' then 'that flyer has a new rewards balance of 2000 points' } scenario 'segment flown', { given 'a frequent flyer with a rewards balance of 1500 points', { flyer = new FrequentFlyer(1500) } when 'that flyer completes a segment worth 500 points', { flyer.fly(new Segment(500)) } then 'that flyer has a new rewards balance of 2000 points', { flyer.pointsBalance.shouldBe 2000 } } DSLs 2010 - 121
  • 122. ©ASERT2006-2010 …EasyB • Description: BDD, Rspec-like testing library examples "The number #{number}' should be converted to #{romanNumerals}", { number = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] romanNumerals = ["I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X"] } scenario "Converting #number into the roman numeral #romanNumerals", { given "the number #number", { theNumber = number } when "the system converts this number to the roman numeral equivalent", { theConvertedNumber = RomanNumerals.fromInteger(theNumber) } then "the result should be #romanNumerals", { theConvertedNumber.shouldBe romanNumerals } } DSLs 2010 - 122 Source: http://www.wakaleo.com/blog/285-example-driven-testing-with-easyb
  • 123. EasyB Preprocessing... • EasyB supports either of these: • But we would like this: given ("some data") { println '... setting expectations' } Source: http://hamletdarcy.blogspot.com/2010/02/groovy-antlr-plugins-for-better-dsls.html given "some data", { println '... setting expectations' } given "some data" { println '... setting expectations' } DSLs 2010 - 123 But also consider Command Expressions (GEP-3) for Groovy1.8+.
  • 124. ...EasyB Preprocessing... String addCommas(text) { def pattern = ~/(.*)(given|when|then) "([^"]*(.[^"]*)*)" {(.*)/ def replacement = /$1$2 "$3", {$4/ (text =~ pattern).replaceAll(replacement) } Source: http://hamletdarcy.blogspot.com/2010/02/groovy-antlr-plugins-for-better-dsls.html DSLs 2010 - 124 Adds the comma in where required before Groovy sees it.
  • 125. ...EasyB Preprocessing class SourceModifierParserPlugin extends AntlrParserPlugin { Reduction parseCST(SourceUnit sourceUnit, Reader reader) throws CompilationFailedException { def text = addCommas(reader.text) StringReader stringReader = new StringReader(text) super.parseCST(sourceUnit, stringReader) } } def parserPluginFactory = new ParserPluginFactory() { ParserPlugin createParserPlugin() { new SourceModifierParserPlugin() } } def conf = new CompilerConfiguration(pluginFactory: parserPluginFactory) def binding = ... def shell = new GroovyShell(binding, conf) Source: http://hamletdarcy.blogspot.com/2010/02/groovy-antlr-plugins-for-better-dsls.html DSLs 2010 - 125 But use with restraint as error messages will be with respect to converted source code not original.
  • 126. GParsec... • Miniature parsers • Parser Combinators def isLetter = { ch -> return Character.isLetter(ch) } def isDigit = { ch -> return Character.isDigit(ch) } def letter = satisfyC(isLetter) def digit = satisfyC(isDigit) def letterAndDigit = seqC(letter, digit) assert letterAndDigit('a123###') == ['a', '1'] def identifier = seqC(identifierStart, noneOrMoreC(identifierRest)) DSLs 2010 - 126
  • 127. ...GParsec... • BNF • Groovy definitions variableDeclarator : identifier ( '=' expression )? variableDefinitions : variableDeclarator ( ',' variableDeclarator)* def variableDeclarator = seqC(identifier, optionalC(seqC(assign, expression))) def variableDefinitions = seqC(variableDeclarator, noneOrMoreC(seqC(comma, variableDeclarator))) DSLs 2010 - 127
  • 128. ...GParsec • satisfyC: combinator consumes a single input when its predicate succeeds • altC (alt3C, altCs): is the choice combinator. Given two parsers it only looks at its second alternative if the first has not consumed any input - regardless of the final value • seqC (seq3C, seqCs): is the sequencing combinator. It runs two parsers in succession and if successful, returns the result of the two parsers • noneOrMoreC, oneOrMoreC: applies a parser zero or more times to an input stream. The result from each application of the parser are returned in a list • optionalC: The combinator optionalC may succeed in parsing some input. It always returns success. DSLs 2010 - 128
  • 129. Using External Parsers DSLs 2010 - 129 ©ASERT2006-2010 import static com.mdimension.jchronic.Chronic.parse def span = parse("tomorrow at 5pm") println span.endCalendar.format('yyyy-MM-dd HH:mm') // => 2010-05-19 17:00
  • 130. Topics • Introduction • Groovy DSL Features A Week In The Life Of DSL INC • Summary • More Info DSLs 2010 - 130 ©ASERT2006-2010
  • 131. MONDAY DSLs 2010 - 131 ©ASERT2006-2010
  • 132. Currency DSL… DSLs 2010 - 132 ©ASERT2006-2010 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 Customer
  • 133. …Currency DSL... DSLs 2010 - 133 ©ASERT2006-2010 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 == 57
  • 134. …Currency DSL… DSLs 2010 - 134 ©ASERT2006-2010 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 Customer
  • 135. ...Currency DSL... DSLs 2010 - 135 ©ASERT2006-2010 class CoinMath { static multiply(Integer self, Coin c) { self * c.value } } use (CoinMath) { assert 2 * quarter + 1 * nickel + 2 * penny == 57 } // EMC equivalent Integer.metaClass.multiply = { Coin c -> delegate * c.value } assert 2 * quarter + 1 * nickel + 2 * penny == 57
  • 136. …Currency DSL… DSLs 2010 - 136 ©ASERT2006-2010 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 Customer
  • 137. ... Currency DSL DSLs 2010 - 137 ©ASERT2006-2010 class 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 == 57
  • 138. TUESDAY DSLs 2010 - 138 ©ASERT2006-2010 http://www.google.com/imgres?imgurl=http://i733.photobucket.com/albums/ww336/snake1953/Day_of_Week/Tue/HappyTuesda
  • 139. Business logic DSL… DSLs 2010 - 139 ©ASERT2006-2010 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.
  • 140. …Business logic DSL… • Options – Embedded Groovy – Regex parser – Antlr parser – Split / StringTokenizer – Parser combinators • … Many options, details not important … DSLs 2010 - 140 ©ASERT2006-2010
  • 141. … Business logic DSL … DSLs 2010 - 141 ©ASERT2006-2010 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.
  • 142. … Business logic DSL … DSLs 2010 - 142 ©ASERT2006-2010 def shell = new GroovyShell() shell.evaluate(''' price = 3 quantity = 10 total = price * quantity assert total == 30 ''')
  • 143. … Business logic DSL … DSLs 2010 - 143 ©ASERT2006-2010 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.
  • 144. … Business logic DSL … DSLs 2010 - 144 ©ASERT2006-2010 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.") } // ... } Please also see the ArithmeticShell which is included under examples in the Groovy distribution
  • 145. … Business logic DSL DSLs 2010 - 145 ©ASERT2006-2010 def shell = new CustomerShell() shell.evaluate(''' price = 3 quantity = 10 total = price * quantity assert total == 30 ''')
  • 146. WEDNESDAY DSLs 2010 - 146 ©ASERT2006-2010 http://www.crystalscomments.com/1/days/wednesday/animals/006.jpg
  • 147. Stock Exchange Order DSL… DSLs 2010 - 147 ©ASERT2006-2010 Example inspired from DSLs in Action: http://www.manning.com/ghosh/ 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 Customer
  • 148. … Stock Exchange Order DSL… DSLs 2010 - 148 ©ASERT2006-2010 // ----- Implementation of the Fluent API ----- enum Action { Buy, Sell } class Order { def security def quantity, limitPrice boolean allOrNone def valueCalculation Action action def buy(Integer quantity, String security) { this.quantity = quantity this.security = security this.action = Action.Buy return this } def sell(Integer quantity, String security) { this.quantity = quantity this.security = security this.action = Action.Sell return this } def limitPrice(Integer limit) { this.limitPrice = limit; return this } def allOrNone(boolean allOrNone) { this.allOrNone = allOrNone; return this } def valueAs(Closure valueCalculation) { this.valueCalculation = valueCalculation; return this } String toString() { "$action $quantity shares of $security at valuation of ${valueCalculation(quantity, limitPrice)}" } } println new Order() .sell(150, "IBM") .limitPrice(300) .allOrNone(true) .valueAs{ qty, unitPrice -> qty * unitPrice - 100 } println new Order() .buy(200, "GOOG") .limitPrice(200) .allOrNone(true) .valueAs{ qty, unitPrice -> qty * unitPrice - 500 } Sell 150 shares of IBM at valuation of 44900 Buy 200 shares of GOOG at valuation of 39500
  • 149. …Stock Exchange Order DSL… DSLs 2010 - 149 ©ASERT2006-2010 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 Customer
  • 150. …Stock Exchange Order DSL… DSLs 2010 - 150 ©ASERT2006-2010 class Order { def security def quantity def limitPrice def allOrNone def value def 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] } ...
  • 151. …Stock Exchange Order DSL… DSLs 2010 - 151 ©ASERT2006-2010 ... def getTo() { this } def valueAs(closure) { value = closure(quantity, limitPrice) } } class Security { String name } class Quantity { Security security Integer quantity } 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, it's 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() newOrder.to.buy(100.shares.of(IBM)) { limitPrice 300 allOrNone true valueAs { qty, unitPrice -> qty * unitPrice - 200 } } newOrder.to.sell 200.shares.of(GOOG), { limitPrice 200 allOrNone false valueAs { qty, unitPrice -> qty * unitPrice - 500 } }
  • 152. …Stock Exchange Order DSL… DSLs 2010 - 152 ©ASERT2006-2010 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 Customer
  • 153. …Stock Exchange Order DSL… DSLs 2010 - 153 ©ASERT2006-2010 class Order { ... def allOrNone def valueCalculation def bs ... private buySell(securityQuantity, closure) { ... valueCalculation = c() // debug print resulting order println toString() return this } ... 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 } String toString() { "$bs $quantity shares of $security.name and valuation of ${valueCalculation(quantity, limitPrice)}" } } newOrder.to.buy(100.shares.of(IBM)) { limitPrice 300 allOrNone true valueAs { qty * unitPrice - 500 } } newOrder.to.sell 200.shares.of(GOOG), { limitPrice 200 allOrNone false valueAs { qty * unitPrice - 100 } }
  • 154. …Stock Exchange Order DSL… DSLs 2010 - 154 ©ASERT2006-2010 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 Customer
  • 155. …Stock Exchange Order DSL… DSLs 2010 - 155 ©ASERT2006-2010 // 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 } String toString() { "$action $quantity shares of $security.name at limit price of $limitPrice" } } // ... http://groovyconsole.appspot.com/script/226001 by glaforge
  • 156. …Stock Exchange Order DSL… DSLs 2010 - 156 ©ASERT2006-2010 // ... 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 // don't thrown 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 }
  • 157. …Stock Exchange Order DSL… DSLs 2010 - 157 ©ASERT2006-2010 // ---- Stock exchange orders 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 } } http://groovyconsole.appspot.com/script/226001 by glaforge
  • 158. …Stock Exchange Order DSL DSLs 2010 - 158 ©ASERT2006-2010 From: customer@finance-broker.org To: Guillaume Subject: Project Request Date: Wednesday evening Dear Guillaume, Brilliant! Even our CEO could write orders! Thanks, Happy Customer
  • 159. THURSDAY DSLs 2010 - 159 ©ASERT2006-2010 http://t1.gstatic.com/images?q=tbn:_VBloEP8YC-6IM:http://msp248.photobucket.com/albums/gg171/ingrid2002/TextPL252028
  • 160. Einstein‟s Riddle DSL … DSLs 2010 - 160 ©ASERT2006-2010 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 Customer
  • 161. Einstein‟s Riddle… • Wikipedia: The zebra puzzle is a well- known logic puzzle – It is often called Einstein's Puzzle or Einstein's 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 world's population can solve it.” – The puzzle is also sometimes attributed to Lewis Carroll. However, there is no known evidence for Einstein's or Carroll's authorship; and the original puzzle cited below mentions brands of cigarette, such as Kools, that did not exist during Carroll's lifetime or Einstein's boyhood DSLs 2010 - 161 ©ASERT2006-2010
  • 162. …Einstein‟s Riddle DSLs 2010 - 162 ©ASERT2006-2010 • 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 house – The man who smokes Blend has a neighbor who drinks water • And a question: – Who owns the fish?
  • 163. Einstein‟s Riddle : Prolog ©ASERT2006-2010 % from http://www.baptiste-wicht.com/2010/09/solve-einsteins-riddle-using-prolog % Preliminary definitions 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). % The Brit lives in a red house hint1([(brit,red,_, _, _)|_]). hint1([_|T]) :- hint1(T). % The Swede keeps dogs as pets hint2([(swede,_,_,_,dog)|_]). hint2([_|T]) :- hint2(T). % The Dane drinks tea hint3([(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). ... DSLs 2010 - 163
  • 164. Einstein‟s Riddle DSL … DSLs 2010 - 164 ©ASERT2006-2010 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 Customer
  • 165. Einstein‟s Riddle : Polyglot ©ASERT2006-2010 @GrabResolver('http://dev.inf.unideb.hu:8090/archiva/repository/internal') //@Grab('jlog:jlogic-debug:1.3.6') @Grab('org.prolog4j:prolog4j-api:0.2.0') // uncomment one of the next three lines //@Grab('org.prolog4j:prolog4j-jlog:0.2.0') @Grab('org.prolog4j:prolog4j-tuprolog:0.2.0') //@Grab('org.prolog4j:prolog4j-jtrolog:0.2.0') import org.prolog4j.* def p = ProverFactory.prover p.addTheory(new File('/GroovyExamples/tuProlog/src/einstein.pl').text) def sol = p.solve("solution(Persons).") //println sol.solution.get('Persons') // jlog to avoid converter println sol.get('Persons') // jtrolog/tuProlog DSLs 2010 - 165
  • 166. Einstein‟s Riddle DSL … DSLs 2010 - 166 ©ASERT2006-2010 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 Customer
  • 167. Einstein‟s Riddle : Polyglot w/ DSL… ©ASERT2006-2010 // 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' DSLs 2010 - 167 // 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). ''')
  • 168. …Einstein‟s Riddle : Polyglot w/ DSL… ©ASERT2006-2010 DSLs 2010 - 168 // 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++ } ...
  • 169. …Einstein‟s Riddle : Polyglot w/ DSL… ©ASERT2006-2010 DSLs 2010 - 169 // 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]) } ] } }]} ] } ...
  • 170. …Einstein‟s Riddle : Polyglot w/ DSL… ©ASERT2006-2010 DSLs 2010 - 170 // now define the DSL the man from the centre house drinks milk the Norwegian owns the first house the Dane drinks tea the German smokes prince the Swede keeps dogs // alternate ending: has a pet dog the Brit has a red house // alternate ending: red abode the owner of the green house drinks coffee the owner of the yellow house smokes dunhill the person known to smoke pallmall rears birds // alt’n8 end: keeps birds the man known to smoke bluemaster drinks beer the green house is on the left side of the white house the man known to smoke blends lives next to the one who keeps cats the man known to keep horses lives next to the man who smokes dunhill the man known to smoke blends lives next to the one who drinks water the Norwegian lives next to the blue house
  • 171. Einstein‟s Riddle DSL … DSLs 2010 - 171 ©ASERT2006-2010 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 Customer
  • 172. …Einstein‟s Riddle : Polyglot w/ DSL… ©ASERT2006-2010 DSLs 2010 - 172 // 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 = German ctx = [from: German] [drinks: …, smokes: { s -> addPairHint([from: German, smoke: s]) }, keeps: …, … ] addPairHint([from: German, smoke: prince])
  • 173. …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: DSLs 2010 - 173 ©ASERT2006-2010 „house‟ is key for a Map and could be any value
  • 174. …Einstein‟s Riddle : Polyglot w/ DSL ©ASERT2006-2010 DSLs 2010 - 174 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) } def the(Color c1) {[ house: { _is -> [on: { _the -> [left: { _side -> [of: { __the -> Color.values().collectEntries{ c2 -> [c2.toString(), { _dummy -> addToLeftHint([color:c1], [color:c2]) }]} }]}]}]} ]} „house‟ is now understood
  • 175. Einstein‟s Riddle DSL … DSLs 2010 - 175 ©ASERT2006-2010 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 Customer
  • 176. Einstein‟s Riddle : Choco w/ DSL… ©ASERT2006-2010 DSLs 2010 - 176 @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) } ...
  • 177. …Einstein‟s Riddle : Choco w/ DSL… ©ASERT2006-2010 DSLs 2010 - 177 // 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)}] }, ...
  • 178. …Einstein‟s Riddle : Choco w/ DSL… ©ASERT2006-2010 DSLs 2010 - 178 // 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 ...
  • 179. …Einstein‟s Riddle : Choco w/ DSL • Output: DSLs 2010 - 179 ©ASERT2006-2010 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 Einstein's 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 house
  • 180. FRIDAY DSLs 2010 - 180 ©ASERT2006-2010 http://t3.gstatic.com/images?q=tbn:mgkj1lXpQ2-uWM:http://i298.photobucket.com/albums/mm269/bearyjuicylicious/comments
  • 181. Important business meetings all afternoon! DSLs 2010 - 181 ©ASERT2006-2010 http://www.flickr.com/photos/bobswanson/5093775403/sizes/l/in/pool-52240148330@N01/
  • 182. Topics • Introduction • Groovy DSL Features • A Week In The Life Of DSL INC Summary • More Info DSLs 2010 - 182 ©ASERT2006-2010
  • 183. Some Finishing Guidelines • Language and program evolve together … In the end your program will look as if the language had been designed for it … you end up with code which is clear, small, and efficient. – Paul Graham • Don‟t think just about Coding up your DSL – But also about its target audience, its evolution, required tool support and its testability DSLs 2010 - 183
  • 184. Start small, with key concepts Beware over-engineering!
  • 186. Get your hands dirty Play with the end-users
  • 187. Let your DSL fly, it‟s not yours, it‟s theirs!
  • 189. Stay humble, You can‟t get it right the 1st time. Don‟t design alone at your desk Involve the end users from the start
  • 190. Playing it safe in a sandbox
  • 191. Topics • Introduction • Groovy DSL Features • A Week In The Life Of DSL INC • Summary More Info DSLs 2010 - 191 ©ASERT2006-2010
  • 192. Further Info… DSLs 2010 - 192 ©ASERT2006-2010 • Compilers : Principles, Techniques, and Tools/ Edition 2, Alfred Aho, Ravi Sethi, Jeffrey Ullman, Monica Lam • Practical API Design : Confessions of a Java Framework Architect, Jaroslav Tulach • Language Implementation Patterns : Create Your Own Domain-Specific and General Programming Languages, Terence Parr
  • 193. …Further Info DSLs 2010 - 193 ©ASERT2006-2010 • A model-driven framework for domain specific languages, Martin Karlsch • Design Guidelines for Domain Specific Languages, Gabor Karsai et al • Program Comprehension for Domain- Specific Languages, Pereira et al • Diagrammatic Representations in Domain- Specific Languages, Konstantinos Tourlas • DSLs in Action, Debasish Ghosh • Domain-Specific Languages, Martin Fowler • DSLs in Boo : Domain Specific Languages in .NET, Ayende Rahien
  • 194. GinA 2ed “ReGinA” is coming! DSLs 2010 - 194 Contains a chapter on DSLs!