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