Friday, June 11, 2010

Buoyancy

Steve suggested: For this to work like you want, Java style, you'd have to add a Class member to the Box class, to represent the type of Box at runtime.

Like this:
public final class Box<E> {

  private final Class<E> type;
  private final E value;

  public Box (Class<E> type, E value) {
    this.type = checkNotNull(type, "type");
    this.value = value;
  }

  public E getValue() {
    return value;
  }

  public boolean isType (Class<?> specific) {
    return specific == this.type;
  }

  @SuppressWarnings("unchecked")
  public <X> Box<X> asType (Class<X> specific) {
    if (isType(specific)) {
      return (Box) this;
    } else {
      throw new ClassCastException();
    }
  }
}
You could then write a static generic filter taking an Iterable of generic Boxes and a Class object to filter by, and returning an Iterable of the correct type fairly simply.

Sort of like this (assuming appropriate definitions of map and filter):
// The essential code is in a bold green face.

public <X> Iterable<Box<X>> boxesOfType (final Class<X> type,
                                         Iterable<Box<?>> boxes) {
  return
      map (
          filter (
              boxes,
              new Predicate<Box<?>> () {
                @Override
                public boolean apply (Box<?> box) {
                  return box.isType(type);
                }
              }),
          new Function<Box<?>, Box<X>> () {
            @Override
            public Box<X> apply (Box<?> box) {
              return box.asType(type);
            }
          });
}
Dunno if that floats your boat, tho.

It definitely floats my boat (thanks, Steve) in that I can now, in a typesafe manner, take a bunch of heterogeneous boxes and select and downcast them to a subset of homogeneous boxes.

I'll discuss the leaks in the boat in the next post.

Addendum

mquander offered a C# solution to the same problem:
internal interface IBox { }
 
public class Box<T> : IBox
{
    public T Value;
}
 
public static class BoxManipulators
{
    public static int MinimumIntegerInBox(IEnumerable<IBox> boxes)
    {
        return boxes.OfType<Box<int>>().Min(x => x.Value);
    }
}

4 comments:

mquander said...

Just for curiousity's sake, I provide a full typesafe solution in idiomatic C#, which many people suppose is a clone of Java:

internal interface IBox { }

public class Box : IBox
{
public T Value;
}

public static class BoxManipulators
{
public static int MinimumIntegerInBox(IEnumerable boxes)
{
return boxes.OfType>().Min(x => x.Value);
}
}

Note that this solution makes a compromise that isn't present in the Java solution: I need an extra interface to be a type that all generic Boxs share, since C# doesn't have wildcards for parameterized types like Java does. However, it beats the Java solution in another regard; you don't have to repeat the type as a separate parameter when constructing boxes and calling the function.

mquander said...

Blogger munged my solution -- it killed some of my angle brackets. Here's the full one. http://pastebin.com/Cc6SGpiu

John Cowan said...

I don't see that the Java solution is more perspicuous than the previous Java solution. It's in the nature of Java that an object's non-generic types are manifest at runtime, and carrying the type in an extra instance variable is not only pointless but violates the DRY rule. ("I deduct two points for this answer, for you have not only failed to get it right, but you have also gotten it wrong." --my dad's geometry teacher, circa 1919)

Jrm said...

I agree that this solution is no more perspicuous, but it is more correct. The manifest type that is passed in is the specific type of the contents of the box, not the box itself, so it isn't redundant at that level. It is necessary for two reasons:
1. If the box contains null, there is no way to find the specific subtype.

2. In general, having a generic class does not imply that the class literally contains an instance of the specific subclass. In the case of the box, we need to know the return type of the getValue method. It logically happens to be the same type as the value field, but that's more or less a coincidence.