One of the cases I’ve had to deal with in gwt-pectin is creating Reduce style functions that operate on a collection of values. The basic idea is to define interface of the form.
public interface Reduce<R,T> { R reduce(List<T> source); }
I use this on my ReducingValueModel<R,T> to automatically compute fields such as a sum’s of numbers, or a string representation of a list and so forth. So on my ReducingValueModel I have a method to configure the reducing function to use as follows.
public class ReducingValueModel<R,T> { // our function private Reduce<R,T> function; // and it's setter public void setFunction(Reduce<R,T> function) { this.function = function; recompute(); } // and every thing else that makes it go ... }
This works fine when define a new function for each different combination of R and T. The trouble appears when you want to create a generic Reduce function that you can use on any ReductingValueModel. Something like a generic “list to string” style function for example:
public class ListToStringFunction<String, Object> { // we can convert any list of objects to a string public String reduce(List<Object> values) { // concate our list values and return the result. return ...; } }
So now lets try and use it.
ReducingValueModel<String, Integer> reducingModel = ...;
// This won't compile...
reductingModel.setFunction(new ListToStringFunction());
But this won’t compile because ListToStringFunction is a Reduce<String, Object> and not Reduce<String, Integer>. So we bung in the standard <? super T> clause on the ReducingValueModel so it can accept a function that works on any super type.
public class ReducingValueModel<R,T> {private Reduce<R, ? super T> function; // now lets use <? super S> so we can use functions that // operate on any super source type. public void setFunction(Reduce<R,? super T> function) { this.function = function; recompute(); } }
And while it looks like this should work, it doesn’t. The problem is that were I’m using the Reduce function it’s now defined as a Reduce<R, ? super T> (making it a Reduce<T,Object> for all intents and purposes) so it can’t accept any old List<T> as an argument.
public class ReducingValueModel<R,T> { private Reduce<R, ? super T> function; protected void recompute() { ArrayList<T> values = ...; // prepare the values.. // now this won't compile because our the function we // passed in only works on List<? super T> and not List<T> R computedValue = function.compute(values); fireValueChangeEvent(computedValue); } }
Fortunately the fix is simple. We need to update our Reduce<R,T> interface to accept any values that extend T. I.e.
public interface Reduce<R,T> { // this allows our Reduce<R, ? super T> to operate // on any list that extends T T reduce(List<? extends T> source); }Now our ReducingValueModel<R,T> can accept functions that work on any super type of T and our Reduce<R,T> can accept any list whose values that extend T.
So the two things to do are:
- Make sure your functions can operate on source collections of type <? extends T>. i.e.
public interface Reduce<R,T> { R reduce(List<? extends T> source) }- Allow users to configure functions use <? super S> for the source values. i.e.
public void setFunction(Reduce<R, ? super T> function) {...}All good.