Using <? extends T> and <? super T> for reduce style functions that operate on collections

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:

  1. 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)
    }
  2. Allow users to configure functions use <? super S> for the source values.  i.e.
    public void setFunction(Reduce<R, ? super T> function) {...}

All good.

This entry was posted in Development, GWT. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *