Groovy 2.0 and beyond Guillaume Laforge Groovy Project Manager SpringSource / VMware @glaforge
May 17, 2015
Groovy 2.0and beyondGuillaume LaforgeGroovy Project ManagerSpringSource / VMware@glaforge
Guillaume Laforge
• Groovy Project Manager at VMware
• Initiator of the Grails framework
• Creator of the Gaelyk
• Co-author of Groovy in Action
• Follow me on...
• My blog: http://glaforge.appspot.com
• Twitter: @glaforge
• Google+: http://gplus.to/glaforge
A blossomingECOSYSTEM
GV
M
GVM
GVMGROOVYENVIRONMENTMANAGER
GVM: Groovy enVironment Manager
• New kid in town!
• http://gvmtool.net/ — @gvmtool
• Manage parallel versions of various SDKs
• Currently supports
• Groovy, Grails, Griffon, Gradle, Vert.x
• On Linux, MacOS, Cygwin, Solaris, FreeBSD
GROOVY
Groovy 2.0 bird’s eye view
Java 7Project
Coin
Invoke Dynamic
A more modular Groovy
Static Type Checking
Static Compilation
Groovy 2.0 bird’s eye view
A more modular Groovy
Groovy Modularity
• Groovy’s « all » JAR weighs in at 6 MB
•Nobody needs everything
• Template engine, Ant scripting, Swing UI building...
• Provide a smaller core
• and several smaller JARs per feature
• Provide hooks for setting up DGM methods,
The new JARs
• A smaller JAR: 3MB
• Modules
– console– docgenerator– groovydoc– groovysh– ant– bsf
– jsr-223– jmx– sql– swing– servlet– templates
– test– testng– json– xml
The new JARs
• A smaller JAR: 3MB
• Modules
– console– docgenerator– groovydoc– groovysh– ant– bsf
– jsr-223– jmx– sql– swing– servlet– templates
– test– testng– json– xml
Extension modules
• Create your own module
• contribute instance extension methods
package foo
class StringExtension { static introduces(String self, String name) { "Hi ${name), I’m ${self}" }}
// usage: "Guillaume".introduces("Cédric")
Extension modules
• Create your own module
• contribute instance extension methods
package foo
class StringExtension { static introduces(String self, String name) { "Hi ${name), I’m ${self}" }}
// usage: "Guillaume".introduces("Cédric")
Same structureas Categories
Extension modules
• Create your own module
• contribute class extension methods
package foo
class StaticStringExtension { static hi(String self) { "Hi!" }}
// usage: String.hi()
Extension module descriptor
• Descriptor in: META-INF/services/org.codehaus.groovy.runtime.ExtensionModule
moduleName = stringExtensionsmoduleVersion = 1.0// comma-‐separated list of classesextensionClasses = foo.StringExtension// comma-‐separated list of classesstaticExtensionClasses = foo.StaticStringExtension
Groovy 2.0 bird’s eye view
Java 7Project
Coin
Invoke Dynamic
A more modular Groovy
Static Type Checking
Static Compilation
Groovy 2.0 bird’s eye view
Java 7Project
Coin
Invoke Dynamic
Binary literals
• We had decimal, octal and hexadecimal notations for number literals
• We can now use binary representations too
int x = 0b10101111assert x == 175 byte aByte = 0b00100001assert aByte == 33 int anInt = 0b1010000101000101assert anInt == 41285
Underscore in literals
• Now we can also add underscores in number literals for more readability
long creditCardNumber = 1234_5678_9012_3456Llong socialSecurityNumbers = 999_99_9999Lfloat monetaryAmount = 12_345_132.12long hexBytes = 0xFF_EC_DE_5Elong hexWords = 0xFFEC_DE5Elong maxLong = 0x7fff_ffff_ffff_ffffLlong alsoMaxLong = 9_223_372_036_854_775_807Llong bytes = 0b11010010_01101001_10010100_10010010
Multicatch
• One block for multiple exception caught
• rather than duplicating the block
try { /* ... */} catch(IOException | NullPointerException e) { /* one block to treat 2 exceptions */}
InvokeDynamic
•Groovy 2.0 supports JDK 7’s invokeDynamic
• compiler has a flag for compiling against JDK 7
• might use the invokeDynamic backport for < JDK 7
• Benefits
• more runtime performance!
• at least as fast as current « dynamic » Groovy
• in the long run, will allow us to get rid of code!
• call site caching, thanks to MethodHandles
• metaclass registry, thanks to ClassValues
• will let the JIT inline calls more easily
Groovy 2.0 bird’s eye view
Java 7Project
Coin
Invoke Dynamic
A more modular Groovy
Static Type Checking
Static Compilation
Groovy 2.0 bird’s eye view
Static Type Checking
Static Compilation
Static Type Checking
• Goal: make the Groovy compiler « grumpy »!
• and throw compilation errors (not at runtime)
•Not everybody needs dynamic features all the time
• think Java libraries scripting
• Grumpy should...
• tell you about your method or variable typos
• complain if you call methods that don’t exist
• shout on assignments of wrong types
• infer the types of your variables
• figure out GDK methods
Typos in a variable or method
import groovy.transform.TypeChecked void method() {} @TypeChecked test() { // Cannot find matching method metthhoood() metthhoood() def name = "Guillaume" // variable naamme is undeclared println naamme}
Typos in a variable or method
import groovy.transform.TypeChecked void method() {} @TypeChecked test() { // Cannot find matching method metthhoood() metthhoood() def name = "Guillaume" // variable naamme is undeclared println naamme}
Compilationerrors!
Typos in a variable or method
import groovy.transform.TypeChecked void method() {} @TypeChecked test() { // Cannot find matching method metthhoood() metthhoood() def name = "Guillaume" // variable naamme is undeclared println naamme}
Compilationerrors!
Annotation can be atclass or method level
Wrong assignments
// cannot assign value of type... to variable...int x = new Object()Set set = new Object() def o = new Object()int x = o String[] strings = ['a','b','c']int str = strings[0] // cannot find matching method plus()int i = 0i += '1'
Wrong assignments
// cannot assign value of type... to variable...int x = new Object()Set set = new Object() def o = new Object()int x = o String[] strings = ['a','b','c']int str = strings[0] // cannot find matching method plus()int i = 0i += '1'
Compilationerrors!
Wrong return types
// checks if/else branch return values@TypeCheckedint method() { if (true) { 'String' } else { 42 }}// works for switch/case & try/catch/finally // transparent toString() implied@TypeCheckedString greeting(String name) { def sb = new StringBuilder() sb << "Hi " << name}
Wrong return types
// checks if/else branch return values@TypeCheckedint method() { if (true) { 'String' } else { 42 }}// works for switch/case & try/catch/finally // transparent toString() implied@TypeCheckedString greeting(String name) { def sb = new StringBuilder() sb << "Hi " << name}
Compilationerror!
Type inference
@TypeChecked test() { def name = " Guillaume " // String type infered (even inside GString) println "NAME = ${name.toUpperCase()}" // Groovy GDK method support // (GDK operator overloading too) println name.trim() int[] numbers = [1, 2, 3] // Element n is an int for (int n in numbers) { println n }}
Statically checked & dynamic methods
@TypeCheckedString greeting(String name) { // call method with dynamic behavior // but with proper signature generateMarkup(name.toUpperCase())} // usual dynamic behaviorString generateMarkup(String name) { def sw = new StringWriter() new MarkupBuilder(sw).html { body { div name } } sw.toString()}
Instanceof checks
@TypeChecked void test(Object val) {
if (val instanceof String) { println val.toUpperCase() } else if (val instanceof Number) { println "X" * val.intValue() }}
Instanceof checks
@TypeChecked void test(Object val) {
if (val instanceof String) { println val.toUpperCase() } else if (val instanceof Number) { println "X" * val.intValue() }}
No needfor casts
Instanceof checks
@TypeChecked void test(Object val) {
if (val instanceof String) { println val.toUpperCase() } else if (val instanceof Number) { println "X" * val.intValue() }}
No needfor casts
Can call String#multiply(int)from the Groovy Development Kit
Lowest Upper Bound
• Represents the lowest « super » type classes have in common
• may be virtual (aka « non-denotable »)
@TypeChecked test() { // an integer and a BigDecimal return [1234, 3.14]}
Lowest Upper Bound
• Represents the lowest « super » type classes have in common
• may be virtual (aka « non-denotable »)
@TypeChecked test() { // an integer and a BigDecimal return [1234, 3.14]}
Inferred return type:List<Number & Comparable>
Lowest Upper Bound
• Represents the lowest « super » type classes have in common
• may be virtual (aka « non-denotable »)
@TypeChecked test() { // an integer and a BigDecimal return [1234, 3.14]}
Inferred return type:List<Number & Comparable>
Flow typing
• Static type checking shouldn’t complain even for bad coding practicies which work without type checks
@TypeChecked test() { def var = 123 // inferred type is int int x = var // var is an int var = "123" // assign var with a String
x = var.toInteger() // no problem, no need to cast
var = 123 x = var.toUpperCase() // error, var is int!}
Gotcha: static checking vs dynamic
•Type checking works at compile-time
• adding @TypeChecked doesn’t change behavior
• do not confuse with static compilation
• Most dynamic features cannot be type checked
• metaclass changes, categories
• dynamically bound variables (ex: script’s binding)
• But compile-time metaprogramming works
• as long as proper type information is defined
Gotcha: runtime metaprogramming
@TypeChecked void test() { Integer.metaClass.foo = {} 123.foo()}
Gotcha: runtime metaprogramming
@TypeChecked void test() { Integer.metaClass.foo = {} 123.foo()}
Not allowed:metaClass property
is dynamic
Gotcha: runtime metaprogramming
@TypeChecked void test() { Integer.metaClass.foo = {} 123.foo()}
Not allowed:metaClass property
is dynamic
Method notrecognized
Gotcha: explicit type for closures
@TypeChecked test() { ["a", "b", "c"].collect { it.toUpperCase() // Not OK }}
Gotcha: explicit type for closures
@TypeChecked test() { ["a", "b", "c"].collect { String it -‐> it.toUpperCase() // OK, it’s a String }}
Closure shared variables
@TypeChecked test() { def var = "abc" def cl = { var = new Date() } if (random) cl() var.toUpperCase() // Not OK!}
Closure shared variables
@TypeChecked test() { def var = "abc" def cl = { var = new Date() } if (random) cl() var.toUpperCase() // Not OK!}
var assigned in the closure: « shared closure variable »
Closure shared variables
@TypeChecked test() { def var = "abc" def cl = { var = new Date() } if (random) cl() var.toUpperCase() // Not OK!}
var assigned in the closure: « shared closure variable »
Impossible to ensure the
assignment really happens
Closure shared variables
@TypeChecked test() { def var = "abc" def cl = { var = new Date() } if (random) cl() var.toUpperCase() // Not OK!}
var assigned in the closure: « shared closure variable »
Impossible to ensure the
assignment really happens
Only methods of the most specific compatible type (LUB) are allowed by the type checker
Closure shared variables
class A { void foo() {} }class B extends A { void bar() {} }
@TypeChecked test() { def var = new A() def cl = { var = new B() } if (random) cl() var.foo() // OK!}
Closure shared variables
class A { void foo() {} }class B extends A { void bar() {} }
@TypeChecked test() { def var = new A() def cl = { var = new B() } if (random) cl() var.foo() // OK!}
var is at least an instance of A
Static Compilation
• Given your Groovy code can be type checked...we can as well compile it « statically »
• ie. generate the same byte code as javac
• Also interesting for those stuck in JDK < 7 to benefit from performance improvements
Static Compilation: advantages
• You gain:
•Type safety
• thanks to static type checking
• static compilation builds upon static type checking
• Faster code
• as close as possible to Java’s performance
•Code immune to « monkey patching »
• metaprogramming badly used can interfere with framework code
• Smaller bytecode size
Static Compilation: disadvantages
• But you loose:
• Dynamic features
• metaclass changes, categories, etc.
• Dynamic method dispatch
• although as close as possible to « dynamic » Groovy
Statically compiled & dynamic methods
@CompileStaticString greeting(String name) { // call method with dynamic behavior // but with proper signature generateMarkup(name.toUpperCase())} // usual dynamic behaviorString generateMarkup(String name) { def sw = new StringWriter() new MarkupBuilder(sw).html { body { div name } } sw.toString()}
What about performance?
• Comparisons between:
• Java
• Groovy static compilation (Groovy 2.0)
• Groovy with primitive optimizations (Groovy 1.8+)
• Groovy without optimizations (Groovy 1.7)
What about performance?
Fibonacci Pi (π) quadrature
Binarytrees
Java
Staticcompilation
Primitive optimizations
No prim.optimizations
191 ms 97 ms 3.6 s
197 ms 101 ms 4.3 s
360 ms 111 ms 23.7 s
2590 ms 3220 ms 50.0 s1.7
1.8
2.0
Back to the future...
Full invoke dynamic support
• In Groovy 2.0, not all call paths were going through invoke dynamic calls
• essentially method calls only
• still used call site caching techniques
•On JDK 7 with the « indy » JAR, Groovy 2.1 uses invoke dynamic everywhere
• On JDK < 7, still uses call site caching
Groovy
2.1
@DelegatesTo
• Static type checking works nicely for certain Domain-Specific Languages
• command chains, extension methods, etc.
• But for changes of delegation within closures, it’s not helping
• often used by DSLs like within Gradle
• Enters @DelegatesTo!
Groovy
2.1
@DelegatesTo Groovy
2.1
class ExecSpec { void foo()}
@DelegatesTo Groovy
2.1
class ExecSpec { void foo()}
void exec(ExecSpec sp, Closure c) { c.delegate = sp c()}
@DelegatesTo Groovy
2.1
class ExecSpec { void foo()}
void exec(ExecSpec sp, Closure c) { c.delegate = sp c()}
exec(spec) { foo()}
@DelegatesTo Groovy
2.1
class ExecSpec { void foo()}
void exec(ExecSpec sp, Closure c) { c.delegate = sp c()}
exec(spec) { foo()}
Static type checker doesn’t know about that foo() method
@DelegatesTo Groovy
2.1
void exec(ExecSpec sp, Closure c) { c.delegate = sp c()}
@DelegatesTo Groovy
2.1
void exec(ExecSpec sp, Closure c) { c.delegate = sp c()}
Annotate with:@DelegatesTo(ExecSpec)
@DelegatesTo
• With a special delegation strategy
Groovy
2.1
void exec(ExecSpec sp, Closure c) { c.delegate = sp c.resolveStrategy = DELEGATE_FIRST c()}
@DelegatesTo
• With a special delegation strategy
Groovy
2.1
void exec(ExecSpec sp, Closure c) { c.delegate = sp c.resolveStrategy = DELEGATE_FIRST c()}
Annotate with:@DelegatesTo(value = ExecSpec, strategy = DELEGATE_FIRST)
@DelegatesTo
• Use Target to specify the precise argument to delegate to
Groovy
2.1
void exec(ExecSpec sp, Closure c) { c.delegate = sp c()}
@DelegatesTo
• Use Target to specify the precise argument to delegate to
Groovy
2.1
void exec(ExecSpec sp, Closure c) { c.delegate = sp c()}
@DelegatesTo.Target("id")
@DelegatesTo
• Use Target to specify the precise argument to delegate to
Groovy
2.1
void exec(ExecSpec sp, Closure c) { c.delegate = sp c()}
@DelegatesTo.Target("id") @DelegatesTo(target = "id")
@DelegatesTo
• For DSLs using Groovy closure delegation
• Great for...
• documenting APIs
• IDE integration
• code completion, code navigation...
• working nicely with static type checking and static compilation
Groovy
2.1
Custom type checking
• You can make Groovy’s static type checking even smarter, with your own smarts!
• even smarter than Java’s :-)
• Create your own type checker extension
Groovy
2.1
@TypeChecked(extensions = 'MyExtension.groovy')void exec() { // code to be further checked...}
• Your own extension has access to an event-based API
Custom type checking
• onMethodSelection
• afterMethodCall
• beforeMethodCall
• afterVisitMethod
• beforeVisitMethod
• methodNotFound
• unresolvedVariable
• unresolvedProperty
• unresolvedAttribute
• incompatibleAssignment
Groovy
2.1
Custom type checking
onMethodSelection { expr, method -‐> ... }afterMethodCall { mc -‐> ... }unresolvedVariable { var -‐> ... }methodNotFound { receiver, name, argList, argTypes, call -‐> ... }methodNotFound { receiver, name, argList, argTypes, call -‐> ... }incompatibleAssignment { lhsType, rhsType, expr -‐> ... }
Groovy
2.1
MyExtension.groovy
Custom type checking
onMethodSelection { expr, method -‐> ... }afterMethodCall { mc -‐> ... }unresolvedVariable { var -‐> ... }methodNotFound { receiver, name, argList, argTypes, call -‐> ... }methodNotFound { receiver, name, argList, argTypes, call -‐> ... }incompatibleAssignment { lhsType, rhsType, expr -‐> ... }
Know your Groovy AST API well ;-)
Groovy
2.1
MyExtension.groovy
Custom type checking
onMethodSelection { expr, method -‐> ... }afterMethodCall { mc -‐> ... }unresolvedVariable { var -‐> ... }methodNotFound { receiver, name, argList, argTypes, call -‐> ... }methodNotFound { receiver, name, argList, argTypes, call -‐> ... }incompatibleAssignment { lhsType, rhsType, expr -‐> ... }
Know your Groovy AST API well ;-)
Groovy
2.1
MyExtension.groovyDoesn’t need
to be compiled
Alias annotations
• Ability to create meta-annotations combining and/or parameterizing other annotations
• Works also for local AST transformations and their annotations
Groovy
2.1
Alias annotations
@Immutable@ToString(excludes = ["age"])@AnnotationCollector@interface MyAlias {}
Groovy
2.1
Alias annotations
@Immutable@ToString(excludes = ["age"])@AnnotationCollector@interface MyAlias {}
Groovy
2.1
The « collector »
Alias annotations
@Immutable@ToString(excludes = ["age"])@AnnotationCollector@interface MyAlias {}
Groovy
2.1
The « collector »
Collected annotations
Alias annotations
@Immutable@ToString(excludes = ["age"])@AnnotationCollector@interface MyAlias {}
Groovy
2.1
The « collector »
Your own custom alias name
Collected annotations
Alias annotations
@Immutable@ToString(excludes = ["age"])@AnnotationCollector@interface MyAlias {}
Groovy
2.1
@MyAliasclass Foo { String name int age}
The « collector »
Your own custom alias name
Collected annotations
Alias annotations
@Immutable@ToString(excludes = ["age"])@AnnotationCollector@interface MyAlias {}
Groovy
2.1
@MyAliasclass Foo { String name int age}
The « collector »
Your own custom alias name
Use your concise alias
Collected annotations
Take control of your Groovy!
• Groovy 1.8 introduced compilation customizers
• add imports, AST xforms, secure the AST...
• With static type checking and static compilation, we received feedback from people wanting them applied « by default »
• Allow to instruct the groovyc compiler with a configuration script
• groovyc -configurator compConf.groovy Foo.groovy
Groovy
2.1
Take control of your Groovy!
• Add a default @ToString transform
•
Groovy
2.1
import groovy.transform.ToStringimport org.codehaus.groovy.control.customizers .ASTTransformationCustomizer
config.addCompilationCustomizer( new ASTTransformationCustomizer(ToString))
Take control of your Groovy!
• Add a default @ToString transform
•
Groovy
2.1
import groovy.transform.ToStringimport org.codehaus.groovy.control.customizers .ASTTransformationCustomizer
config.addCompilationCustomizer( new ASTTransformationCustomizer(ToString))
Implicit «config» variable representing a CompilationConfiguration instance
Take control of your Groovy!
• You can use a builder syntax:
Groovy
2.1
config.customizers { // apply to MyBean.groovy source(basename: 'MyBean') { ast(ToString) }}
Take control of your Groovy!
• You can use a builder syntax:
Groovy
2.1
config.customizers { // apply to MyBean.groovy source(basename: 'MyBean') { ast(ToString) }}
config.customizers { // apply to *.gbean files source(extension: '.gbean') { ast(ToString) }}
Take control of your Groovy!
• You can use a builder syntax:
Groovy
2.1
config.customizers { // apply to MyBean.groovy source(basename: 'MyBean') { ast(ToString) }}
config.customizers { // apply to *.gbean files source(extension: '.gbean') { ast(ToString) }}
config.customizers { // custom filter logic // with compilation units source(unitValidator: { unit -‐> ... }) { ast(ToString) }}
Roadmap
2014 2014 20132012
Groovy 2.1 Groovy 4.0
Groovy 3.0Groovy 2.0
Roadmap
2014 2014 20132012
Groovy 2.1 Groovy 4.0
Groovy 3.0Groovy 2.0
Roadmap
2014 2014 20132012
Groovy 2.1 Groovy 4.0
Groovy 3.0Groovy 2.0
End of 2012... and the world?
2012GGeX
End of the world
End of 2012... and the world?
Dec17-23
2012GGeX
End of the world
End of 2012... and the world?
Dec17-23
2012GGeX
Groovy 2.1 beta
End of the world
End of 2012... and the world?
Dec17-23
2012GGeX
Groovy 2.1 beta
Recoveringfrom GGeX
End of the world
End of 2012... and the world?
Dec17-23
2012GGeX
Groovy 2.1 beta
Recoveringfrom GGeX
End of the world
End of 2012... and the world?
Dec17-23
2012GGeX
Groovy 2.1 beta
Recoveringfrom GGeX
Christmas
End of the world
End of 2012... and the world?
Dec17-23
2012GGeX
Groovy 2.1 beta
Recoveringfrom GGeX
Christmas
End of the world
End of 2012... and the world?
Dec17-23
2012GGeX
Groovy 2.1 beta
Recoveringfrom GGeX
Christmas
End of the world
End of 2012... and the world?
Dec17-23
2012GGeX
Groovy 2.1 beta
Recoveringfrom GGeX
Christmas
Recoveringagain !
End of the world
Happy New Year!
Jan7-13
Happy New Year!
Jan7-13
Groovy 2.1 RC
Happy New Year!
Jan14-21
Jan7-13
Groovy 2.1 RC
Happy New Year!
Jan14-21
Jan7-13
Groovy 2.1 RC
Wait for feedback
Happy New Year!
Jan14-21
Jan7-13
Groovy 2.1 RC
Wait for feedback
Potential 2nd RC
Happy New Year!
Jan22-29
Jan14-21
Jan7-13
Groovy 2.1 RC
Wait for feedback
Potential 2nd RC
Happy New Year!
Jan22-29
Jan14-21
Jan7-13
Groovy 2.1 RC
Wait for feedback
Groovy 2.1Final !
Potential 2nd RC
Back to 2013 and beyond
2014 2014 20132012
Groovy 2.1 Groovy 4.0
Groovy 3.0Groovy 2.0
The infamous
MOP
2Groovy
3.0
Antlr 4grammar
Groovy
4.0
λJDK 8
Lambdas
Groovy
4.0
Summary (1/2)
• As always, a rich and blossoming ecosystem
•Groovy 2.0
• more modularity
• static theme
• static type checking
• static compilation
• JDK 7 theme
• invoke dynamic support
• project coin syntax enhancements
Summary (2/2)
•Groovy 2.1
• complete indy support
• @DelegatesTo
• custom type checking for your DSLs
• alias annotation
• And beyond...
• a new MOP (Meta-Object Protocol)
• a new grammar with Antlr 4
• JDK 8 lambda support
Thank you!
Guillaume Laforge
Head of Groovy Development
Email: [email protected]
Twitter: @glaforge
Google+: http://gplus.to/glaforge
Blog: http://glaforge.appspot.com
Picture credits
• Londonhttp://www.londonup.com/media/1251/Tower-Bridge-london-582331_1024_768.jpg
• cherry blossomhttp://wallpaperswide.com/cherry_blossom_3-wallpapers.html
• NKOTBhttp://images1.fanpop.com/images/photos/2300000/nkotb-new-kids-on-the-block-2314664-1280-960.jpg
• USA todayhttp://www.adams-pr.com/images/uploads/USA_Today_logo.jpg
• back to the futurehttp://davidhiggerson.files.wordpress.com/2012/02/back-to-the-future-delorean.jpg
• magnifying glasshttp://www.renders-graphiques.fr/image/upload/normal/loupe.png
• Santa Claushttp://icons.iconarchive.com/icons/fasticon/santa-claus/256/Happy-SantaClaus-icon.png
• Champagnehttp://reallife101blogs.files.wordpress.com/2010/11/champagne_glasses2.jpg
• that’s all folkshttp://4.bp.blogspot.com/-wJxosualm48/T4M_spcUUjI/AAAAAAAAB8E/njfLjNZQdsc/s1600/thats-all-folks.jpg
• MOPhttp://imagethumbnails.milo.com/024/913/894/trimmed/24913521_25989894_trimmed.jpg
• grammarhttp://edudemic.com/wp-content/uploads/2012/11/connected-learner-grammar.jpg