I prefer to support different _methodMissing_ methods taking different arguments, because they can participate in method selection and return a more specific type.
Something to consider too: @DelegatesTo is not on par with @ClosureParams wrt to the types it can express. In particular, it doesn't have that "String" representation which supports more complex types, in particular the generics (you cannot write @DelegatesTo(List<String>). 2016-10-06 8:31 GMT+02:00 Graeme Rocher <graeme.roc...@gmail.com>: > Hi Jochen, > > Yeah that sounds good, I like the idea of an interface or trait, that > makes it easier to optimize the performance of the compiler. I also > like the idea of avoiding the overhead of closure method dispatch. To > make it more flexible we could like I said allow specifying the > delegate via @DelegatesTo in a methodMissing signature: > > def methodMissing(String name, @DelegatesTo(MyDelegate) Closure callable) > > The other option like you say is to use existing methods. For example > in StreamingJsonBuilder you have methods like: > > public void call(String name, > @DelegatesTo(StreamingJsonDelegate.class) Closure c) throws > IOException { > .. > } > > We could add something like a @BuilderMethod annotation: > > @BuilderMethod > public void call(String name, > @DelegatesTo(StreamingJsonDelegate.class) Closure c) throws > IOException { > .. > } > > And then if you do: > > json.foo { > > } > > The compiler will look for a method that takes a String and a Closure > that is annotated with @BuilderMethod > > The only issue with this proposal is that for the dynamic side you > would still have to implement methodMissing or invokeMethod unless we > make the runtime aware of @BuilderMethod > > Whilst a much simpler solution that would work for both today is to > define a regular methodMissing: > > def methodMissing(String name, args) > > So those are some more ideas :) > > Cheers > > On Wed, Oct 5, 2016 at 11:25 PM, Jochen Theodorou <blackd...@gmx.org> > wrote: > > 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''' > > > > > > -- > Graeme Rocher >