groovy-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Graeme Rocher <graeme.roc...@gmail.com>
Subject Re: Proposal: Statically compileable builders
Date Thu, 06 Oct 2016 06:31:41 GMT
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 <blackdrag@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

Mime
View raw message