groovy-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Jochen Theodorou <blackd...@gmx.org>
Subject Re: Proposal: Statically compileable builders
Date Wed, 05 Oct 2016 21:25:47 GMT
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'''


Mime
View raw message