4

Imagine having a main class -- Simulator -- that uses two other classes -- Producers and Evaluators, which implement interfaces IProducer and IEvaluator, respectively.

IProducer implementations produce results, while the IEvaluator implementations evaluate those results. The Simulator controls the flow of execution by querying the IProducer implementation and then conveying the results to the IEvaluator instance.

The actual implementation of the Producer and Evaluator are known at run-time, at compile time I only know their interfaces. Check the example below.

package com.test;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * Producers produce results. I do not care what their actual type is, but the
 * values in the map have to be comparable amongst themselves.
 */
interface IProducer<T extends Comparable<T>> {
    public Map<Integer, T> getResults();
}

/**
 * This example implementation ranks items in the map by using Strings.
 */
class ProducerA implements IProducer<String> {
    @Override
    public Map<Integer, String> getResults() {
        Map<Integer, String> result = new HashMap<Integer, String>();
        result.put(1, "A");
        result.put(2, "B");
        result.put(3, "B");

        return result;
    }
}

/**
 * This example implementation ranks items in the map by using integers.
 */
class ProducerB implements IProducer<Integer> {
    @Override
    public Map<Integer, Integer> getResults() {
        Map<Integer, Integer> result = new HashMap<Integer, Integer>();
        result.put(1, 10);
        result.put(2, 30);
        result.put(3, 30);

        return result;
    }
}

/**
 * Evaluator evaluates the results against the given groundTruth. All it needs
 * to know about results, is that they are comparable amongst themselves.
 */
interface IEvaluator {
    public <T extends Comparable<T>> double evaluate(Map<Integer, T> results,
            Map<Integer, Double> groundTruth);
}

/**
 * This is example of an evaluator, metric Kendall Tau-B. Don't bother with
 * semantics, all that matters is that I want to be able to call
 * r1.compareTo(r2) for every (r1, r2) that appear in Map<Integer, T> results.
 */
class KendallTauB implements IEvaluator {
    @Override
    public <T extends Comparable<T>> double evaluate(Map<Integer, T> results,
            Map<Integer, Double> groundTruth) {
        int concordant = 0, discordant = 0, tiedRanks = 0, tiedCapabilities = 0;

        for (Entry<Integer, T> rank1 : results.entrySet()) {
            for (Entry<Integer, T> rank2 : results.entrySet()) {
                if (rank1.getKey() < rank2.getKey()) {
                    final T r1 = rank1.getValue();
                    final T r2 = rank2.getValue();
                    final Double c1 = groundTruth.get(rank1.getKey());
                    final Double c2 = groundTruth.get(rank2.getKey());

                    final int ranksDiff = r1.compareTo(r2);
                    final int actualDiff = c1.compareTo(c2);

                    if (ranksDiff * actualDiff > 0) {
                        concordant++;
                    } else if (ranksDiff * actualDiff < 0) {
                        discordant++;
                    } else {
                        if (ranksDiff == 0)
                            tiedRanks++;

                        if (actualDiff == 0)
                            tiedCapabilities++;
                    }
                }
            }
        }

        final double n = results.size() * (results.size() - 1d) / 2d;

        return (concordant - discordant)
                / Math.sqrt((n - tiedRanks) * (n - tiedCapabilities));
    }
}

/**
 * The simulator class that queries the producer and them conveys results to the
 * evaluator.
 */
public class Simulator {
    public static void main(String[] args) {
        // example of a ground truth
        Map<Integer, Double> groundTruth = new HashMap<Integer, Double>();
        groundTruth.put(1, 1d);
        groundTruth.put(2, 2d);
        groundTruth.put(3, 3d);

        // dynamically load producers
        List<IProducer<?>> producerImplementations = lookUpProducers();

        // dynamically load evaluators
        List<IEvaluator> evaluatorImplementations = lookUpEvaluators();

        // pick a producer
        IProducer<?> producer = producerImplementations.get(0);

        // pick an evaluator
        IEvaluator evaluator = evaluatorImplementations.get(0);

        // evaluate the result against the ground truth
        double score = evaluator.evaluate(producer.getResults(), groundTruth);

        System.out.printf("Score is %.2f\n", score);
    }

    // Methods below are for demonstration purposes only. I'm actually using
    // ServiceLoader.load(Clazz) to dynamically discover and load classes that
    // implement interfaces IProducer and IEvaluator
    public static List<IProducer<?>> lookUpProducers() {
        List<IProducer<?>> producers = new ArrayList<IProducer<?>>();
        producers.add(new ProducerA());
        producers.add(new ProducerB());

        return producers;
    }

    public static List<IEvaluator> lookUpEvaluators() {
        List<IEvaluator> evaluators = new ArrayList<IEvaluator>();
        evaluators.add(new KendallTauB());

        return evaluators;
    }
}

This code compiles without warnings and also runs the way it should. This is the solution to the question that I asked before, so this is sort of a follow-up question.

Using the code above, imagine that you'd like to store the result of the producer.getResults() call in a variable (which would later be used in the call to the evaluator.evaluate(results, groundTruth) call). What would be the type of that variable?

Map<Integer, ?>, Map<Integer, ? extends Comparable<?>>? Make the main method generic and use the generic type? Nothing that I've tried so far works. The compiler complains for every type that I've come up with.

public static void main(String[] args) {
    // example of a ground truth
    Map<Integer, Double> groundTruth = new HashMap<Integer, Double>();
    groundTruth.put(1, 1d);
    groundTruth.put(2, 2d);
    groundTruth.put(3, 3d);

    // dynamically load producers
    List<IProducer<?>> producerImplementations = lookUpProducers();

    // dynamically load evaluators
    List<IEvaluator> evaluatorImplementations = lookUpEvaluators();

    // pick a producer
    IProducer<?> producer = producerImplementations.get(0);

    // pick an evaluator
    IEvaluator evaluator = evaluatorImplementations.get(0);

    // evaluate the result against the ground truth
    Map<Integer, ?> data = producer.getResults(); // this type works
    double score = evaluator.evaluate(data, groundTruth); // but now this call does not


    System.out.printf("Score is %.2f\n", score);
}

It seems as the producer.getResults() returns something that can't be statically expressed in Java. Is this a bug, or I'm I missing something?

Community
  • 1
  • 1
David
  • 166
  • 8
  • Why can't you just use `evaluator.evaluate(producer.getResults(), groundTruth)`? – Jeffrey Sep 03 '12 at 23:21
  • @Jeffrey, I can use it, however, imagine that you'd like to invoke evaluate(data, goundTruth) on several different evaluator instances **with the same data** (that was returned from a certain producer instance). It would not be wise to call the getResult for every such invocation, since getResults often contains *heavy computing*. – David Sep 03 '12 at 23:30
  • Have you tried just using a `Map`, with no generic type arguments? I know that kind of sidesteps the issue here, but it should at least compile and run. Then it's just your responsibility to make sure the `Map` has the correct kind of things in it. – aroth Sep 03 '12 at 23:38
  • @David Fair enough, I posted one possible workaround as an answer for you. – Jeffrey Sep 03 '12 at 23:39

2 Answers2

2

One note before my answer: All of your T extends Comparable<T> statements should probably be T extends Comparable<? super T>, it allows more flexibility (why should you care if it compares Ts or Objects?), and it's required for my solution to work.

This isn't really a "bug" of the Java type system, it's just an inconvenience of it. Java doesn't particularly like having intersection types as a part of a type declaration.

One way I've found to get around this is to create an "unsafe" method that under normal circumstances should never be used:

@SuppressWarnings("unchecked")
private static <T extends Comparable<? super T>> Map<Integer, T> cast(Map<Integer, ?> map) {
    return (Map<Integer, T>) map;
}

Just make sure to call this method with a Map that actually is a Map<Integer, T extends Comparable<? super T>> (like the ones IProducers return).

Using this method, you can do the following:

IProducer<?> producer = ...
IEvaluator evaluator = ...
Map<Integer, ?> product = producer.getResults();
evaluator.evaluate(cast(product), truth);

And then Java will automagically infer the correct type parameter for you.

Also, the I prefix is generally frowned upon in the Java community.

Community
  • 1
  • 1
Jeffrey
  • 44,417
  • 8
  • 90
  • 141
  • Thanks. Some comments: - Instead of @SuppressWarnings("unused") it should be @SuppressWarnings("unchecked"). - I had something similar, but the SuppressWarnings directive bothered me -- for no obvious reason, only that it felt I was not doing something right. So I tried to find something better. I'm quite surprised that I stumbled upon a language limitation/inconvience. - PS: Also thanks for pointing out he IInterface convention. I'll drop it. – David Sep 04 '12 at 00:23
  • @David Ah, whoops. I was typing this code freehand. Suppressing an unchecked warning is perfectly fine as long as you can verify that the cast is indeed correct. The compiler isn't smart enough to do this for you since it drops all type information. You, as the programmer, *are* smart enough to verify the cast since you don't have to go through type erasure to maintain binary compatibility. – Jeffrey Sep 04 '12 at 00:28
2

It is not a bug, but indeed a limitation. It is well-known in the type system community that Java with wildcards has types that cannot be expressed in Java syntax. Your example exhibits one such case, and demonstrates that wildcards are essentially incompatible with F-bounded polymorphism (that is, type parameters of the form T extends Something<T>).

To put it bluntly, wildcards are a terrible type system hack. They should never have been put into Java. What one really wants, and what would make your example expressible, are proper existential types (of which wildcards are a limited ad-hoc variant). Unfortunately, Java does not have them (though Scala has).

Andreas Rossberg
  • 34,518
  • 3
  • 61
  • 72