On 05.10.2016 21:41, Graeme Rocher wrote:
[...]
Then for (2)... methodMissing(String, Closure) will not get you anywhere in
terms of type checking. This is for methods "foo{...}" or "bar{...}", where
bar and foo are the String. If you write foobar instead, there is nothing
preventing compilation and failure at runtime. And it does not scale: The
more methodMissing you have, the less likely there is the chance for a
compilation error due to builder method argument. And I actually still fail
to see how you want to use this with @DelegateTo(Map), unless all builder
methods of that form will have these properties.
I disagree. It is quite a common pattern to have dynamic methods, but
type checked properties. See for example MarkupTemplateEngine which
has special extensions to allow model variables to be type checked,
whilst method dispatch for builder methods remains dynamic:
which means you can have type checking for model variables, but not for
builder methods. But I guess I now finally understand how you want to
use the @DelegatesTo map idea. You do not want this for the builder
methods, you want this for the property access used in the builder!
[...]
The goal here is to be able to type check code like:
String title = "Blah"
mkp.html {
body {
h1( title.toUpperCas() ) // compilation error here
}
}
To implement the above you would implement "methodMissing" but not
"propertyMissing" thus you are able to achieve type checking for
properties and local variables when using the builder, so I disagree
with the statement that we "will not get anywhere in terms of type
checking".
based on this I would make the following (to be improved) suggestion:
interface AutoDelegationBuilder{}
class MyBuilder implements AutoDelegationBuilder {
String title
def methodMissing(String name, args) {
...
}
}
def mkp = new MyBuilder(title:"Blah")
mkp.html {
body {
h1( title.toUpperCas() ) // compilation error here
}
}
a transform would then do the following things... all method calls
without explicit receiver will be mapped to a fitting method in
MyBuilder or methodMissing if no such method exists and methodMissing
exists. Similar for properties. This logic could be used for static
compilation as well as for non-static compilation to some extend (not
correctly resolving overloads for example). In both cases we get a fast
builder and you will get something with proper error messages at compile
time for the @CompileStatic variant. The advantage is that the user can
easily choose how the builder should work. You can go for example 100%
static if you want to use methods for all the possible markup elements
and not give a methodMissing. Maybe we can separate methodMissing part
from the actual methods part to avoid mismatches in the markup as well.
Also possible would be to have the a model class and use it like this:
def myModel = new MyModel(title:"Blah")
def mkp = new MyGeneralizedBuilder(myModel)
mkp.html {
body {
h1( title.toUpperCas() ) // compilation error here
}
}
Or the other way around... new BuilderModel(fallback)... where
BuilderModel has the model data and fallback the optional methodMissing
part. Many things are possible if we know what we actually want.
And I would consider actually transforming the blocks to something that
does not use Closure. Using Closure here is totally unneeded overhead.
You have only version of a doCall method, you have no gain from using
thisObject, owner or delegate, especially since they are both ignored,
you have no need for state or any MOP. It would be easy to compile the
structure directly into a helper class where each block is realized by a
method. You really only need to resolve the local variables... probably
do the closure shared transformation ourselves or change the compiler to
make that more easy. I can actually see several implementation
techniques for this... in theory you could even reuse methods.. well
ideas, ideas ;)
And we achieve your goal of not having to write a type checking
extension. And I think for something like gradle it would also be
interesting to combine several builder - which would need to go a lot
further than just this.
Well actually... if we give up on Closure, and think of the case with
missingMethod, where all methods must be mapped to existing ones, we
could let the user define interfaces.. or use one interface, that
operates on a AutoDelegation context type, then we could use the
extension method mechanism we have (or static categories in the future)
to realize arbitrary mixes of builders.
Hey, if gradle wants to hire Canoo for implementing this I can do it ;)
Ahem... sorry ;)
bye Jochen
PS: Of course I did totally ignore here the part of '''... we've
explicitly discarded the idea of doing "Scala-like" dynamic in compile
static mode'''