GroovyDSLs

8,478 views

Published on

Using Groovy to write (predominantly internal/embedded) Domains Specific Languages (DSLs)

Published in: Technology
0 Comments
13 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
8,478
On SlideShare
0
From Embeds
0
Number of Embeds
39
Actions
Shares
0
Downloads
235
Comments
0
Likes
13
Embeds 0
No embeds

No notes for slide

GroovyDSLs

  1. 1. © ASERT 2006-2010 Writing Domain Specific Languages (DSLs) using Groovy Dr Paul King @paulk_asert paulk at asert.com.au September 2010
  2. 2. Topics DSL Origins • Groovy Intro • Groovy DSL Features • Groovy DSL Examples • Advanced Topics & Guidelines © ASERT 2006-2010 • More Info DSLs 2010 - 2
  3. 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 © ASERT 2006-2010 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
  4. 4. ...What is a DSL? • Advantages: • Disadvantages: – Domain experts can – Learning cost vs. limited understand, validate, modify, applicability and often even develop DSL – Cost of designing, programs implementing & maintaining – Somewhat self-documenting DSL as well as the tools/IDEs – Enhance quality, – Attaining proper scope © ASERT 2006-2010 productivity, reliability, – Trade-offs between domain- maintainability, portability specificity and general- and reusability purpose programming – Safety; as long as the language constructs language constructs are safe – Efficiency costs any sentence written with – Proliferation of similar non- them can be considered safe standard DSLs, i.e. different but similar DSLs used within two insurance companies Source: http://en.wikipedia.org/wiki/Domain-specific_language DSLs 2010 - 4
  5. 5. Origins • It has always been a holy grail of computer users to be able to speak directly to our computers © ASERT 2006-2010 • 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 - 5
  6. 6. Origins • It has always been a holy grail of computer users to be able to speak directly to our computers © ASERT 2006-2010 http://dsal.uchicago.edu/reference/gazetteer/pager.html?objectid=DS405.1.I34_V02_298.gif DSLs 2010 - 6
  7. 7. 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 © ASERT 2006-2010 “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 - 7
  8. 8. Origins: Minilanguages... Source: The Art of Unix Programming:Taxonomy of languages: http://www.faqs.org/docs/artu/ch08s01.html # Minilanguage taxonomy %!PS-Adobe-3.0 # Base ellipses %%Creator: groff version 1.20.1 define smallellipse {ellipse width 3.0 height 1.5} ... M: ellipse width 3.0 height 1.8 fill 0.2 597.6 12 72 12 DL(increasing loopiness)297.71 8.2 Q(/etc/passwd)102.67 line from M.n to M.s dashed 94.6 Q(.ne)110.715 106.6 Q(wsrc)-.25 E(SNG)195.2 100.6 Q(re)243.8 94.6 Q D: smallellipse() with .e at M.w + (0.8, 0) (ge)-.15 E(xps)-.15 E(Glade)247.26 106.6 Q(m4)306.81 58.6 Q -1(Ya)303.43 line from D.n to D.s dashed 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) I: smallellipse() with .w at M.e - (0.8, 0) 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) # Arrow headings 354.84 106.6 Q(f)-.25 E(Postscript)343.875 118.6 Q(dc)412.88 94.6 Q(bc) arrow from D.w + (0.4, 0.8) to D.e + (-0.4, 0.8) 412.88 106.6 Q(Emacs Lisp)462.53 94.6 Q(Ja)465.395 106.6 Q -.25(va)-.2 G "flat to structured" "" at last arrow.c (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 # Interpreters %%Trailer "Emacs Lisp" "JavaScript" at 0.25 between M.e and I.e end "sh" "tcl" at 0.55 between M.e and I.e %%EOF "Perl" "Python" "Java" at 0.8 between M.e and I.e Input DSL: pic for above picture Output "DSL": Postscript for above picture DSLs 2010 - 8
  9. 9. ...Origins: Minilanguages... 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' ) } } PowerPoint DSL for previous slide DSLs 2010 - 9
  10. 10. ...Origins: Minilanguages Glade XSLT <?xml version="1.0"?> <?xml version="1.0"?> <GTK-Interface> <xsl:stylesheetversion="1.0" <widget> xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <class>GtkWindow</class> <xsl:output method="xml"/> <name>HelloWindow</name> <xsl:template match="*"> <border_width>5</border_width> <xsl:element name="{name()}"> <Signal> <xsl:for-each select="@*"> <name>destroy</name> <xsl:element name="{name()}"> <handler>gtk_main_quit</handler> <xsl:value-of select="."/> </Signal> </xsl:element> <title>Hello</title> </xsl:for-each> <type>GTK_WINDOW_TOPLEVEL</type> <xsl:apply-templates select="*|text()"/> <position>GTK_WIN_POS_NONE</position> </xsl:element> <allow_shrink>True</allow_shrink> </xsl:template> <allow_grow>True</allow_grow> </xsl:stylesheet> <auto_shrink>False</auto_shrink> Regex <widget> <class>GtkButton</class> "x.z?z{1,3}y" <name>Hello World</name> <can_focus>True</can_focus> fetchmail <label>Hello World</label> </widget> # Poll this site first each cycle. poll pop.provider.net proto pop3 </widget> </GTK-Interface> user "jsmith" with pass "secret1" is "smith" here user jones with pass "secret2" is "jjones" here with options keep SQL # Poll this site second, unless Lord Voldemort zaps us first. poll billywig.hogwarts.com with proto imap: SELECT * FROM TABLE user harry_potter with pass "floo" is harry_potter here WHERE NAME LIKE '%SMI' # Poll this site third in the cycle. ORDER BY NAME # Password will be fetched from ~/.netrc poll mailhost.net with proto imap: user esr is esr here Troff cat thesis.ms | chem | tbl | refer | grap | pic | eqn | groff -Tps > thesis.ps Source: Applying minilanguages: http://www.faqs.org/docs/artu/ch08s02.html DSLs 2010 - 10
  11. 11. And now? The twitterverse says... DSLs 2010 - 11
  12. 12. ...And Now? The twitterverse says DSLs 2010 - 12
  13. 13. State of the Art for DSLs? © ASERT 2006-2010 Source: http://www.infoq.com/presentations/vanderburg-state-of-dsl-ruby – Gartner's take on technology adoption DSLs 2010 - 13
  14. 14. "Is it a DSL or an API?" Checklist • A ten-question test to help determine whether a wad of code represents a DSL or an API: 1. Have you ever programmed in a language other than Ruby? (PHP and HTML don‟t count.) If not, it‟s a DSL. 2. Is the defining syntactic feature that you’ve cleverly left the parentheses off of a list of function arguments? If so, it‟s a DSL. 3. Is the code primarily a list of key-value pairs? Welcome to DSL Town, population you! 4. Does the code require the liberal use of eval() or equivalent? DSL, yay! 5. Have you ever used the phrase “… and it reads just like English!” in seriousness? You‟d better get to the hospital; you‟re coming down with a case of the DSLs! 6. ... Source: http://www.oreillynet.com/onlamp/blog/2007/05/the_is_it_a_dsl_or_an_api_ten.html DSLs 2010 - 14
  15. 15. 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 © ASERT 2006-2010 • External (custom language) – May involve writing your own traditional parser – Using parser combinators – Interpreting – String grepping • Other characteristics – Functional vs OO DSLs 2010 - 15
  16. 16. ...DSL usage patterns... © ASERT 2006-2010 Source: DSLs in Action, Debasish Ghosh, Manning now in MEAP DSLs 2010 - 16
  17. 17. ...DSL usage patterns © ASERT 2006-2010 Source: DSLs in Action, Debasish Ghosh, Manning now in MEAP DSLs 2010 - 17
  18. 18. DSL usage patterns (Advanced) © ASERT 2006-2010 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.
  19. 19. Topics • DSL origins Groovy Intro • Groovy DSL Features • Groovy DSL Examples • Advanced Topics & Guidelines © ASERT 2006-2010 • More Info DSLs 2010 - 19
  20. 20. 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.” © ASERT 2006-2010 Groovy = Java – boiler plate code + optional dynamic typing + closures + domain specific languages + builders + metaprogramming DSLs 2010 - 20
  21. 21. Groovy Goodies Overview • Fully object oriented • Closures: reusable and assignable pieces of code • Operators can be • GPath: efficient overloaded © ASERT 2006-2010 object navigation • Multimethods • GroovyBeans • Literal declaration for • grep and switch lists (arrays), maps, ranges and regular • Templates, builder, expressions swing, Ant, markup, XML, SQL, XML-RPC, Scriptom, Grails, tests, Mocks DSLs 2010 - 21
  22. 22. Growing Acceptance … A slow and steady start but now gaining in momentum, maturity and mindshare Now free
  23. 23. … Growing Acceptance … © ASERT 2006-2010 DSLs 2010 - 23
  24. 24. … Growing Acceptance … © ASERT 2006-2010 Groovy and Grails downloads: 70-90K per month and growing DSLs 2010 - 24
  25. 25. … Growing Acceptance … © ASERT 2006-2010 Source: http://www.micropoll.com/akira/mpresult/501697-116746 Source: http://www.grailspodcast.com/ DSLs 2010 - 25
  26. 26. … Growing Acceptance … © ASERT 2006-2010 http://www.jroller.com/scolebourne/entry/devoxx_2008_whiteboard_votes http://www.java.net DSLs 2010 - 26
  27. 27. … Growing Acceptance … What alternative JVM language are you using or intending to use © ASERT 2006-2010 http://www.leonardoborges.com/writings DSLs 2010 - 27
  28. 28. … Growing Acceptance … © ASERT 2006-2010 http://it-republik.de/jaxenter/quickvote/results/1/poll/44 (translated using http://babelfish.yahoo.com) DSLs 2010 - 28
  29. 29. … Growing Acceptance © ASERT 2006-2010 DSLs 2010 - 29
  30. 30. The Landscape of JVM Languages optional static types © ASERT 2006-2010 Dynamic features call for dynamic types Java bytecode calls for static types The terms “Java Virtual Machine” and “JVM” mean a Virtual Machine for the Java™ platform. DSLs 2010 - 30
  31. 31. 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() © ASERT 2006-2010 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)... DSLs 2010 - 31
  32. 32. A Better Java... import java.util.List; import java.util.ArrayList; class Erase { private List removeLongerThan(List strings, int length) { This code List result = new ArrayList(); for (int i = 0; i < strings.size(); i++) { is valid String s = (String) strings.get(i); if (s.length() <= length) { Java and } result.add(s); valid Groovy } return result; © ASERT 2006-2010 } 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(); Based on an List shortNames = e.removeLongerThan(names, 3); System.out.println(shortNames.size()); example by for (int i = 0; i < shortNames.size(); i++) { Jim Weirich String s = (String) shortNames.get(i); System.out.println(s); & Ted Leung } } } DSLs 2010 - 32
  33. 33. ...A Better Java... import java.util.List; import java.util.ArrayList; class Erase { private List removeLongerThan(List strings, int length) { Do the List result = new ArrayList(); for (int i = 0; i < strings.size(); i++) { semicolons String s = (String) strings.get(i); if (s.length() <= length) { add anything? } result.add(s); And shouldn‟t } we us more return result; © ASERT 2006-2010 } modern list public static void main(String[] args) { List names = new ArrayList(); notation? names.add("Ted"); names.add("Fred"); names.add("Jed"); names.add("Ned"); Why not System.out.println(names); import common Erase e = new Erase(); List shortNames = e.removeLongerThan(names, 3); libraries? System.out.println(shortNames.size()); for (int i = 0; i < shortNames.size(); i++) { String s = (String) shortNames.get(i); System.out.println(s); } } } DSLs 2010 - 33
  34. 34. ...A Better Java... 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() © ASERT 2006-2010 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) } } } DSLs 2010 - 34
  35. 35. ...A Better Java... 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) Do we need } } the static types? } return result Must we always have a main public static void main(String[] args) { List names = new ArrayList() method and © ASERT 2006-2010 names.add("Ted"); names.add("Fred") names.add("Jed"); names.add("Ned") class definition? System.out.println(names) Erase e = new Erase() How about List shortNames = e.removeLongerThan(names, 3) improved System.out.println(shortNames.size()) for (String s in shortNames) { consistency? System.out.println(s) } } } DSLs 2010 - 35
  36. 36. ...A Better Java... def removeLongerThan(strings, length) { def result = new ArrayList() for (s in strings) { if (s.size() <= length) { result.add(s) } } return result } © ASERT 2006-2010 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) } DSLs 2010 - 36
  37. 37. ...A Better Java... def removeLongerThan(strings, length) { def result = new ArrayList() for (s in strings) { if (s.size() <= length) { result.add(s) Shouldn‟t we } } have special return result notation for lists? } And special © ASERT 2006-2010 names = new ArrayList() facilities for names.add("Ted") names.add("Fred") list processing? names.add("Jed") Is „return‟ names.add("Ned") needed at end? System.out.println(names) shortNames = removeLongerThan(names, 3) System.out.println(shortNames.size()) for (s in shortNames) { System.out.println(s) } DSLs 2010 - 37
  38. 38. ...A Better Java... 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) } © ASERT 2006-2010 DSLs 2010 - 38
  39. 39. ...A Better Java... def removeLongerThan(strings, length) { strings.findAll{ it.size() <= length } } Is the method names = ["Ted", "Fred", "Jed", "Ned"] now needed? System.out.println(names) shortNames = removeLongerThan(names, 3) Easier ways to System.out.println(shortNames.size()) use common shortNames.each{ System.out.println(s) } methods? © ASERT 2006-2010 Are brackets required here? DSLs 2010 - 39
  40. 40. ...A Better Java... names = ["Ted", "Fred", "Jed", "Ned"] println names shortNames = names.findAll{ it.size() <= 3 } println shortNames.size() shortNames.each{ println it } © ASERT 2006-2010 DSLs 2010 - 40
  41. 41. ...A Better Java names = ["Ted", "Fred", "Jed", "Ned"] println names shortNames = names.findAll{ it.size() <= 3 } println shortNames.size() shortNames.each{ println it } © ASERT 2006-2010 ["Ted", "Fred", "Jed", "Ned"] 3 Ted Jed Ned DSLs 2010 - 41
  42. 42. Grapes / Grab // 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' © ASERT 2006-2010 assert fruit.inverse().yellow == 'lemon' DSLs 2010 - 42
  43. 43. Better Design Patterns: Immutable... • Java Immutable Class – As per Joshua Bloch // ... @Override Effective Java public boolean equals(Object obj) { if (this == obj) public final class Punter { return true; private final String first; if (obj == null) private final String last; return false; if (getClass() != obj.getClass()) public String getFirst() { return false; return first; Punter other = (Punter) obj; } if (first == null) { if (other.first != null) © ASERT 2006-2010 public String getLast() { return false; return last; } else if (!first.equals(other.first)) } return false; if (last == null) { @Override if (other.last != null) public int hashCode() { return false; final int prime = 31; } else if (!last.equals(other.last)) int result = 1; return false; result = prime * result + ((first == null) return true; ? 0 : first.hashCode()); } result = prime * result + ((last == null) ? 0 : last.hashCode()); @Override return result; public String toString() { } return "Punter(first:" + first + ", last:" + last + ")"; public Punter(String first, String last) { } this.first = first; this.last = last; } } // ... DSLs 2010 - 43
  44. 44. ...Better Design Patterns: Immutable... • Java Immutable Class boilerplate – As per Joshua Bloch // ... @Override Effective Java public boolean equals(Object obj) { if (this == obj) public final class Punter { return true; private final String first; if (obj == null) private final String last; return false; if (getClass() != obj.getClass()) public String getFirst() { return false; return first; Punter other = (Punter) obj; } if (first == null) { if (other.first != null) © ASERT 2006-2010 public String getLast() { return false; return last; } else if (!first.equals(other.first)) } return false; if (last == null) { @Override if (other.last != null) public int hashCode() { return false; final int prime = 31; } else if (!last.equals(other.last)) int result = 1; return false; result = prime * result + ((first == null) return true; ? 0 : first.hashCode()); } result = prime * result + ((last == null) ? 0 : last.hashCode()); @Override return result; public String toString() { } return "Punter(first:" + first + ", last:" + last + ")"; public Punter(String first, String last) { } this.first = first; this.last = last; } } // ... DSLs 2010 - 44
  45. 45. ...Better Design Patterns: Immutable @Immutable class Punter { String first, last } © ASERT 2006-2010 DSLs 2010 - 45
  46. 46. Malleable Syntax 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 © ASERT 2006-2010 def "length of Spock's & his friends' names"() { expect: name.size() == length where: name | length "Spock" | 5 "Kirk" | 4 "Scotty" | 6 } Groovy 1.8+ DSLs 2010 - 46
  47. 47. Topics • DSL origins • Groovy Intro Groovy DSL Features • Groovy DSL Examples • Advanced Topics & Guidelines © ASERT 2006-2010 • More Info DSLs 2010 - 47
  48. 48. Groovy DSL Features © ASERT 2006-2010 DSLs 2010 - 48
  49. 49. Import / Import Static • Imports @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' © ASERT 2006-2010 • Static Imports import static java.util.Calendar.getInstance as now println now().format('yyyy/MMM/dd') What Java gives us plus aliases DSLs 2010 - 49
  50. 50. Static Imports...  Java Static Imports Discussed later:  Builders  Closures import groovy.swing.SwingXBuilder © ASERT 2006-2010 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() DSLs 2010 - 50
  51. 51. ...Static Imports... Java gives us this import groovy.swing.SwingXBuilder © ASERT 2006-2010 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() DSLs 2010 - 51
  52. 52. ...Static Imports... Java gives us this import groovy.swing.SwingXBuilder © ASERT 2006-2010 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() DSLs 2010 - 52
  53. 53. ...Static Imports... © ASERT 2006-2010 Source: http://www.thedoghousediaries.com/?p=1406 DSLs 2010 - 53
  54. 54. ...Static Imports Java doesn’t give us this! import groovy.swing.SwingXBuilder import static java.lang.Math.* import static java.awt.Color.GREEN as Lime © ASERT 2006-2010 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() DSLs 2010 - 54
  55. 55. Literal Syntax Conventions • Lists – Special syntax for list literals – Additional common methods (operator overloading) def list = [3, new Date(), 'Jan'] assert list + list == list * 2 • Maps © ASERT 2006-2010 – Special syntax for map literals – Additional common methods def map = [a: 1, b: 2] assert map['a'] == 1 && map.b == 2 • Ranges – Special syntax for various kinds of ranges def letters = 'a'..'z' def numbers = 0..<10 DSLs 2010 - 55
  56. 56. Literal Syntax Conventions in DSLs 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: ' © ASERT 2006-2010 if (now[DAY_OF_WEEK] in MONDAY..FRIDAY) println roomrate.weekday * multiplier else println roomrate.weekend * multiplier }  Literal list & map syntax  Ranges Your room rate is: 150 Also Your room rate is: 142.50  Static import aliases Your room rate is: 135.0  BigDecimal arithmetic DSLs 2010 - 56
  57. 57. Compact Syntax... • Java public class BuySharesJava { public static void buyShares(int qty, String name) { // business logic here ... System.out.println("buying " + qty + " shares of " + name); } © ASERT 2006-2010 public static void main(String[] args) { buyShares(3, "BHP"); }  Script syntax  Conventions for visibility }  GDK methods: println • Groovy  Implicit typing  GString interpolation def buyShares(qty, name) {  Optional brackets & „;‟ // business logic here ... println "buying $qty shares of $name" } buyShares 3, 'BHP' DSLs 2010 - 57
  58. 58. ...Compact Syntax... • Java  Script syntax, Named params, Extra imports  Conventions for visibility, GDK methods import java.util.Date;  Implicit typing, Multi-line GString import java.util.HashMap;  Elvis operator, Optional brackets & „;‟ 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); © ASERT 2006-2010 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 • Groovy Last name: ${cust.last ?: 'unknown'}""" } printDetails first: 'John', last: 'Smith' DSLs 2010 - 58
  59. 59. ...Compact Syntax • Java 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); • Groovy String last = cust.getLast(); System.out.println("Last name: " + (last != null ? last : "unknown")); } public static void main(String[] args) { CustomerJ c = new CustomerJ(); class CustomerG { String first, last } © ASERT 2006-2010 c.setFirst("John"); c.setLast("Smith"); def printDetails(cust) { printDetails(c); } println """Details as at: ${new Date()} } First name: $cust.first class CustomerJ { Last name: ${cust.last ?: 'unknown'}""" private String first; private String last; } public String getFirst() { return first; printDetails new CustomerG(first: 'John', } last: 'Smith') public void setFirst(String first) { this.first = first; } Plus:  Named params for constructors public String getLast() { return last;  JavaBean conventions } public void setLast(String last) { this.last = last;  Leverage Duck Typing } } DSLs 2010 - 59
  60. 60. Using With Example letters = ['a', 'b', 'c'] range = 'b'..'d' letters.with { add 'd' Just normal methods for ArrayList here remove 'a' } © ASERT 2006-2010 assert letters == range map = [a:10, b:4, c:7] map.with { assert (a + b) / c == 2 } DSLs 2010 - 60
  61. 61. 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 © ASERT 2006-2010 around, combined in structured ways to form more complex algorithms and data; functional coding style 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 DSLs 2010 - 61
  62. 62. ...Closures... int myConst = 4 def multiplier = { number -> number * myConst } assert multiplier(10) == 40 Name © ASERT 2006-2010 Closure other = { it + myConst } assert other.call(10) == 14 def twice = { it * 2 } // anonymous functions assert twice(10) == 20 // implemented as Closures DSLs 2010 - 62
  63. 63. ...Closures... int myConst = 4 def multiplier = { number -> number * myConst } assert multiplier(10) == 40 Code © ASERT 2006-2010 Closure other = { it + myConst } assert other.call(10) == 14 def twice = { it * 2 } // anonymous functions assert twice(10) == 20 // implemented as Closures DSLs 2010 - 63
  64. 64. ...Closures... Parameter(s) int myConst = 4 def multiplier = { number -> number * myConst } assert multiplier(10) == 40 Default parameter © ASERT 2006-2010 Closure other = { it + myConst } assert other.call(10) == 14 def twice = { it * 2 } // anonymous functions assert twice(10) == 20 // implemented as Closures DSLs 2010 - 64
  65. 65. ...Closures... int myConst = 4 def multiplier = { number -> number * myConst } assert multiplier(10) == 40 Call closure © ASERT 2006-2010 Closure other = { it + myConst } assert other.call(10) == 14 Alternative syntax def twice = { it * 2 } // anonymous functions assert twice(10) == 20 // implemented as Closures DSLs 2010 - 65
  66. 66. ...Closures... int myConst = 4 def multiplier = { number -> number * myConst } assert multiplier(10) == 40 Free variable © ASERT 2006-2010 Closure other = { it + myConst } assert other.call(10) == 14 def twice = { it * 2 } // anonymous functions assert twice(10) == 20 // implemented as Closures DSLs 2010 - 66
  67. 67. ...Closures... Bound to environment/context when called int myConst = 4 def multiplier = { number -> number * myConst } assert multiplier(10) == 40 © ASERT 2006-2010 Closure other = { it + myConst } assert other.call(10) == 14 def twice = { it * 2 } // anonymous functions assert twice(10) == 20 // implemented as Closures DSLs 2010 - 67
  68. 68. Closures in DSLs... Nothing special here. import static java.util.Calendar.* Just a list of maps. 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, © ASERT 2006-2010 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'] ] // ... DSLs 2010 - 68
  69. 69. ... Closures in DSLs... // ... 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) © ASERT 2006-2010 } } 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 DSLs 2010 - 69
  70. 70. ... Closures in DSLs... // ... 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) © ASERT 2006-2010 } } 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 DSLs 2010 - 70
  71. 71. ... Closures in DSLs // ... def specials = [noDiscount, tenPercentOffAll, tenPercentOffGifts, tenPercentOffOverForty, 1 240.00 tenPercentOffBooks, tenPercentOffDVDs, buyThreeGetOneFree] 2 216.00 3 220.00 [ 4 225.00 SUNDAY..SATURDAY, 5 221.00 © ASERT 2006-2010 [cart1, cart2] 6 235.00 ].combinations().each { day, cart -> 7 190.00 printf "%d %2.2fn", day, cart.sum(specials[day-1]) 1 110.00 } 2 99.00 3 105.00 4 105.00 5 105.00 6 104.00 7 110.00 DSLs 2010 - 71
  72. 72. Operator Overloading Example • Java 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)); • Groovy © ASERT 2006-2010 def c = 3.5, d = 4.0 assert c * d == 14.0 DSLs 2010 - 72
  73. 73. Groovy Lab Example // 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 © ASERT 2006-2010 // 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)) DSLs 2010 - 73
  74. 74. Better Control Structures: Switch Poker… hand1 hand2 8C TS KC 9H 4S 7D 2S 5D 3S AC 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() } © ASERT 2006-2010 rankValues = { List cards -> cards.collect{ rank(it) }.sort() } // ... println rankSizes(["7S", "7H", "2H", "7D", "AH"]) // => [1, 1, 3] DSLs 2010 - 74
  75. 75. …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) © ASERT 2006-2010 } 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 - 75
  76. 76. … 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 © ASERT 2006-2010 case straight : return 5 case threeOfAKind : return 4 case twoPair : return 3 case pair : return 2 default : return 1 } } // ... DSLs 2010 - 76
  77. 77. Groovy DSL Features © ASERT 2006-2010 DSLs 2010 - 77
  78. 78. Builders… • MarkupBuilder <html> import groovy.xml.* <head> def page = new MarkupBuilder() <title>Hello</title> page.html { </head> head { title 'Hello' } <body> body { © ASERT 2006-2010 <ul> ul { <li>world 1</li> for (count in 1..5) { <li>world 2</li> li "world $count" <li>world 3</li> } } } } <li>world 4</li> <li>world 5</li> </ul> </body> </html> DSLs 2010 - 78
  79. 79. ...Builders… • GraphicsBuilder 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] ) } © ASERT 2006-2010 DSLs 2010 - 79
  80. 80. …Builders new AntBuilder().with { echo(file:'Temp.java', ''' class Temp { public static void main(String[] args) { System.out.println("Hello"); } } ''') © ASERT 2006-2010 javac(srcdir:'.', includes:'Temp.java', fork:'true') java(classpath:'.', classname:'Temp', fork:'true') echo('Done') } // => // [javac] Compiling 1 source file // [java] Hello // [echo] Done DSLs 2010 - 80
  81. 81. Metaprogramming • Runtime Metaprogramming – Categories including DGM – invokeMethod, methodMissing, getProperty – GroovyInterceptable – ExpandoMetaClass © ASERT 2006-2010 • Compile-time Metaprogramming – AST Macros – Local and Global flavors based around annotations – Reduced runtime overhead – Several built-in annotations: • @Immutable • @Delegate • @Singleton DSLs 2010 - 81
  82. 82. ExpandoMetaClass… import static java.lang.Character.isUpperCase String.metaClass.swapCase = { delegate.collect { ch -> isUpperCase(ch as char) ? ch.toLowerCase() : ch.toUpperCase() }.join() } © ASERT 2006-2010 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 - 82
  83. 83. …ExpandoMetaClass class Person { String name } class MortgageLender { def borrowMoney() { "buy house" } © ASERT 2006-2010 } def lender = new MortgageLender() Person.metaClass.buyHouse = lender.&borrowMoney def p = new Person() assert "buy house" == p.buyHouse() DSLs 2010 - 83
  84. 84. Adding your own control structures • Thread enhancement ROCK! . 25 import java.util.concurrent.locks.ReentrantLock . 33 import static System.currentTimeMillis as now . 34 def startTime = now() . 35 ReentrantLock.metaClass.withLock = { critical -> . 36 lock() .. 131 try { critical() } .. 134 finally { unlock() } .. 137 } def lock = new ReentrantLock() .. 138 def worker = { threadNum -> .. 139 4.times { count -> ... 232 lock.withLock { ... 234 print " " * threadNum ... 237 print "." * (count + 1) ... 238 println " ${now() - startTime}" ... 239 } .... 334 Thread.sleep 100 .... 336 } } .... 337 5.times { Thread.start worker.curry(it) } .... 338 println "ROCK!" .... 339 Source: http://chrisbroadfoot.id.au/articles/2008/08/06/groovy-threads DSLs 2010 - 84
  85. 85. EMC DSL… © ASERT 2006-2010 DSLs 2010 - 85
  86. 86. ...EMC Neo4j DSL… @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 { © ASERT 2006-2010 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) } } // ... DSLs 2010 - 86
  87. 87. …EMC Neo4j DSL // ... 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!" © ASERT 2006-2010 relationship.message = "brave Neo4j" tx.success() } finally { tx.finish() println "$firstNode.message $relationship.message $secondNode.message" // => Hello, brave Neo4j world! graphDb.shutdown() } DSLs 2010 - 87
  88. 88. Groovy DSL Features © ASERT 2006-2010 DSLs 2010 - 88
  89. 89. AST Builder • Numerous approaches, still evolving. “From code” approach: def result = new AstBuilder().buildFromCode { println "Hello World" } • Produces: BlockStatement -> ReturnStatement -> MethodCallExpression -> VariableExpression("this") -> ConstantExpression("println") -> ArgumentListExpression -> ConstantExpression("Hello World") DSLs 2010 - 89
  90. 90. Type Transformation Example 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) { © ASERT 2006-2010 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 - 90
  91. 91. GParsec... • Miniature parsers def isLetter = { ch -> return Character.isLetter(ch) } def isDigit = { ch -> return Character.isDigit(ch) } def letter = satisfyC(isLetter) def digit = satisfyC(isDigit) • Parser Combinators def letterAndDigit = seqC(letter, digit) assert letterAndDigit('a123###') == ['a', '1'] def identifier = seqC(identifierStart, noneOrMoreC(identifierRest)) DSLs 2010 - 91
  92. 92. ...GParsec... • BNF variableDeclarator : identifier ( '=' expression )? variableDefinitions : variableDeclarator ( ',' variableDeclarator)* • Groovy definitions def variableDeclarator = seqC(identifier, optionalC(seqC(assign, expression))) def variableDefinitions = seqC(variableDeclarator, noneOrMoreC(seqC(comma, variableDeclarator))) DSLs 2010 - 92
  93. 93. ...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 - 93
  94. 94. Using External Parsers 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 © ASERT 2006-2010 DSLs 2010 - 94
  95. 95. Topics • DSL origins • Groovy Intro • Groovy DSL Features Groovy DSL Examples • Advanced Topics & Guidelines © ASERT 2006-2010 • More Info DSLs 2010 - 95
  96. 96. Coin example... enum Coin { penny(1), nickel(5), dime(10), quarter(25) Coin(int value) { this.value = value } int value } © ASERT 2006-2010 import static Coin.* assert 2 * quarter.value + 1 * nickel.value + 2 * penny.value == 57 DSLs 2010 - 96
  97. 97. ...Coin example... class CoinMath { static multiply(Integer self, Coin c) { self * c.value } } use (CoinMath) { © ASERT 2006-2010 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 DSLs 2010 - 97
  98. 98. ...Coin example 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 © ASERT 2006-2010 } // 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 DSLs 2010 - 98
  99. 99. Game example... // 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) { © ASERT 2006-2010 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.* ... DSLs 2010 - 99
  100. 100. ...Game example... ... class Room { def description def contents = [] } ... © ASERT 2006-2010 DSLs 2010 - 100
  101. 101. ...Game example... ... class Player { def currentRoom def inventory = [] void look() { println "You are in ${currentRoom?.description?:'the void'} which contains ${listItems(currentRoom?.contents)}" } © ASERT 2006-2010 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" } } ... DSLs 2010 - 101
  102. 102. ...Game example... ... void drop(item) { if (inventory?.remove(item)) { currentRoom?.contents << item println "You dropped the $item" } else { println "You don't have the $item" } © ASERT 2006-2010 } def propertyMissing(String name) { if (metaClass.respondsTo(this, name)) { this."$name"() } name } } ... DSLs 2010 - 102
  103. 103. ...Game example... ... 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 © ASERT 2006-2010 inv look take emerald inv look take key drop emerald inv look } assert player.inventory == ['dagger', 'key'] ... DSLs 2010 - 103
  104. 104. ...Game example ... // now try some error conditions plainRoom.description = null player.with { drop gold take gold drop emerald © ASERT 2006-2010 take emerald take emerald look } DSLs 2010 - 104
  105. 105. GEP3 example... 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) } © ASERT 2006-2010 given = { it } given 100 please show the square_root // ==> 10.0 DSLs 2010 - 105
  106. 106. ...GEP3 example... Object.metaClass.of = { delegate[0](delegate[1](it)) } Object.metaClass.the = { clos -> [delegate[0], clos] } show = [{ println it }] © ASERT 2006-2010 square_root = { Math.sqrt(it) } please = { it } please show the square_root of 100 // ==> 10.0 DSLs 2010 - 106
  107. 107. ...GEP3 example... show = { println it } square_root = { Math.sqrt(it) } def please(action) { [the: { what -> [of: { n -> action(what(n)) }] © ASERT 2006-2010 }] } please show the square_root of 100 // ==> 10.0 Inspiration for this example came from … DSLs 2010 - 107
  108. 108. ...GEP3 example... // Japanese DSL using GEP3 rules Object.metaClass.を = Object.metaClass.の = { clos -> clos(delegate) } まず = { it } 表示する = { println it } © ASERT 2006-2010 平方根 = { 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 DSLs 2010 - 108
  109. 109. ...GEP3 example © ASERT 2006-2010 // source: http://d.hatena.ne.jp/uehaj/20100919/1284906117 // http://groovyconsole.appspot.com/edit/241001 DSLs 2010 - 109

×