ok, hit send too soon (shift-enter) :-(

Lamda extension points are valuable but mostly in places where it is
specifically designed for it. I think that for example a library like
Wicket Bootstrap with more specific components can benefit from an API
point by providing lambda handlers.

IModel is a great extension point where lambda's make sense. They were
and are orthogonal pieces of functionality for components and
behaviors. They were designed to be orthogonal. Similar to the
functions that are applied to collection streams.

Component event handlers in Wicket are not designed to be orthogonal
pieces, but are inherent to the component itself: they define the
behavior of the component from within the component. As such when
trying to cram lambda's into the framework of Component's life cycle,
we run into all kinds of nasty side effects where we try to juggle the
designed behavior with the new added behavior.

This is not to say lambda's are bad, but rather that Wicket's design
doesn't cater well for adding lambda's as an orthogonal event
mechanism, *in its core*.

Not adopting lambda's for onClick/et al doesn't hinder you in creating
such an API yourself catered specifically to your needs.

As a framework for general use we have to look at all uses, and weigh
usability with performance overhead and memory consumption. As an
application developer you know precisely the extension points and the
amount of memory and CPU cycles you want to spend on typing ~20
characters less.

As an example of how one can put lambda's to good use in an
application take a look at these java files:

https://gist.github.com/dashorst/d668f57d0961445faf9a0df5bab355e6

As an example of how the API is used, see the default method in
BootstrapActions.java, or the code below:

getActions().addLeftAction()
    .label("Print")
    .icon(FontAwesomeIcon.print)
    .withClass("btn btn-outline")
    .onSubmit(form, this::print);

getActions().addRightAction()
    .label(isEditable() ? "Cancel" : "Back")
    .icon(FontAwesomeIcon.cancel)
    .withClass("btn btn-outline")
    .onClick(this::cancel);

getActions().addRightAction()
    .label("Next")
    .icon(FontAwesomeIcon.next)
    .withClass("btn btn-outline")
    .visibleWhen(() -> getModelObject().getProperty() != null)
    .onClick(this::nextStep);

getActions().addRightAction()
    .label("Save")
    .icon(FontAwesomeIcon.save)
    .withClass("btn btn-primary")
    .visibleWhen(() -> true
        && isVragenlijstBeschikbaar()
        && isVragenlijstWijzigbaar()
        && alleVragenZijnIngevuld())
   .onSubmit(vragenlijstForm, this::opslaan)
    // method component() breaks the builder pattern by providing
access to the button itself
   .component().add(new SelfAjaxUpdatingBehavior());

This is IMO a great way to design APIs tailored to your application,
or component library such as Wicket Bootstrap. But that is way beyond
what Wicket can and should provide out of the box because it is at too
a high level.

In summary: I believe we should not ship with lambda bindings for
Component and Behavior life cycle and event methods. This doesn't
prevent anyone from introducing lambda's in their application layers
where they can be used as targeted extension points in specific APIs
rather than compound Wicket innate memory and CPU cycle consumption.

Martijn



On Thu, Feb 23, 2017 at 2:10 PM, Martijn Dashorst
<[email protected]> wrote:
> Hi,
>
> Good post. However I don't agree with it :-).
>
> Note that I am not against using lambdas, but rather think that Wicket
> core should not provide lambda extension points for component/behavior
> life cycle methods.
>
> Comments inline and discussion at the end of this message.
>
> On Tue, Feb 21, 2017 at 3:36 AM, Andrew Geery <[email protected]> wrote:
>> 1. I don't think we should consider every onXXX as a candidate for lambda
>> usage.  Specifically, I don't think Component-level methods like
>> onInitialize(), onConfigure(), onDetach(), etc. should be candidates
>> because overriding those methods is fairly rare.
>
> Perhaps in your development, but I just crafted some statistics across
> our projects and see the following statistics:
>
> Biggest project (1,301,269 lines of Java code):
>
> onInitialize: 906
> onConfigure: 1029
> onBeforeRender: 366
> onComponentTag: 65
> onAfterRender: 6
> onDetach: 1524
> onEvent: 118
> onClick: 1611
> onSubmit: 814
>
> Smaller project (1,069,352 lines of Java code):
>
> onInitialize: 194
> onConfigure: 832
> onBeforeRender: 153
> onComponentTag: 81
> onAfterRender: 10
> onDetach: 1400
> onEvent: 56
> onClick: 1788
> onSubmit: 805
>
> Smallest project (39,162 lines of Java code):
>
> onInitialize: 40
> onConfigure: 76
> onBeforeRender: 6
> onComponentTag: 40
> onAfterRender: 4
> onDetach: 56
> onEvent: 13
> onClick: 105
> onSubmit: 67
>
> In-house Framework used by above applications (173,241 lines of Java code):
>
> onInitialize: 45
> onConfigure: 206
> onBeforeRender: 79
> onComponentTag: 91
> onAfterRender: 4
> onDetach: 43
> onEvent: 14
> onClick: 139
> onSubmit: 70
>
> As you can see, the number of times onConfigure, onBeforeRender,
> onInitialize are overridden is in the same ballpark as
> onClick/onSubmit. It is not an order of magnitude higher.
>
>>  For example, if I
>> override those methods, I am almost always in a standalone sub-class of
>> Panel; it is unusual for me to override them for things like Link or Button
>> components.  Indeed, to deal with concerns like visibility or being enabled
>> (common reasons to override #onConfigure()), I add a Behavior to control
>> this rather than sub-class the Component.
>
> Adding a behavior is nice if you have no memory restrictions. Adding a
> Lambda is nice if you don't have memory restrictions.
>
> Inheritence is a very efficient way of extending behavior and not
> consuming much memory. This is why we override
> onConfigure/onBeforeRender/onComponentTag when we already have to
> override onClick. You already have a subclass, you already have the
> model so make use of it.
>
>> 2a. I think what makes a method a good candidate for a lambda is where a
>> little more _specific_ functionality needs to be added to a component to
>> make it useful/complete.  In my mind, the Link, AjaxLink and
>> AjaxFallbackLink components are quintessential examples of this: the
>> developer needs to add one (usually small) piece of functionality (the
>> onClick(...) method) to make the component useful.
>
> Nobody is denying that onClick/onSubmit are useful methods to
> implement, however, we are questioning the added API complexity, added
> memory and computing inefficiency introducing lambda's for these
> events, when you typically (see the stats above) also need to
> implement other functionality on the component as well.
>
> The only argument I see for improved readability when enabling
> lambda's is passing method references as the lambda. Even if you only
> have two lines of code in your onClick handler, the benefits of a
> lambda are negligible over just subclassing and overriding onClick.
>
>> 3. A major concern with introducing lambda expressions is to not penalize
>> components space-wise that don't use lambdas -- e.g., existing
>> functionality that sub-classes AjaxLink to override the onClick(...) method
>> should not have to store a null pointer to an unused lambda reference
>
> Also don't count out the computational burden of having to look up the
> unused lambda reference when no override is present and no lambda was
> attached.
>
>> 4. In the Component class, it is possible to store and access typed
>> "meta-data" with a component instance (#setMetaData/#getMetaData).  If the
>> lambda handler is stored as meta-data, storage for the lambda will only be
>> allocated if a lambda handler is actually assigned.  If a lambda handler is
>> not used, no storage for the handler would be allocated.
>
> We have considered that in our discussion. This requires performing
> the lookup, and when enabling the use of lambdas in this manner people
> will start using it (jay!), but memory consumption will rise
> dramatically.
>
>> 4. Make AjaxLink non-abstract and implement onClick(AjaxRequestHandler) by
>> calling getMetaData(AJAX_CLICK_HANDLER_KEY) and calling the #handleClick
>> method on the lambda with the AjaxRequestTarget if the lambda is not null.
>
> This will fail with the default code completion template of Eclipse
> that inserts a super call for all overridden methods. It conflicts
> with Wicket's other lifecycle methods where you are required to call
> super (onConfigure, onInitialize, onDetach) in that in this case you
> should not call super.
>
> Also this breaks our explicit contract where we require users to
> implement the onClick method. It directs their use of our API and is
> practically self-documenting. By making (Ajax)Link non-abstract and
> having an implementation for onClick() we loose this.
>
> Furthermore when added to Wicket Core, we introduce a second way to
> implement behavior in our API:
>
>  - use inheritance
>  - use lambda event handlers
>
> where the use of lambda event handlers will come with some caveats:
>
> - don't use for components where there are many instances of
> - don't override the onClick, or don't call super.onClick
> - components that have onClick overridden and a onclick lambda handler
> is registered
> - components that have a onclick lambda handler registered and have
> onClick overridden
>
>> 5. Because AjaxClickHandler is a @FunctionalInterface, the setClickHandler
>> method can be invoked with a lambda (and without explicitly referencing the
>> ClickHandler interface at all):
>>
>> new AjaxLink("id").setClickHandler(target -> target.add(myLink));
>
> OK, and now I need to change the visiblity dynamically, so I implement
> (according to our current idioms):
>
> new AjaxLink("id") {
>     @Override
>     protected void onConfigure() {
>         super.onConfigure();
>         setVisibilityAllowed( some condition );
>     }
> }.setClickHandler(target -> {
>     do some action
>     target.add(component);
> });
>
> What have we gained?
>
>> The advantage of this is:
>>
>> 1) Usages of AjaxLink that do not use lambdas do not take up any more space
>> than they did before; and
>> 2) It is still possible to sub-class AjaxLink (e.g., to implement
>> #onConfigure).
>>
>> The disadvantage of this is that, as Martijn pointed out, it doesn't make
>> sense to call setClickHandler() on a subclass where onClick has been
>> overridden.  However, because we're adding lambda handlers to a relatively
>> small number of components, and adding it to implement a very specific
>> piece of functionality (i.e., onClick is much more specific than
>> onConfigure), I don't think this is that confusing.
>
> I posit that it *is* confusing, because you now have two ways of
> accomplishing the same task, where one does not bring many benefits
> over the other, but rather restricts and directs developers into the
> wrong direction. I can see the pull requests coming where we should
> add "setConfigureHandler", "setBeforeRenderHandler",
> "setEventHandler", "setInitializeHandler", etc.
>
>
> --
> Become a Wicket expert, learn from the best: http://wicketinaction.com



-- 
Become a Wicket expert, learn from the best: http://wicketinaction.com

Reply via email to