groovy-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From C├ędric Champeau <cedric.champ...@gmail.com>
Subject Re: Proposal: Statically compileable builders
Date Thu, 06 Oct 2016 07:16:53 GMT
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.rocher@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 <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