Diese Präsentation wurde erfolgreich gemeldet.
Wir verwenden Ihre LinkedIn Profilangaben und Informationen zu Ihren Aktivitäten, um Anzeigen zu personalisieren und Ihnen relevantere Inhalte anzuzeigen. Sie können Ihre Anzeigeneinstellungen jederzeit ändern.

ConFess Vienna 2015 - Metaprogramming with Groovy

1.175 Aufrufe

Veröffentlicht am

Groovy is a dynamic language that provides different types of metaprogramming techniques. In this talk we’ll mainly see runtime metaprogramming. You’ll understand the Groovy Meta-Object-Protocol (MOP), the metaclass, how to intercept method calls, how to deal with method missing and property missing, the use of mixins, traits and categories. All of these topics will be explained with examples in order to understand them. Also, you’ll see a little bit about compile-time metaprogramming with AST Transformations. AST Transformations provide a wonderful way of manipulating code at compile time via modifications of the Abstract Syntax Tree. You’ll see a basic but powerful example of what we can do with AST transformations.

Veröffentlicht in: Technologie
  • Als Erste(r) kommentieren

ConFess Vienna 2015 - Metaprogramming with Groovy

  1. 1. METAPROGRAMMING WITH GROOVY Iván López @ilopmar
  2. 2. Hello! I am Iván López @ilopmar http://greachconf.com@madridgug
  3. 3. Groovy is dynamic ▷ “Delay” to runtime some decisions ▷ Add properties/behaviours in runtime ▷ Wide range of applicability
  4. 4. What is metaprogramming?
  5. 5. “Metaprogramming is the writing of computer programs that write or manipulate other programs (or themselves) as their data. - Wikipedia
  6. 6. 1. Runtime metaprogramming
  7. 7. Runtime metaprogramming ▷ Groovy provides this through Meta- Object Protocol (MOP) ▷ Use MOP to: – Invoke methods dynamically – Synthesize classes and methods on the fly
  8. 8. What is the Meta Object Protocol? Groovy Groovy Java Java MOP
  9. 9. Intercepting methods using MOP
  10. 10. public interface GroovyObject { Object invokeMethod(String name, Object args) Object getProperty(String propertyName) void setProperty(String propertyName, Object newValue) MetaClass getMetaClass() void setMetaClass(MetaClass metaClass) } Groovy Interceptable ▷ GroovyObject interface ▷ Implement GroovyInterceptable to hook into the execution
  11. 11. GroovyInterceptable example class Person implements GroovyInterceptable { String name Integer age public Object getProperty(String propertyName) { println "Getting property '${propertyName}'" return this.@"${propertyName}" } public void setProperty(String propertyName, Object newValue) { println "Setting property '${propertyName}' with value '${newValue}'" this.@"${propertyName}" = newValue } } def person = new Person() person.name = "Iván" person.age = 35 println "Hello ${person.name}, you're ${person.age}" // Execution Setting property 'name' with value 'Iván' Setting property 'age' with value '35' Getting property 'name' Getting property 'age' Hello Iván, you're 35
  12. 12. GroovyInterceptable example class Person implements GroovyInterceptable { String name Integer age public Object getProperty(String propertyName) { println "Getting property '${propertyName}'" return this.@"${propertyName}" } public void setProperty(String propertyName, Object newValue) { println "Setting property '${propertyName}' with value '${newValue}'" this.@"${propertyName}" = newValue } } def person = new Person() person.name = "Iván" person.age = 35 println "Hello ${person.name}, you're ${person.age}" // Execution Setting property 'name' with value 'Iván' Setting property 'age' with value '35' Getting property 'name' Getting property 'age' Hello Iván, you're 35
  13. 13. GroovyInterceptable example class Person implements GroovyInterceptable { String name Integer age public Object getProperty(String propertyName) { println "Getting property '${propertyName}'" return this.@"${propertyName}" } public void setProperty(String propertyName, Object newValue) { println "Setting property '${propertyName}' with value '${newValue}'" this.@"${propertyName}" = newValue } } def person = new Person() person.name = "Iván" person.age = 35 println "Hello ${person.name}, you're ${person.age}" // Execution Setting property 'name' with value 'Iván' Setting property 'age' with value '35' Getting property 'name' Getting property 'age' Hello Iván, you're 35
  14. 14. GroovyInterceptable example class Person implements GroovyInterceptable { String name Integer age public Object getProperty(String propertyName) { println "Getting property '${propertyName}'" return this.@"${propertyName}" } public void setProperty(String propertyName, Object newValue) { println "Setting property '${propertyName}' with value '${newValue}'" this.@"${propertyName}" = newValue } } def person = new Person() person.name = "Iván" person.age = 35 println "Hello ${person.name}, you're ${person.age}" // Execution Setting property 'name' with value 'Iván' Setting property 'age' with value '35' Getting property 'name' Getting property 'age' Hello Iván, you're 35
  15. 15. GroovyInterceptable example class Person implements GroovyInterceptable { String name Integer age public Object getProperty(String propertyName) { println "Getting property '${propertyName}'" return this.@"${propertyName}" } public void setProperty(String propertyName, Object newValue) { println "Setting property '${propertyName}' with value '${newValue}'" this.@"${propertyName}" = newValue } } def person = new Person() person.name = "Iván" person.age = 35 println "Hello ${person.name}, you're ${person.age}" // Execution Setting property 'name' with value 'Iván' Setting property 'age' with value '35' Getting property 'name' Getting property 'age' Hello Iván, you're 35
  16. 16. class Hello implements GroovyInterceptable { public Object invokeMethod(String methodName, Object args) { System.out.println "Invoking method '${methodName}' with args '${args}'" def method = metaClass.getMetaMethod(methodName, args) method?.invoke(this, args) } void sayHi(String name) { System.out.println "Hello ${name}" } } def hello = new Hello() hello.sayHi("ConFess Vienna!") hello.anotherMethod() GroovyInterceptable example (II) // Execution Invoking method 'sayHi' with args '[ConFess Vienna!]' Hello ConFess Vienna! Invoking method 'anotherMethod' with args '[]'
  17. 17. class Hello implements GroovyInterceptable { public Object invokeMethod(String methodName, Object args) { System.out.println "Invoking method '${methodName}' with args '${args}'" def method = metaClass.getMetaMethod(methodName, args) method?.invoke(this, args) } void sayHi(String name) { System.out.println "Hello ${name}" } } def hello = new Hello() hello.sayHi("ConFess Vienna!") hello.anotherMethod() GroovyInterceptable example (II) // Execution Invoking method 'sayHi' with args '[ConFess Vienna!]' Hello ConFess Vienna! Invoking method 'anotherMethod' with args '[]'
  18. 18. class Hello implements GroovyInterceptable { public Object invokeMethod(String methodName, Object args) { System.out.println "Invoking method '${methodName}' with args '${args}'" def method = metaClass.getMetaMethod(methodName, args) method?.invoke(this, args) } void sayHi(String name) { System.out.println "Hello ${name}" } } def hello = new Hello() hello.sayHi("ConFess Vienna!") hello.anotherMethod() GroovyInterceptable example (II) // Execution Invoking method 'sayHi' with args '[ConFess Vienna!]' Hello ConFess Vienna! Invoking method 'anotherMethod' with args '[]'
  19. 19. class Hello implements GroovyInterceptable { public Object invokeMethod(String methodName, Object args) { System.out.println "Invoking method '${methodName}' with args '${args}'" def method = metaClass.getMetaMethod(methodName, args) method?.invoke(this, args) } void sayHi(String name) { System.out.println "Hello ${name}" } } def hello = new Hello() hello.sayHi("ConFess Vienna!") hello.anotherMethod() GroovyInterceptable example (II) // Execution Invoking method 'sayHi' with args '[ConFess Vienna!]' Hello ConFess Vienna! Invoking method 'anotherMethod' with args '[]'
  20. 20. MetaClass ▷ MetaClass registry for each class ▷ Collection of methods/properties ▷ We can always modify the metaclass ▷ Intercept methods implementing invokeMethod on metaclass
  21. 21. class Hello { void sayHi(String name) { println "Hello ${name}" } } Hello.metaClass.invokeMethod = { String methodName, args -> println "Invoking method '${methodName}' with args '${args}'" def method = Hello.metaClass.getMetaMethod(methodName, args) method?.invoke(delegate, args) } def hello = new Hello() hello.sayHi("ConFess Vienna!") hello.anotherMethod() // Execution Invoking method 'sayHi' with args '[ConFess Vienna!]' Hello ConFess Vienna! Invoking method 'anotherMethod' with args '[]' MetaClass example
  22. 22. class Hello { void sayHi(String name) { println "Hello ${name}" } } Hello.metaClass.invokeMethod = { String methodName, args -> println "Invoking method '${methodName}' with args '${args}'" def method = Hello.metaClass.getMetaMethod(methodName, args) method?.invoke(delegate, args) } def hello = new Hello() hello.sayHi("ConFess Vienna!") hello.anotherMethod() // Execution Invoking method 'sayHi' with args '[ConFess Vienna!]' Hello ConFess Vienna! Invoking method 'anotherMethod' with args '[]' MetaClass example
  23. 23. class Hello { void sayHi(String name) { println "Hello ${name}" } } Hello.metaClass.invokeMethod = { String methodName, args -> println "Invoking method '${methodName}' with args '${args}'" def method = Hello.metaClass.getMetaMethod(methodName, args) method?.invoke(delegate, args) } def hello = new Hello() hello.sayHi("ConFess Vienna!") hello.anotherMethod() // Execution Invoking method 'sayHi' with args '[ConFess Vienna!]' Hello ConFess Vienna! Invoking method 'anotherMethod' with args '[]' MetaClass example
  24. 24. class Hello { void sayHi(String name) { println "Hello ${name}" } } Hello.metaClass.invokeMethod = { String methodName, args -> println "Invoking method '${methodName}' with args '${args}'" def method = Hello.metaClass.getMetaMethod(methodName, args) method?.invoke(delegate, args) } def hello = new Hello() hello.sayHi("ConFess Vienna!") hello.anotherMethod() // Execution Invoking method 'sayHi' with args '[ConFess Vienna!]' Hello ConFess Vienna! Invoking method 'anotherMethod' with args '[]' MetaClass example
  25. 25. class Hello { void sayHi(String name) { println "Hello ${name}" } } Hello.metaClass.invokeMethod = { String methodName, args -> println "Invoking method '${methodName}' with args '${args}'" def method = Hello.metaClass.getMetaMethod(methodName, args) method?.invoke(delegate, args) } def hello = new Hello() hello.sayHi("ConFess Vienna!") hello.anotherMethod() // Execution Invoking method 'sayHi' with args '[ConFess Vienna!]' Hello ConFess Vienna! Invoking method 'anotherMethod' with args '[]' MetaClass example
  26. 26. MOP method injection
  27. 27. MOP Method Injection ▷ Injecting methods at code-writing time ▷ We can “open” a class any time ▷ Different techniques: – MetaClass – Categories – Extensions – Mixins vs Traits
  28. 28. class StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') } } String chuckIpsum = "If you can see Chuck Norris, he can see you. If you can not see Chuck Norris you may be only seconds away from death" println StringUtils.truncate(chuckIpsum, 72) println StringUtils.truncate(chuckIpsum, 72, true) // Execution If you can see Chuck Norris, he can see you. If you can not see Chuck No If you can see Chuck Norris, he can see you. If you can not see Chuck No... String.metaClass.truncate = { Integer length, Boolean overflow = false -> delegate.take(length) + (overflow ? '...' : '') } assert chuckIpsum.truncate(72, true) == StringUtils.truncate(chuckIpsum, 72, true) Adding methods using MetaClass
  29. 29. class StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') } } String chuckIpsum = "If you can see Chuck Norris, he can see you. If you can not see Chuck Norris you may be only seconds away from death" println StringUtils.truncate(chuckIpsum, 72) println StringUtils.truncate(chuckIpsum, 72, true) // Execution If you can see Chuck Norris, he can see you. If you can not see Chuck No If you can see Chuck Norris, he can see you. If you can not see Chuck No... String.metaClass.truncate = { Integer length, Boolean overflow = false -> delegate.take(length) + (overflow ? '...' : '') } assert chuckIpsum.truncate(72, true) == StringUtils.truncate(chuckIpsum, 72, true) Adding methods using MetaClass
  30. 30. class StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') } } String chuckIpsum = "If you can see Chuck Norris, he can see you. If you can not see Chuck Norris you may be only seconds away from death" println StringUtils.truncate(chuckIpsum, 72) println StringUtils.truncate(chuckIpsum, 72, true) // Execution If you can see Chuck Norris, he can see you. If you can not see Chuck No If you can see Chuck Norris, he can see you. If you can not see Chuck No... String.metaClass.truncate = { Integer length, Boolean overflow = false -> delegate.take(length) + (overflow ? '...' : '') } assert chuckIpsum.truncate(72, true) == StringUtils.truncate(chuckIpsum, 72, true) Adding methods using MetaClass
  31. 31. class StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') } } String chuckIpsum = "If you can see Chuck Norris, he can see you. If you can not see Chuck Norris you may be only seconds away from death" println StringUtils.truncate(chuckIpsum, 72) println StringUtils.truncate(chuckIpsum, 72, true) // Execution If you can see Chuck Norris, he can see you. If you can not see Chuck No If you can see Chuck Norris, he can see you. If you can not see Chuck No... String.metaClass.truncate = { Integer length, Boolean overflow = false -> delegate.take(length) + (overflow ? '...' : '') } assert chuckIpsum.truncate(72, true) == StringUtils.truncate(chuckIpsum, 72, true) Adding methods using MetaClass
  32. 32. class StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') } } String chuckIpsum = "If you can see Chuck Norris, he can see you. If you can not see Chuck Norris you may be only seconds away from death" println StringUtils.truncate(chuckIpsum, 72) println StringUtils.truncate(chuckIpsum, 72, true) // Execution If you can see Chuck Norris, he can see you. If you can not see Chuck No If you can see Chuck Norris, he can see you. If you can not see Chuck No... String.metaClass.truncate = { Integer length, Boolean overflow = false -> delegate.take(length) + (overflow ? '...' : '') } assert chuckIpsum.truncate(72, true) == StringUtils.truncate(chuckIpsum, 72, true) Adding methods using MetaClass
  33. 33. class StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') } } String chuckIpsum = "If you can see Chuck Norris, he can see you. If you can not see Chuck Norris you may be only seconds away from death" println StringUtils.truncate(chuckIpsum, 72) println StringUtils.truncate(chuckIpsum, 72, true) // Execution If you can see Chuck Norris, he can see you. If you can not see Chuck No If you can see Chuck Norris, he can see you. If you can not see Chuck No... String.metaClass.truncate = { Integer length, Boolean overflow = false -> delegate.take(length) + (overflow ? '...' : '') } assert chuckIpsum.truncate(72, true) == StringUtils.truncate(chuckIpsum, 72, true) Adding methods using MetaClass
  34. 34. class Utils { } def utilsInstance = new Utils() Utils.metaClass.version = "3.0" utilsInstance.metaClass.released = true assert utilsInstance.version == "3.0" assert utilsInstance.released == true Adding properties using MetaClass
  35. 35. Adding properties using MetaClass class Utils { } def utilsInstance = new Utils() Utils.metaClass.version = "3.0" utilsInstance.metaClass.released = true assert utilsInstance.version == "3.0" assert utilsInstance.released == true
  36. 36. class Utils { } def utilsInstance = new Utils() Utils.metaClass.version = "3.0" utilsInstance.metaClass.released = true assert utilsInstance.version == "3.0" assert utilsInstance.released == true Adding properties using MetaClass
  37. 37. class Utils { } def utilsInstance = new Utils() Utils.metaClass.version = "3.0" utilsInstance.metaClass.released = true assert utilsInstance.version == "3.0" assert utilsInstance.released == true Adding properties using MetaClass
  38. 38. // Integer assert '42' == 42.toString() Integer.metaClass.toString = { delegate == 42 ? 'The answer to life, the universe and everything' : String.valueOf(delegate) } assert 42.toString() == 'The answer to life, the universe and everything' assert 100.toString() == '100' // Boolean assert false.toBoolean() == false Boolean.metaClass.toBoolean = { !delegate } assert false.toBoolean() == true Overriding methods using MetaClass
  39. 39. // Integer assert '42' == 42.toString() Integer.metaClass.toString = { delegate == 42 ? 'The answer to life, the universe and everything' : String.valueOf(delegate) } assert 42.toString() == 'The answer to life, the universe and everything' assert 100.toString() == '100' // Boolean assert false.toBoolean() == false Boolean.metaClass.toBoolean = { !delegate } assert false.toBoolean() == true Overriding methods using MetaClass
  40. 40. // Integer assert '42' == 42.toString() Integer.metaClass.toString = { delegate == 42 ? 'The answer to life, the universe and everything' : String.valueOf(delegate) } assert 42.toString() == 'The answer to life, the universe and everything' assert 100.toString() == '100' // Boolean assert false.toBoolean() == false Boolean.metaClass.toBoolean = { !delegate } assert false.toBoolean() == true Overriding methods using MetaClass
  41. 41. // Integer assert '42' == 42.toString() Integer.metaClass.toString = { delegate == 42 ? 'The answer to life, the universe and everything' : String.valueOf(delegate) } assert 42.toString() == 'The answer to life, the universe and everything' assert 100.toString() == '100' // Boolean assert false.toBoolean() == false Boolean.metaClass.toBoolean = { !delegate } assert false.toBoolean() == true Overriding methods using MetaClass
  42. 42. // Integer assert '42' == 42.toString() Integer.metaClass.toString = { delegate == 42 ? 'The answer to life, the universe and everything' : String.valueOf(delegate) } assert 42.toString() == 'The answer to life, the universe and everything' assert 100.toString() == '100' // Boolean assert false.toBoolean() == false Boolean.metaClass.toBoolean = { !delegate } assert false.toBoolean() == true Overriding methods using MetaClass
  43. 43. // Integer assert '42' == 42.toString() Integer.metaClass.toString = { delegate == 42 ? 'The answer to life, the universe and everything' : String.valueOf(delegate) } assert 42.toString() == 'The answer to life, the universe and everything' assert 100.toString() == '100' // Boolean assert false.toBoolean() == false Boolean.metaClass.toBoolean = { !delegate } assert false.toBoolean() == true Overriding methods using MetaClass
  44. 44. Categories ▷ MetaClass changes are “persistent” ▷ Change metaclass in confined code ▷ MOP modified only in the closure
  45. 45. Categories example class StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') } } use (StringUtils) { println "Lorem ipsum".truncate(5) } try { println "Lorem ipsum".truncate(5) } catch (MissingMethodException mme) { println mme } // Execution Lorem groovy.lang.MissingMethodException: No signature of method: java.lang.String.truncate() is applicable for argument types: (java.lang.Integer) values: [5] Possible solutions: concat(java.lang.String), take(int)
  46. 46. Categories example class StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') } } use (StringUtils) { println "Lorem ipsum".truncate(5) } try { println "Lorem ipsum".truncate(5) } catch (MissingMethodException mme) { println mme } // Execution Lorem groovy.lang.MissingMethodException: No signature of method: java.lang.String.truncate() is applicable for argument types: (java.lang.Integer) values: [5] Possible solutions: concat(java.lang.String), take(int)
  47. 47. Categories example class StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') } } use (StringUtils) { println "Lorem ipsum".truncate(5) } try { println "Lorem ipsum".truncate(5) } catch (MissingMethodException mme) { println mme } // Execution Lorem groovy.lang.MissingMethodException: No signature of method: java.lang.String.truncate() is applicable for argument types: (java.lang.Integer) values: [5] Possible solutions: concat(java.lang.String), take(int)
  48. 48. Categories example class StringUtils { static String truncate(String text, Integer length, Boolean overflow = false) { text.take(length) + (overflow ? '...' : '') } } use (StringUtils) { println "Lorem ipsum".truncate(5) } try { println "Lorem ipsum".truncate(5) } catch (MissingMethodException mme) { println mme } // Execution Lorem groovy.lang.MissingMethodException: No signature of method: java.lang.String.truncate() is applicable for argument types: (java.lang.Integer) values: [5] Possible solutions: concat(java.lang.String), take(int)
  49. 49. class FileBinaryCategory { def static leftShift(File file, URL url) { def input def output try { input = url.openStream() output = new BufferedOutputStream(new FileOutputStream(file)) output << input } finally { input?.close() output?.close() } } } Categories example (II)
  50. 50. class FileBinaryCategory { def static leftShift(File file, URL url) { def input def output try { input = url.openStream() output = new BufferedOutputStream(new FileOutputStream(file)) output << input } finally { input?.close() output?.close() } } } Categories example (II)
  51. 51. class FileBinaryCategory { def static leftShift(File file, URL url) { def input def output try { input = url.openStream() output = new BufferedOutputStream(new FileOutputStream(file)) output << input } finally { input?.close() output?.close() } } } Categories example (II) File tmpFile = File.createTempFile('tmp_', '') use (FileBinaryCategory) { tmpFile << "http://groovy.codehaus.org/images/groovy-logo-medium.png".toURL() } println tmpFile // Execution /tmp/tmp_7428855173238452155
  52. 52. class FileBinaryCategory { def static leftShift(File file, URL url) { def input def output try { input = url.openStream() output = new BufferedOutputStream(new FileOutputStream(file)) output << input } finally { input?.close() output?.close() } } } Categories example (II) File tmpFile = File.createTempFile('tmp_', '') use (FileBinaryCategory) { tmpFile << "http://groovy.codehaus.org/images/groovy-logo-medium.png".toURL() } println tmpFile // Execution /tmp/tmp_7428855173238452155
  53. 53. class FileBinaryCategory { def static leftShift(File file, URL url) { def input def output try { input = url.openStream() output = new BufferedOutputStream(new FileOutputStream(file)) output << input } finally { input?.close() output?.close() } } } Categories example (II) File tmpFile = File.createTempFile('tmp_', '') use (FileBinaryCategory) { tmpFile << "http://groovy.codehaus.org/images/groovy-logo-medium.png".toURL() } println tmpFile // Execution /tmp/tmp_7428855173238452155
  54. 54. class FileBinaryCategory { def static leftShift(File file, URL url) { def input def output try { input = url.openStream() output = new BufferedOutputStream(new FileOutputStream(file)) output << input } finally { input?.close() output?.close() } } } Categories example (II) File tmpFile = File.createTempFile('tmp_', '') use (FileBinaryCategory) { tmpFile << "http://groovy.codehaus.org/images/groovy-logo-medium.png".toURL() } println tmpFile // Execution /tmp/tmp_7428855173238452155
  55. 55. Extension modules ▷ JAR file that provides extra methods ▷ Meta-information file ▷ Put jar in classpath to enhance classes
  56. 56. // src/main/groovy/confess2015/StringUtilsExtension.groovy package confess2015 class StringUtilsExtension { static String truncate(String self, Integer length, Boolean overflow = false) { self.take(length) + (overflow ? '...' : '') } } package confess2015 import spock.lang.Specification class StringUtilsExtensionSpec extends Specification { void 'test trucate'() { expect: "Lorem" == "Lorem ipsum".truncate(5) "Lorem..." == "Lorem ipsum".truncate(5, true) } } // Execute with: // gradle build // groovy -cp build/libs/string- extensions-1.0.jar ExtensionExample1.groovy assert "Lorem..." == "Lorem ipsum". truncate(5, true) # src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule moduleName = string-utils-module moduleVersion = 0.1 extensionClasses = confess2015.StringUtilsExtension Extension modules example
  57. 57. Extension modules example // src/main/groovy/confess2015/StringUtilsExtension.groovy package confess2015 class StringUtilsExtension { static String truncate(String self, Integer length, Boolean overflow = false) { self.take(length) + (overflow ? '...' : '') } } # src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule moduleName = string-utils-module moduleVersion = 0.1 extensionClasses = confess2015.StringUtilsExtension package confess2015 import spock.lang.Specification class StringUtilsExtensionSpec extends Specification { void 'test trucate'() { expect: "Lorem" == "Lorem ipsum".truncate(5) "Lorem..." == "Lorem ipsum".truncate(5, true) } } // Execute with: // gradle build // groovy -cp build/libs/string- extensions-1.0.jar ExtensionExample1.groovy assert "Lorem..." == "Lorem ipsum". truncate(5, true)
  58. 58. Extension modules example // src/main/groovy/confess2015/StringUtilsExtension.groovy package confess2015 class StringUtilsExtension { static String truncate(String self, Integer length, Boolean overflow = false) { self.take(length) + (overflow ? '...' : '') } } package confess2015 import spock.lang.Specification class StringUtilsExtensionSpec extends Specification { void 'test trucate'() { expect: "Lorem" == "Lorem ipsum".truncate(5) "Lorem..." == "Lorem ipsum".truncate(5, true) } } // Execute with: // gradle build // groovy -cp build/libs/string- extensions-1.0.jar ExtensionExample1.groovy assert "Lorem..." == "Lorem ipsum". truncate(5, true) # src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule moduleName = string-utils-module moduleVersion = 0.1 extensionClasses = confess2015.StringUtilsExtension
  59. 59. Extension modules example // src/main/groovy/confess2015/StringUtilsExtension.groovy package confess2015 class StringUtilsExtension { static String truncate(String self, Integer length, Boolean overflow = false) { self.take(length) + (overflow ? '...' : '') } } package confess2015 import spock.lang.Specification class StringUtilsExtensionSpec extends Specification { void 'test trucate'() { expect: "Lorem" == "Lorem ipsum".truncate(5) "Lorem..." == "Lorem ipsum".truncate(5, true) } } // Execute with: // gradle build // groovy -cp build/libs/string- extensions-1.0.jar ExtensionExample1.groovy assert "Lorem..." == "Lorem ipsum". truncate(5, true) # src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule moduleName = string-utils-module moduleVersion = 0.1 extensionClasses = confess2015.StringUtilsExtension
  60. 60. // src/main/groovy/confess2015/StringUtilsExtension.groovy package confess2015 class StringUtilsExtension { static String truncate(String self, Integer length, Boolean overflow = false) { self.take(length) + (overflow ? '...' : '') } } package confess2015 import spock.lang.Specification class StringUtilsExtensionSpec extends Specification { void 'test trucate'() { expect: "Lorem" == "Lorem ipsum".truncate(5) "Lorem..." == "Lorem ipsum".truncate(5, true) } } // Execute with: // gradle build // groovy -cp build/libs/string- extensions-1.0.jar ExtensionExample1.groovy assert "Lorem..." == "Lorem ipsum". truncate(5, true) Extension modules example # src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule moduleName = string-utils-module moduleVersion = 0.1 extensionClasses = confess2015.StringUtilsExtension
  61. 61. Mixins ▷ “Bring in” or “mix in” implementations from multiple classes ▷ Calls first routed to mixed-in class ▷ Last mixin wins ▷ Not easily un-done
  62. 62. class SpidermanPower { String spiderSense() { "Using spider-sense..." } } Mixins example @Mixin([SpidermanPower]) class Person {} def person = new Person() assert person.spiderSense() == "Using spider-sense..." assert !(person instanceof SpidermanPower) Person.mixin SupermanPower assert person.fly() == "Flying..." assert !(person instanceof SupermanPower) class SupermanPower { String fly() { "Flying..." } }
  63. 63. @Mixin([SpidermanPower]) class Person {} def person = new Person() assert person.spiderSense() == "Using spider-sense..." assert !(person instanceof SpidermanPower) Person.mixin SupermanPower assert person.fly() == "Flying..." assert !(person instanceof SupermanPower) class SupermanPower { String fly() { "Flying..." } } class SpidermanPower { String spiderSense() { "Using spider-sense..." } } Mixins example
  64. 64. @Mixin([SpidermanPower]) class Person {} def person = new Person() assert person.spiderSense() == "Using spider-sense..." assert !(person instanceof SpidermanPower) Person.mixin SupermanPower assert person.fly() == "Flying..." assert !(person instanceof SupermanPower) class SupermanPower { String fly() { "Flying..." } } class SpidermanPower { String spiderSense() { "Using spider-sense..." } } Mixins example
  65. 65. @Mixin([SpidermanPower]) class Person {} def person = new Person() assert person.spiderSense() == "Using spider-sense..." assert !(person instanceof SpidermanPower) Person.mixin SupermanPower assert person.fly() == "Flying..." assert !(person instanceof SupermanPower) class SupermanPower { String fly() { "Flying..." } } class SpidermanPower { String spiderSense() { "Using spider-sense..." } } Mixins example
  66. 66. @Mixin([SpidermanPower]) class Person {} def person = new Person() assert person.spiderSense() == "Using spider-sense..." assert !(person instanceof SpidermanPower) Person.mixin SupermanPower assert person.fly() == "Flying..." assert !(person instanceof SupermanPower) class SpidermanPower { String spiderSense() { "Using spider-sense..." } } Mixins example class SupermanPower { String fly() { "Flying..." } }
  67. 67. @Mixin([SpidermanPower]) class Person {} def person = new Person() assert person.spiderSense() == "Using spider-sense..." assert !(person instanceof SpidermanPower) Person.mixin SupermanPower assert person.fly() == "Flying..." assert !(person instanceof SupermanPower) class SpidermanPower { String spiderSense() { "Using spider-sense..." } } Mixins example class SupermanPower { String fly() { "Flying..." } }
  68. 68. Traits ▷ Groovy 2.3+ ▷ Similar to Java 8 default methods ▷ Supported in JDK 6, 7 and 8 ▷ Stateful ▷ Composition over inheritance ▷ Documentation
  69. 69. class Person implements SpidermanPower {} def person = new Person() assert person.spiderSense() == "Using spider-sense..." assert person instanceof SpidermanPower def person2 = person.withTraits SupermanPower assert person2.fly() == "Flying..." assert person2 instanceof SupermanPower Traits example trait SpidermanPower { String spiderSense() { "Using spider-sense..." } } trait SupermanPower { String fly() { "Flying..." } }
  70. 70. class Person implements SpidermanPower {} def person = new Person() assert person.spiderSense() == "Using spider-sense..." assert person instanceof SpidermanPower def person2 = person.withTraits SupermanPower assert person2.fly() == "Flying..." assert person2 instanceof SupermanPower Traits example trait SpidermanPower { String spiderSense() { "Using spider-sense..." } } trait SupermanPower { String fly() { "Flying..." } }
  71. 71. class Person implements SpidermanPower {} def person = new Person() assert person.spiderSense() == "Using spider-sense..." assert person instanceof SpidermanPower def person2 = person.withTraits SupermanPower assert person2.fly() == "Flying..." assert person2 instanceof SupermanPower Traits example trait SpidermanPower { String spiderSense() { "Using spider-sense..." } } trait SupermanPower { String fly() { "Flying..." } }
  72. 72. class Person implements SpidermanPower {} def person = new Person() assert person.spiderSense() == "Using spider-sense..." assert person instanceof SpidermanPower def person2 = person.withTraits SupermanPower assert person2.fly() == "Flying..." assert person2 instanceof SupermanPower Traits example trait SpidermanPower { String spiderSense() { "Using spider-sense..." } } trait SupermanPower { String fly() { "Flying..." } }
  73. 73. MOP method synthesis
  74. 74. MOP Method Synthesis ▷ Dynamically figure out behaviour upon invocation ▷ It may not exist until it's called/executed ▷ “Intercept, Cache, Invoke” pattern
  75. 75. def p = new Person(name: 'Iván', age: 34) assert p.respondsTo('sayHi') assert p.respondsTo('sayHiTo', String) assert !p.respondsTo('goodbye') assert p.hasProperty('name') assert !p.hasProperty('country') Check for methods and properties class Person { String name Integer age String sayHi() { "Hi, my name is ${name} and I'm ${age}" } String sayHiTo(String name) { "Hi ${name}, how are you?" } }
  76. 76. def p = new Person(name: 'Iván', age: 35) assert p.respondsTo('sayHi') assert p.respondsTo('sayHiTo', String) assert !p.respondsTo('goodbye') assert p.hasProperty('age') assert !p.hasProperty('country') Check for methods and properties class Person { String name Integer age String sayHi() { "Hi, my name is ${name} and I'm ${age}" } String sayHiTo(String name) { "Hi ${name}, how are you?" } }
  77. 77. Check for methods and properties class Person { String name Integer age String sayHi() { "Hi, my name is ${name} and I'm ${age}" } String sayHiTo(String name) { "Hi ${name}, how are you?" } } def p = new Person(name: 'Iván', age: 35) assert p.respondsTo('sayHi') assert p.respondsTo('sayHiTo', String) assert !p.respondsTo('goodbye') assert p.hasProperty('age') assert !p.hasProperty('country')
  78. 78. Check for methods and properties class Person { String name Integer age String sayHi() { "Hi, my name is ${name} and I'm ${age}" } String sayHiTo(String name) { "Hi ${name}, how are you?" } } def p = new Person(name: 'Iván', age: 35) assert p.respondsTo('sayHi') assert p.respondsTo('sayHiTo', String) assert !p.respondsTo('goodbye') assert p.hasProperty('age') assert !p.hasProperty('country')
  79. 79. MethodMissing example ▷ Requirements: – Send notifications to users by different channels – +50 notifications – Not all notifications by all channels – Extensible and open to future modifications
  80. 80. MethodMissing example abstract class Channel { void sendNewFollower(String username, String follower) { } void sendNewMessage(String username, String msg) { } ... } class EmailChannel extends Channel { void sendNewFollower(String username, String follower) { println "Sending email notification to '${username}' for new follower '${follower}'" } void sendNewMessage(String username, String msg) { println "Sending email notification to '${username}' for new message '${msg}'" } } class MobilePushChannel extends Channel { void sendNewFollower(String username, String follower) { println "Sending mobile push notification to '${username}' for new follower '${follower}'" } }
  81. 81. MethodMissing example abstract class Channel { void sendNewFollower(String username, String follower) { } void sendNewMessage(String username, String msg) { } ... } class EmailChannel extends Channel { void sendNewFollower(String username, String follower) { println "Sending email notification to '${username}' for new follower '${follower}'" } void sendNewMessage(String username, String msg) { println "Sending email notification to '${username}' for new message '${msg}'" } } class MobilePushChannel extends Channel { void sendNewFollower(String username, String follower) { println "Sending mobile push notification to '${username}' for new follower '${follower}'" } }
  82. 82. MethodMissing example abstract class Channel { void sendNewFollower(String username, String follower) { } void sendNewMessage(String username, String msg) { } ... } class EmailChannel extends Channel { void sendNewFollower(String username, String follower) { println "Sending email notification to '${username}' for new follower '${follower}'" } void sendNewMessage(String username, String msg) { println "Sending email notification to '${username}' for new message '${msg}'" } } class MobilePushChannel extends Channel { void sendNewFollower(String username, String follower) { println "Sending mobile push notification to '${username}' for new follower '${follower}'" } }
  83. 83. MethodMissing example class NotificationService { List channels = [] def methodMissing(String name, args) { System.out.println "...methodMissing called for ${name} with args ${args}" // Generate the implementation def implementation = { Object[] methodArgs -> channels.each { channel -> def metaMethod = channel.metaClass.getMetaMethod(name, methodArgs) return metaMethod.invoke(channel, methodArgs) } } // Cache the implementation in the metaClass NotificationService instance = this instance.metaClass."$name" = implementation // Execute it! implementation(args) } }
  84. 84. MethodMissing example class NotificationService { List channels = [] def methodMissing(String name, args) { System.out.println "...methodMissing called for ${name} with args ${args}" // Generate the implementation def implementation = { Object[] methodArgs -> channels.each { channel -> def metaMethod = channel.metaClass.getMetaMethod(name, methodArgs) return metaMethod.invoke(channel, methodArgs) } } // Cache the implementation in the metaClass NotificationService instance = this instance.metaClass."$name" = implementation // Execute it! implementation(args) } }
  85. 85. MethodMissing example class NotificationService { List channels = [] def methodMissing(String name, args) { System.out.println "...methodMissing called for ${name} with args ${args}" // Generate the implementation def implementation = { Object[] methodArgs -> channels.each { channel -> def metaMethod = channel.metaClass.getMetaMethod(name, methodArgs) return metaMethod.invoke(channel, methodArgs) } } // Cache the implementation in the metaClass NotificationService instance = this instance.metaClass."$name" = implementation // Execute it! implementation(args) } } notificationService.sendNewFollower(...) notificationService.sendNewMessage(...)
  86. 86. MethodMissing example class NotificationService { List channels = [] def methodMissing(String name, args) { System.out.println "...methodMissing called for ${name} with args ${args}" // Generate the implementation def implementation = { Object[] methodArgs -> channels.each { channel -> def metaMethod = channel.metaClass.getMetaMethod(name, methodArgs) return metaMethod.invoke(channel, methodArgs) } } // Cache the implementation in the metaClass NotificationService instance = this instance.metaClass."$name" = implementation // Execute it! implementation(args) } }
  87. 87. MethodMissing example class NotificationService { List channels = [] def methodMissing(String name, args) { System.out.println "...methodMissing called for ${name} with args ${args}" // Generate the implementation def implementation = { Object[] methodArgs -> channels.each { channel -> def metaMethod = channel.metaClass.getMetaMethod(name, methodArgs) return metaMethod.invoke(channel, methodArgs) } } // Cache the implementation in the metaClass NotificationService instance = this instance.metaClass."$name" = implementation // Execute it! implementation(args) } }
  88. 88. MethodMissing example // Execution ...methodMissing called for sendNewFollower with args [John, Peter] Sending email notification to 'John' for new follower 'Peter' Sending mobile push notification to 'John' for new follower 'Peter' Sending email notification to 'Mary' for new follower 'Steve' Sending mobile push notification to 'Mary' for new follower 'Steve' ...methodMissing called for sendNewMessage with args [Iván, Hello!] Sending email notification to 'Iván' for new message 'Hello!' def notificationService = new NotificationService( channels: [new EmailChannel(), new MobilePushChannel()] ) assert !notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("John", "Peter") assert notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("Mary", "Steve") notificationService.sendNewMessage("Iván", "Hello!")
  89. 89. MethodMissing example // Execution ...methodMissing called for sendNewFollower with args [John, Peter] Sending email notification to 'John' for new follower 'Peter' Sending mobile push notification to 'John' for new follower 'Peter' Sending email notification to 'Mary' for new follower 'Steve' Sending mobile push notification to 'Mary' for new follower 'Steve' ...methodMissing called for sendNewMessage with args [Iván, Hello!] Sending email notification to 'Iván' for new message 'Hello!' def notificationService = new NotificationService( channels: [new EmailChannel(), new MobilePushChannel()] ) assert !notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("John", "Peter") assert notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("Mary", "Steve") notificationService.sendNewMessage("Iván", "Hello!")
  90. 90. MethodMissing example // Execution ...methodMissing called for sendNewFollower with args [John, Peter] Sending email notification to 'John' for new follower 'Peter' Sending mobile push notification to 'John' for new follower 'Peter' Sending email notification to 'Mary' for new follower 'Steve' Sending mobile push notification to 'Mary' for new follower 'Steve' ...methodMissing called for sendNewMessage with args [Iván, Hello!] Sending email notification to 'Iván' for new message 'Hello!' def notificationService = new NotificationService( channels: [new EmailChannel(), new MobilePushChannel()] ) assert !notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("John", "Peter") assert notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("Mary", "Steve") notificationService.sendNewMessage("Iván", "Hello!")
  91. 91. MethodMissing example // Execution ...methodMissing called for sendNewFollower with args [John, Peter] Sending email notification to 'John' for new follower 'Peter' Sending mobile push notification to 'John' for new follower 'Peter' Sending email notification to 'Mary' for new follower 'Steve' Sending mobile push notification to 'Mary' for new follower 'Steve' ...methodMissing called for sendNewMessage with args [Iván, Hello!] Sending email notification to 'Iván' for new message 'Hello!' def notificationService = new NotificationService( channels: [new EmailChannel(), new MobilePushChannel()] ) assert !notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("John", "Peter") assert notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("Mary", "Steve") notificationService.sendNewMessage("Iván", "Hello!")
  92. 92. MethodMissing example // Execution ...methodMissing called for sendNewFollower with args [John, Peter] Sending email notification to 'John' for new follower 'Peter' Sending mobile push notification to 'John' for new follower 'Peter' Sending email notification to 'Mary' for new follower 'Steve' Sending mobile push notification to 'Mary' for new follower 'Steve' ...methodMissing called for sendNewMessage with args [Iván, Hello!] Sending email notification to 'Iván' for new message 'Hello!' def notificationService = new NotificationService( channels: [new EmailChannel(), new MobilePushChannel()] ) assert !notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("John", "Peter") assert notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("Mary", "Steve") notificationService.sendNewMessage("Iván", "Hello!")class EmailChannel extends Channel { void sendNewFollower(String username, String follower) {…} void sendNewMessage(String username, String msg) {…} } class MobilePushChannel extends Channel { void sendNewFollower(String username, String follower) {…} }
  93. 93. MethodMissing example // Execution ...methodMissing called for sendNewFollower with args [John, Peter] Sending email notification to 'John' for new follower 'Peter' Sending mobile push notification to 'John' for new follower 'Peter' Sending email notification to 'Mary' for new follower 'Steve' Sending mobile push notification to 'Mary' for new follower 'Steve' ...methodMissing called for sendNewMessage with args [Iván, Hello!] Sending email notification to 'Iván' for new message 'Hello!' def notificationService = new NotificationService( channels: [new EmailChannel(), new MobilePushChannel()] ) assert !notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("John", "Peter") assert notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("Mary", "Steve") notificationService.sendNewMessage("Iván", "Hello!")
  94. 94. MethodMissing example // Execution ...methodMissing called for sendNewFollower with args [John, Peter] Sending email notification to 'John' for new follower 'Peter' Sending mobile push notification to 'John' for new follower 'Peter' Sending email notification to 'Mary' for new follower 'Steve' Sending mobile push notification to 'Mary' for new follower 'Steve' ...methodMissing called for sendNewMessage with args [Iván, Hello!] Sending email notification to 'Iván' for new message 'Hello!' def notificationService = new NotificationService( channels: [new EmailChannel(), new MobilePushChannel()] ) assert !notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("John", "Peter") assert notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("Mary", "Steve") notificationService.sendNewMessage("Iván", "Hello!")
  95. 95. MethodMissing example def notificationService = new NotificationService( channels: [new EmailChannel(), new MobilePushChannel()] ) assert !notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("John", "Peter") assert notificationService.respondsTo('sendNewFollower', String, String) notificationService.sendNewFollower("Mary", "Steve") notificationService.sendNewMessage("Iván", "Hello!") // Execution ...methodMissing called for sendNewFollower with args [John, Peter] Sending email notification to 'John' for new follower 'Peter' Sending mobile push notification to 'John' for new follower 'Peter' Sending email notification to 'Mary' for new follower 'Steve' Sending mobile push notification to 'Mary' for new follower 'Steve' ...methodMissing called for sendNewMessage with args [Iván, Hello!] Sending email notification to 'Iván' for new message 'Hello!'
  96. 96. 2. Compile-time metaprogramming
  97. 97. Compile-time metaprogramming ▷ Advance feature ▷ Analyze/modify program structure at compile time ▷ Cross-cutting features ▷ Write code that generates bytecode
  98. 98. AST and compilation ▷ AST: Abstract Syntax Tree ▷ AST modified during compilation ▷ Hook into the phases ▷ Initialization, Parsing, Conversion, Semantic analysis, Canonicalization, Instruction selection, Class generation, Output, Finalization
  99. 99. Global AST transformations
  100. 100. Global AST Transformations ▷ No annotation ▷ Meta-information file ▷ Applied to all code during compilation ▷ Any compilation phase ▷ Grails uses intensively in GORM
  101. 101. Local AST transformations
  102. 102. Local AST Transformations ▷ Annotate code ▷ No meta-information file ▷ Easy to debug
  103. 103. Steps to create local AST Interface AST Enjoy!
  104. 104. Local AST example package confess2015 import ... @Retention(RetentionPolicy.SOURCE) @Target([ElementType.TYPE]) @GroovyASTTransformationClass("confess2015.VersionASTTransformation") @interface Version { String value() } class VersionedClass { public static final String VERSION = "1.0" } import confess2015.Version @Version('1.0') class VersionedClass { }
  105. 105. Local AST example class VersionedClass { public static final String VERSION = "1.0" } package confess2015 import ... @Retention(RetentionPolicy.SOURCE) @Target([ElementType.TYPE]) @GroovyASTTransformationClass("confess2015.VersionASTTransformation") @interface Version { String value() } import confess2015.Version @Version('1.0') class VersionedClass { }
  106. 106. Local AST example class VersionedClass { public static final String VERSION = "1.0" } package confess2015 import ... @Retention(RetentionPolicy.SOURCE) @Target([ElementType.TYPE]) @GroovyASTTransformationClass("confess2015.VersionASTTransformation") @interface Version { String value() } import confess2015.Version @Version('1.0') class VersionedClass { }
  107. 107. Local AST example @GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS) class VersionASTTransformation extends AbstractASTTransformation { @Override public void visit(final ASTNode[] nodes, final SourceUnit source) { if (nodes.length != 2) { return } if (nodes[0] instanceof AnnotationNode && nodes[1] instanceof ClassNode) { def annotation = nodes[0] def version = annotation.getMember('value') if (version instanceof ConstantExpression) { nodes[1].addField('VERSION', ACC_PUBLIC | ACC_STATIC | ACC_FINAL, ClassHelper.STRING_TYPE, version) } else { source.addError(new SyntaxException("Invalid value for annotation", annotation.lineNumber, annotation.columnNumber)) } } } }
  108. 108. Local AST example @GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS) class VersionASTTransformation extends AbstractASTTransformation { @Override public void visit(final ASTNode[] nodes, final SourceUnit source) { if (nodes.length != 2) { return } if (nodes[0] instanceof AnnotationNode && nodes[1] instanceof ClassNode) { def annotation = nodes[0] def version = annotation.getMember('value') if (version instanceof ConstantExpression) { nodes[1].addField('VERSION', ACC_PUBLIC | ACC_STATIC | ACC_FINAL, ClassHelper.STRING_TYPE, version) } else { source.addError(new SyntaxException("Invalid value for annotation", annotation.lineNumber, annotation.columnNumber)) } } } }
  109. 109. Local AST example // Execute with: // gradle build // groovy -cp build/libs/add-version-1.0.jar LocalASTExample.groovy import confess.Version @Version('1.0') class VersionedClass { } println VersionedClass.VERSION // Execution 1.0
  110. 110. Local AST example // Execute with: // gradle build // groovy -cp build/libs/add-version-1.0.jar LocalASTExample.groovy import confess.Version @Version('1.0') class VersionedClass { } println VersionedClass.VERSION // Execution 1.0
  111. 111. Local AST example // Execute with: // gradle build // groovy -cp build/libs/add-version-1.0.jar LocalASTExample.groovy import confess.Version @Version('1.0') class VersionedClass { } println VersionedClass.VERSION // Execution 1.0
  112. 112. 3. Why we should use metaprogramming?
  113. 113. Let’s review some concepts Metaprogramming out-of-the box Easy and very powerful Write better code Add behaviour easily Take advantage of this power Because Groovy, it's groovy
  114. 114. With great power comes great responsibility
  115. 115. Thanks! Any questions? @ilopmar lopez.ivan@gmail.com https://github.com/lmivan Iván López http://kcy.me/208wq

×