https://github.com/apache/wicket/pull/211 :-) Let's drop them all!
Martin Grigorov Wicket Training and Consulting https://twitter.com/mtgrigorov On Wed, Feb 8, 2017 at 4:34 PM, Emond Papegaaij <[email protected]> wrote: > I can live with the following: > > Factory methodes should: > * accept exactly 1 lambda (and possibly an id) > * be a static method on the component or behavior they create > * be given meaningfull names > * be limited to at most 1 or 2 per type > * not pass the instance they create to the lambda > > This would mean all methods should be moved from Lambdas and duplicates > like > AjaxLink.onClick should be removed. > > Still, I like the idea to remove them all better. We can also move them > all to > a factory class and place this class in experimental. That way we can do > with > it whatever we like. > > Best regards, > Emond > > On woensdag 8 februari 2017 16:20:59 CET Andrea Del Bene wrote: > > I agree with Martijn, especially when he warns about the huge risk we > take > > overusing lambdas and making API inconsistent. But I don't agree to take > > such an extreme choice like removing EVERY factory methods. Like Martin > > suggested these methods can be very useful when they are limited to the > > very basic functionality of a component (link onClick for a Link). > > More in general I would say that a lambda factory method should not take > > more than one lambda as parameter. > > > > On Wed, Feb 8, 2017 at 3:17 PM, Martin Grigorov <[email protected]> > > > > wrote: > > > On Wed, Feb 8, 2017 at 3:08 PM, Martijn Dashorst < > > > [email protected] > > > > > > > wrote: > > > > > > > > There are many things to consider when designing an API. Usability of > > > > the API, readability of the resulting code, maintenance burden for > the > > > > core developers, but also performance, memory consumption and > > > > serialization overhead. > > > > > > > > Adding lifecycle fields to Component for any extension point will > > > > dramatically increase the footprint of any Wicket application. > > > > Lambda's are not a free lunch and add considerable memory and > > > > serialization overhead to Java code. > > > > > > > > Wicket is not only used for small demos of how fancy lambda's can be, > > > > but also for applications where 10's of thousands or even millions of > > > > users concurrently use the application. Adding a byte to the > footprint > > > > of Component can increase the memory footprint of the application > with > > > > 10's of megabytes. > > > > > > > > So yes we have considered adding these things but find the problem > > > > they solve not worth the extra footprint. Furthermore the clarity of > > > > the code does not increase when you have 3 or 4 methods that need > > > > implementing, but those add considerable memory footprint (8 bytes > per > > > > reference when unused, much more when used) when provided through > > > > lambdas. > > > > > > > > Calculation > > > > > > > > Say you have 1 million components in memory. Say we add lambda's for > > > > the primary life cycle events and enabled/visible flags. This is: > > > > > > > > - onInitialize > > > > - onEvent > > > > - onReAdd > > > > - onRemove > > > > - onRender > > > > - onConfigure > > > > - onBeforeRender > > > > - onBeforeRenderChildren > > > > - onComponentTag > > > > - onComponentTagBody > > > > - onAfterRender > > > > - onAfterRenderChildren > > > > - onDetach > > > > - isVisible > > > > - isEnabled > > > > > > > > This is without onFoo methods available in Link, Form etc. 15 > > > > references that may or may not be used. 15*8 = 120 bytes per > > > > component. Now we have added 120MB to an application without anyone > > > > even using the functionality. The cost is considerably higher when > > > > actually using the lambda's. See the measurements I did for my talk > at > > > > ApacheCon 2016 in Sevilla [1] > > > > > > > > Of course we can try to minimize the memory footprint, and store the > > > > event references in the data bucket of Component and store only those > > > > events that are actually used. But this will add an O(n^2) for every > > > > request because we need to loop through the data array at each life > > > > cycle event to find if there's a registered reference, and if so use > > > > that. While for a single request this is not too big an issue, it > adds > > > > up when you consider that this must be done for millions of > components > > > > for all lifecycle events. > > > > > > > > API design > > > > > > > > So let's consider the case when we were to add setters for lambda's > to > > > > Component et al. We can't remove the old way of subclassing because > > > > that would break the world. Thus we have two ways of how to add an > > > > onConfigure or onDetach event to a component. Which one is canon? We > > > > already have at least 3 ways to determine whether a component is > > > > visible or not: > > > > > > > > - call setVisible (static visibility) > > > > - override isVisible > > > > - setVisibilityAllowed > > > > - override onConfigure() and call setVisible > > > > > > > > This has harmed the Wicket API in my opinion and is a result of us > not > > > > understanding at the time how, when and how often visibility needed > to > > > > be evaluated. > > > > > > > > Adding setters for lambda's would at least double the API surface. > > > > Furthermore you need to take into account that these setters can't be > > > > hidden by subclasses. So when you use a component that has > onConfigure > > > > overridden, what happens when you setOnConfigure()? > > > > > > > > public class FooPanel extends Panel { > > > > > > > > public FooPanel(String id) { super(id); } > > > > > > > > @Override > > > > protected void onConfigure() { > > > > > > > > super.onConfigure(); > > > > // if something set visiblity > > > > > > > > } > > > > > > > > } > > > > > > > > add(new FooPanel("foo").setOnConfigure(() -> { ... do something > > > > completely different ... }); > > > > > > > > So which one should prevail? the lambda or the overridden method? Ah > I > > > > hear you say "rewrite the FooPanel such that it doesn't override > > > > onConfigure". But then we have 2 onConfigure lambda's set: > > > > > > > > public class FooPanel extends Panel { > > > > > > > > public FooPanel(String id) { > > > > > > > > super(id); > > > > setOnConfigure(() -> { > > > > > > > > /* if something set visibility */ > > > > > > > > }); > > > > > > > > } > > > > > > > > } > > > > > > > > add(new FooPanel("foo").setOnConfigure(() -> { ... do something > > > > completely different ... }); > > > > > > > > So which one should prevail? Well, call both! OK, this means that we > > > > need to store both lambdas. So each lifecycle event lambda store > needs > > > > to be a data structure that can hold multiple lambdas, which > increases > > > > the memory footprint even further. Then we have to issue of which > > > > lambda to call first. The one added in the constructor of FooPanel, > or > > > > the one that was set after construction? > > > > > > > > This doesn't even take into account the semantics of for example the > > > > onInitialize lifecycle event, which states that it is called when a > > > > component is added to the hierarchy of a page (i.e. a parent-path > > > > exists to the page). A setOnInitialize can be invoked at any moment > > > > after construction. Therefore its semantics become even muddier than > > > > they already are: > > > > > > > > Label label = new Label(...); > > > > add(label); > > > > > > > > label.setOnInitialize(() -> { ... }); > > > > > > > > OK, so we don't add a lambda possibility for onInitialize (queue the > > > > pull requests). > > > > > > > > Another thing to consider is that a component builder currently can > > > > decide to close off particular events from being overridden or > > > > modified. For example: > > > > > > > > public class FooPanel extends Panel { > > > > > > > > public FooPanel(String id) { super(id); } > > > > > > > > /** No longer overridable */ > > > > @Override > > > > protected final void onConfigure() { > > > > > > > > super.onConfigure(); > > > > // if something set visiblity > > > > > > > > } > > > > > > > > } > > > > > > > > The component designer found it necessary to remove the ability to > > > > override onConfigure(). > > > > > > > > However you can't remove the ability to add/set lambda's from > > > > Component. These methods need to be public for them to be usable, and > > > > you can't hide those methods. > > > > > > > > Summary > > > > > > > > Lambdas are a great tool but should be used wisely and we should be > > > > careful about our API and take good design into consideration instead > > > > of just papering our API with lambdas. There are many aspects to > > > > consider, not just the fact that you can write 2 lines of > > > > non-executable code less. > > > > > > YES! > > > That's why I think the current factories we have is the best we can > give - > > > they are very handy for (my?!) the trivial case and they cannot be > > > extended > > > further with more functionality, it is just not practical to add more > to > > > them. > > > For more inspiration take a look at other UI libs like > JavaFX/TornadoFX, > > > Vaadin 8, Scalatags/Kotlinx.html, ... > > > > > > > Martijn > > > > > > > > [1] http://www.slideshare.net/dashorst/whats-up-with-wicket- > 8-and-java-8 > > > > > > > > On Wed, Feb 8, 2017 at 11:45 AM, Andrew Geery < > [email protected]> > > > > > > > > wrote: > > > > > Rather than using static factory methods, would we ever consider > > > > > > pushing > > > > > > > > the lambdas into the component classes themselves? > > > > > > > > > > For example, if we did this with the Button class, the change would > > > > > be: > > > > > > > > > > - Add two private fields, SerializableConsumer<Button> > submitHandler, > > > > > errorHandler > > > > > - Add "setters" for these fields -- e.g., Button > > > > > submitHandler(SerializableConsumer<Button> submitHandler) > > > > > - Modify the onSubmit() and onError() methods to call the handler > > > > > > methods > > > > > > > > if they are not null > > > > > > > > > > A call would be something like: > > > > > > > > > > add(new Button("button").submitHandler(b -> > > > > > > > > System.out.println("clicked")); > > > > > > > > > Obviously, it is still possible to sub-class Button and override > > > > > onSubmit(), so existing code will continue to work. However, for > code > > > > > going forward, it should be much less needed and, in my opinion, > much > > > > > clearer. > > > > > > > > > > Another advantage to doing things this way is that sub-classes > inherit > > > > > these methods and there is no need to add static factory methods > for > > > > > > > > every > > > > > > > > > sub-class (sub-classes of AjaxButton would be a better example of > > > > > > this). > > > > > > > > Thanks > > > > > Andrew > > > > > > > > > > On Wed, Feb 8, 2017 at 3:42 AM, Martijn Dashorst < > > > > > > > > [email protected] > > > > > > > > >> wrote: > > > > >> > > > > >> It is that your trivial use case is not my trivial use case and > that > > > > >> we will end up with a 100,000 trivial use cases. > > > > >> > > > > >> And no, confusion is not the big issue (though for > onsubmit+onerror > > > > >> it > > > > >> is) but creating a good API is hard. It takes time and > understanding. > > > > >> Minimizing lines of code is not the only metric for a good API. > > > > >> > > > > >> Just as using inheritance/annotations/generics for everything is > bad, > > > > >> introducing factory methods everywhere will not age well. > > > > >> > > > > >> These methods are trivial for anyone to implement and should we > reach > > > > >> the conclusion that we actually need the factory methods in core, > it > > > > >> is trivial to refactor them towards the Wicket supplied factories. > > > > >> > > > > >> Are the factory methods in the way? Yes they are, because once we > add > > > > >> them, we can't evolve them without adding many more (introduce > bloat) > > > > >> or having to wait until a new major release. > > > > >> > > > > >> Martijn > > > > >> > > > > >> > > > > >> > > > > >> > > > > >> On Tue, Feb 7, 2017 at 11:40 PM, Martin Grigorov < > > > > > > [email protected]> > > > > > > > >> wrote: > > > > >> > Hi, > > > > >> > > > > > >> > These methods are factories for the trivial use cases where > nothing > > > > > > > > else > > > > > > > > >> > needs to be overridden but the action method (methods, when > there > > > > >> > is > > > > >> > onError()). They do help to reduce the verbosity! > > > > >> > There are plenty of those cases. You can see many usages in > > > > >> > wicket-examples. I have used such methods from > wicketstuff-scala in > > > > > > > > one > > > > > > > > >> of > > > > >> > > > > >> > my projects in the past and I use similar ones in a project with > > > > > > > > Kotlin > > > > > > > > >> now. > > > > >> > > > > >> > A builder that provides methods like onConfigure(), > > > > > > onComponentTag(), > > > > > > > >> > onDetach(), ... would look really strange! > > > > >> > Who will use it instead of just creating a (inner) class ?! > > > > >> > > > > > >> > But if those factory methods confuse core developers then they > will > > > > > > be > > > > > > > >> even > > > > >> > > > > >> > more confusing to normal users :-/ > > > > >> > > > > > >> > 0 from me. > > > > >> > > > > > >> > Martin Grigorov > > > > >> > Wicket Training and Consulting > > > > >> > https://twitter.com/mtgrigorov > > > > >> > > > > > >> > On Tue, Feb 7, 2017 at 5:07 PM, Martijn Dashorst < > > > > >> > > > > >> [email protected] > > > > >> > > > > >> >> wrote: > > > > >> >> > > > > >> >> All, > > > > >> >> > > > > >> >> I want to remove all component factory methods that are added > for > > > > >> > > > > >> lambda's. > > > > >> > > > > >> >> My reasoning is that: > > > > >> >> - removing them in 8.x (x > 0) is impossible > > > > >> >> - adding them in 8.x (x > 0) is trivial > > > > >> >> - we currently don't have a way to know what good idioms are > > > > >> >> - having these factories push (new) developers to use the wrong > > > > > > idiom > > > > > > > >> >> - factory methods don't allow for additional overriding, thus a > > > > >> >> combinatorial explosion of API > > > > >> >> - I tend to think that builders are better suited as component > > > > > > > > factories > > > > > > > > >> >> Because it is trivial to re-introduce these factories or their > > > > >> >> replacements at a later time, I propose to remove them now and > > > > > > figure > > > > > > > >> >> out in our actual applications what works, and what doesn't. I > > > > >> >> also > > > > >> >> think that just doing the conversion from W7 to W8 isn't > enough to > > > > >> >> figure this out. > > > > >> >> > > > > >> >> Example 1: > > > > >> >> > > > > >> >> Button.java has two factory methods: > > > > >> >> > > > > >> >> Button#onSubmit(String, SerializableConsumer<Button>), and > > > > >> >> Button#onSubmit(String, SerializableConsumer<Button>, > > > > >> >> SerializableConsumer<Button>) > > > > >> >> > > > > >> >> It is not possible to see without resorting to parameter > naming, > > > > >> >> hovering etc to see which functionality is bound by which > > > > > > parameter. > > > > > > > I > > > > > > > > >> >> find the naming confusing: onSubmit and onSubmit. > > > > >> >> > > > > >> >> Example 2: > > > > >> >> > > > > >> >> Behavior.java has two factory methods: > > > > >> >> > > > > >> >> Behavior onTag(SerializableBiConsumer<Component, > ComponentTag>) > > > > >> >> Behavior onAttribute(String name, > > > > >> >> SerializableFunction<String, CharSequence> onAttribute) > > > > >> >> > > > > >> >> These achieve basically the same functionality, but other life > > > > > > cycle > > > > > > > >> >> events of Behavior don't have factory methods. NOR should they. > > > > >> >> > > > > >> >> Example 3: > > > > >> >> > > > > >> >> Lambdas.java has many factory methods, most of which are better > > > > >> >> implemented by just using an anonymous inner class. For > example, > > > > >> >> Lambdas.link: often times you need to override onconfigure or > > > > >> >> oncomponenttag as well as onclick. > > > > >> >> > > > > >> >> Martijn > > > > >> > > > > >> -- > > > > > > > >> Become a Wicket expert, learn from the best: > > > http://wicketinaction.com > > > > > > > -- > > > > Become a Wicket expert, learn from the best: > http://wicketinaction.com > > >
