I am leaving this here to nerd snipe you into trying it out as a prototype: Right now you discover witnesses by looking at annotated methods in the types involved in a generic expression.
What if, instead, you discovered witnesses via the service provider mechanism?
package dev.mccue.types;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public abstract class TypeProvider<For, Providing> {
private final Type for_;
private final Type providing;
record TypePair(Type a, Type b) {
TypePair(TypeProvider<?, ?> provider) {
this(provider.for_, provider.providing);
}
TypePair(Ty<?, ?> ty) {
this(ty.a(), ty.b());
}
}
private static final ConcurrentHashMap<TypePair, Object> CACHE = new ConcurrentHashMap<>();
private static final List<TypeProvider<?, ?>> PROVIDERS = findProviders();
private static List<TypeProvider<?, ?>> findProviders() {
var m = new HashSet<TypePair>();
var providers = new ArrayList<TypeProvider<?, ?>>();
ServiceLoader.load(TypeProvider.class)
.forEach(provider -> {
if (!m.add(new TypePair(provider.for_, provider.providing))) {
throw new IllegalStateException("Found more than one provider providing " + provider.providing + " for " + provider.for_);
}
providers.add(provider);
});
return providers;
}
protected TypeProvider() {
var thisClass = this.getClass();
var superClass = thisClass.getGenericSuperclass();
if (!(this.getClass().getSuperclass() == TypeProvider.class)) {
throw new IllegalStateException("TypeProvider can only be extended one level.");
}
if (!(superClass instanceof ParameterizedType parameterizedSuperType)) {
throw new IllegalStateException("TypeProvider must be given generic parameters");
}
var typeArguments = parameterizedSuperType.getActualTypeArguments();
this.for_ = typeArguments[0];
this.providing = typeArguments[1];
}
public abstract Providing provide(For for_);
public Type forType() {
return for_;
}
public Type providingType() {
return providing;
}
@Override
public String toString() {
return "TypeProvider[for=" + for_ + ", providing=" + providing;
}
@SuppressWarnings("unchecked")
public static <A, B> B provide(Ty<A, B> types, A instance) {
return (B) CACHE.computeIfAbsent(new TypePair(types), _ -> {
for (var provider : PROVIDERS) {
if (provider.for_.equals(types.a())) {
return ((TypeProvider<A, B>) provider).provide(instance);
}
}
throw new IllegalStateException("No TypeProvider found providing " + types.b() + " for " + types.a());
});
}
}
public class IntDisplay implements Display {
private final int value;
public IntDisplay(int value) {
this.value = value;
}
@Override
public void display() {
IO.println(value);
}
}
public class IntDisplayProvider extends TypeProvider<Integer, Display> {
@Override
public Display provide(Integer for_) {
return new IntDisplay(for_);
}
}
module dev.mccue.types {
exports dev.mccue.types;
exports dev.mccue.types.examples;
provides TypeProvider with IntDisplayProvider;
uses TypeProvider;
}
I am leaving this here to nerd snipe you into trying it out as a prototype: Right now you discover witnesses by looking at annotated methods in the types involved in a generic expression.
What if, instead, you discovered witnesses via the service provider mechanism?