Skip to content

Commit a274309

Browse files
gselzerhinerm
authored andcommitted
Enable optional parameters using reduction
This PR introduces the ability to declare optional parameters in an Op's **functional method** using the Optional annotation. The motivation behind this implementation was to maintain from ImageJ Ops the ability to declare optional parameters without the complexity of field injection. Once one parameter (or more) has been declared optional for some Op, scijava ops will create a set of ReducedOpInfos for that Op; if an Op has n optional parameters, we will then have n+1 OpInfos that delegate to that Op (n of which being ReducedOpInfos). These ReducedOpInfos can then be matched, just as any other OpInfo. **We maintain ImageJ Ops' behavior of matching subsets of optional parameters in a left-to-right manner**; this means that if there is an OpMethod: public Double foo(Double a, @optional Double b, @optional Double c) {} we can match this Op using: * a * a and b * a, b, and c. We do not allow the matching of a and c, as when b and c are the same type, passing a Double would provide no indication whether that Double should be assigned to b or c. To be deterministic we assume that optional parameters are always left off **right to left**. Using this paradigm, users can write a class Op with optional parameters as @plugin(type = Op.class, name = "foo") public class OptionalArgClass implements BiFunction<Double, Double, Double> { public Double apply( Double in1, @optional in2) { if (in2 == null) in2 = <reasonable default>; ... } } When this Op is called *without* the optional parameter, the ReducedOpInfo will use Javassist to create a *wrapper* for OptionalArgClass, passing null for each optional parameter that is omitted in the matched ReducedOpInfo. *note that the Op author is then responsible for null-checking any optional arguments*. Using the OpBuilder syntax, this Op can then be called using *either*: Double d1 = 1.0; Double d2 = 2.0; Double o = ops.op("foo").input(d1, d2).outType(Double.class).apply(); or: Double d1 = 1.0; Double o = ops.op("foo").input(d1).outType(Double.class).apply(); Ops written as methods can similarly be written with optional parameters: @OpMethod(names = "bar", type = BiFunction.class) public Double barMethod(Double in1, @optional Double in2) { if (in2 == null) in2 = <reasonable default>; ... } When writing an Op as a Field, there are two options. One can write the Op as either an anonymous Class: @OpField(names = "foobar") public final BiFunction<Double, Double, Double> foobar = new BiFunction<> { public Double apply( Double in1, @optional in2) { if (in2 == null) in2 = <reasonable default>; ... } } or, with the help of a specialized interface, using a lambda: public interface BiFunctionWithOptional<I1, I2, I3, O> extends Functions.Arity3<I1, I2, I3, O> { @OverRide public O apply(I1 in1, I2 in2, @optional I3 in3); } @OpField(names = "baz") public final BiFunctionWithOptional<Double, Double, Double, Double> baz = (in1, in2, in3) -> { if (in3 == null) in3 = <reasonable default>; ... }; To promote extensibility, each OpInfo is reduced using a InfoReducer plugin. InfoReducers are designed to be able to reduce OpInfos backed by one of a set of functional types (for example, a FunctionReducer should reduce all Functions). For each functional type an InfoReducer can reduce, it should be able to produce an OpInfo for any number of optional parameters. So if, for example, an InfoReducer is able to reduce a Functions.Arity3, it should be able to reduce that Arity3 into a BiFunction, a Function, and a Producer. OpInfo reduction is performed at plugin discovery time, and the Javassist wrapper is created when the Op is matched. Closes #14. Signed-off-by: Curtis Rueden <ctrueden@wisc.edu>
1 parent 25384ba commit a274309

File tree

42 files changed

+2201
-241
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+2201
-241
lines changed

scijava/scijava-function/src/main/java/org/scijava/function/Computers.java

Lines changed: 62 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -37,29 +37,50 @@ private Computers() {
3737

3838
/**
3939
* All known computer types and their arities. The entries are sorted by
40-
* arity, i.e., the {@code i}-th entry has an arity of {@code i}.
40+
* arity, i.e., the {@code i}-th entry has an arity of {@code i}. It might be
41+
* nice to use a BiMap from e.g. Google Guava, but that would require a
42+
* dependency on that component :(
4143
*/
42-
public static final HashMap<Class<?>, Integer> ALL_COMPUTERS;
44+
public static final HashMap<Integer, Class<?>> ALL_COMPUTERS;
45+
public static final HashMap<Class<?>, Integer> ALL_ARITIES;
4346

4447
static {
4548
ALL_COMPUTERS = new HashMap<>();
46-
ALL_COMPUTERS.put(Computers.Arity0.class, 0);
47-
ALL_COMPUTERS.put(Computers.Arity1.class, 1);
48-
ALL_COMPUTERS.put(Computers.Arity2.class, 2);
49-
ALL_COMPUTERS.put(Computers.Arity3.class, 3);
50-
ALL_COMPUTERS.put(Computers.Arity4.class, 4);
51-
ALL_COMPUTERS.put(Computers.Arity5.class, 5);
52-
ALL_COMPUTERS.put(Computers.Arity6.class, 6);
53-
ALL_COMPUTERS.put(Computers.Arity7.class, 7);
54-
ALL_COMPUTERS.put(Computers.Arity8.class, 8);
55-
ALL_COMPUTERS.put(Computers.Arity9.class, 9);
56-
ALL_COMPUTERS.put(Computers.Arity10.class, 10);
57-
ALL_COMPUTERS.put(Computers.Arity11.class, 11);
58-
ALL_COMPUTERS.put(Computers.Arity12.class, 12);
59-
ALL_COMPUTERS.put(Computers.Arity13.class, 13);
60-
ALL_COMPUTERS.put(Computers.Arity14.class, 14);
61-
ALL_COMPUTERS.put(Computers.Arity15.class, 15);
62-
ALL_COMPUTERS.put(Computers.Arity16.class, 16);
49+
ALL_ARITIES = new HashMap<>();
50+
ALL_COMPUTERS.put(0, Computers.Arity0.class);
51+
ALL_ARITIES.put(Computers.Arity0.class, 0);
52+
ALL_COMPUTERS.put(1, Computers.Arity1.class);
53+
ALL_ARITIES.put(Computers.Arity1.class, 1);
54+
ALL_COMPUTERS.put(2, Computers.Arity2.class);
55+
ALL_ARITIES.put(Computers.Arity2.class, 2);
56+
ALL_COMPUTERS.put(3, Computers.Arity3.class);
57+
ALL_ARITIES.put(Computers.Arity3.class, 3);
58+
ALL_COMPUTERS.put(4, Computers.Arity4.class);
59+
ALL_ARITIES.put(Computers.Arity4.class, 4);
60+
ALL_COMPUTERS.put(5, Computers.Arity5.class);
61+
ALL_ARITIES.put(Computers.Arity5.class, 5);
62+
ALL_COMPUTERS.put(6, Computers.Arity6.class);
63+
ALL_ARITIES.put(Computers.Arity6.class, 6);
64+
ALL_COMPUTERS.put(7, Computers.Arity7.class);
65+
ALL_ARITIES.put(Computers.Arity7.class, 7);
66+
ALL_COMPUTERS.put(8, Computers.Arity8.class);
67+
ALL_ARITIES.put(Computers.Arity8.class, 8);
68+
ALL_COMPUTERS.put(9, Computers.Arity9.class);
69+
ALL_ARITIES.put(Computers.Arity9.class, 9);
70+
ALL_COMPUTERS.put(10, Computers.Arity10.class);
71+
ALL_ARITIES.put(Computers.Arity10.class, 10);
72+
ALL_COMPUTERS.put(11, Computers.Arity11.class);
73+
ALL_ARITIES.put(Computers.Arity11.class, 11);
74+
ALL_COMPUTERS.put(12, Computers.Arity12.class);
75+
ALL_ARITIES.put(Computers.Arity12.class, 12);
76+
ALL_COMPUTERS.put(13, Computers.Arity13.class);
77+
ALL_ARITIES.put(Computers.Arity13.class, 13);
78+
ALL_COMPUTERS.put(14, Computers.Arity14.class);
79+
ALL_ARITIES.put(Computers.Arity14.class, 14);
80+
ALL_COMPUTERS.put(15, Computers.Arity15.class);
81+
ALL_ARITIES.put(Computers.Arity15.class, 15);
82+
ALL_COMPUTERS.put(16, Computers.Arity16.class);
83+
ALL_ARITIES.put(Computers.Arity16.class, 16);
6384
}
6485

6586
/**
@@ -70,7 +91,28 @@ private Computers() {
7091
* @throws NullPointerException If {@code c} is {@code null}.
7192
*/
7293
public static boolean isComputer(Class<?> c) {
73-
return ALL_COMPUTERS.containsKey(c);
94+
return ALL_COMPUTERS.containsValue(c);
95+
}
96+
97+
/**
98+
* @param arity an {@code int} corresponding to a {@code Computer} of that
99+
* arity.
100+
* @return the {@code Computer} of arity {@code arity}.
101+
* @throws IllegalArgumentException iff there is no known {@code Computer} of
102+
* arity {@code arity}.
103+
*/
104+
public static Class<?> computerOfArity(int arity) {
105+
if (ALL_COMPUTERS.containsKey(arity)) return ALL_COMPUTERS.get(arity);
106+
throw new IllegalArgumentException("No Computer of arity " + arity);
107+
}
108+
109+
/**
110+
* @param c the {@link Class} of unknown arity
111+
* @return the arity of {@code c}, or {@code -1} if {@code c} is <b>not</b> a
112+
* {@code Computer}.
113+
*/
114+
public static int arityOf(Class<?> c) {
115+
return ALL_ARITIES.getOrDefault(c, -1);
74116
}
75117

76118

scijava/scijava-function/src/main/java/org/scijava/function/Functions.java

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,29 +34,50 @@ private Functions() {
3434

3535
/**
3636
* All known function types and their arities. The entries are sorted by
37-
* arity, i.e., the {@code i}-th entry has an arity of {@code i}.
37+
* arity, i.e., the {@code i}-th entry has an arity of {@code i}. It might be
38+
* nice to use a BiMap from e.g. Google Guava, but that would require a
39+
* dependency on that component :(
3840
*/
3941
public static final HashMap<Integer, Class<?>> ALL_FUNCTIONS;
42+
public static final HashMap<Class<?>, Integer> ALL_ARITIES;
4043

4144
static {
4245
ALL_FUNCTIONS = new HashMap<>(10);
46+
ALL_ARITIES = new HashMap<>(10);
4347
ALL_FUNCTIONS.put(0, Producer.class);
48+
ALL_ARITIES.put(Producer.class, 0);
4449
ALL_FUNCTIONS.put(1, Function.class);
50+
ALL_ARITIES.put(Function.class, 1);
4551
ALL_FUNCTIONS.put(2, BiFunction.class);
52+
ALL_ARITIES.put(BiFunction.class, 2);
4653
ALL_FUNCTIONS.put(3, Functions.Arity3.class);
54+
ALL_ARITIES.put(Functions.Arity3.class, 3);
4755
ALL_FUNCTIONS.put(4, Functions.Arity4.class);
56+
ALL_ARITIES.put(Functions.Arity4.class, 4);
4857
ALL_FUNCTIONS.put(5, Functions.Arity5.class);
58+
ALL_ARITIES.put(Functions.Arity5.class, 5);
4959
ALL_FUNCTIONS.put(6, Functions.Arity6.class);
60+
ALL_ARITIES.put(Functions.Arity6.class, 6);
5061
ALL_FUNCTIONS.put(7, Functions.Arity7.class);
62+
ALL_ARITIES.put(Functions.Arity7.class, 7);
5163
ALL_FUNCTIONS.put(8, Functions.Arity8.class);
64+
ALL_ARITIES.put(Functions.Arity8.class, 8);
5265
ALL_FUNCTIONS.put(9, Functions.Arity9.class);
66+
ALL_ARITIES.put(Functions.Arity9.class, 9);
5367
ALL_FUNCTIONS.put(10, Functions.Arity10.class);
68+
ALL_ARITIES.put(Functions.Arity10.class, 10);
5469
ALL_FUNCTIONS.put(11, Functions.Arity11.class);
70+
ALL_ARITIES.put(Functions.Arity11.class, 11);
5571
ALL_FUNCTIONS.put(12, Functions.Arity12.class);
72+
ALL_ARITIES.put(Functions.Arity12.class, 12);
5673
ALL_FUNCTIONS.put(13, Functions.Arity13.class);
74+
ALL_ARITIES.put(Functions.Arity13.class, 13);
5775
ALL_FUNCTIONS.put(14, Functions.Arity14.class);
76+
ALL_ARITIES.put(Functions.Arity14.class, 14);
5877
ALL_FUNCTIONS.put(15, Functions.Arity15.class);
78+
ALL_ARITIES.put(Functions.Arity15.class, 15);
5979
ALL_FUNCTIONS.put(16, Functions.Arity16.class);
80+
ALL_ARITIES.put(Functions.Arity16.class, 16);
6081
}
6182

6283
/**
@@ -67,7 +88,41 @@ private Functions() {
6788
* @throws NullPointerException If {@code c} is {@code null}.
6889
*/
6990
public static boolean isFunction(Class<?> c) {
70-
return ALL_FUNCTIONS.containsValue(c);
91+
try {
92+
Class<?> superType = superType(c);
93+
return true;
94+
} catch (IllegalArgumentException e) {
95+
return false;
96+
}
97+
}
98+
99+
public static Class<?> superType(Class<?> c) {
100+
if (ALL_FUNCTIONS.containsValue(c)) return c;
101+
for(Class<?> func : ALL_ARITIES.keySet()) {
102+
if (func.isAssignableFrom(c)) return func;
103+
}
104+
throw new IllegalArgumentException(c + " is not a subclass of any known Functions!");
105+
}
106+
107+
/**
108+
* @param arity an {@code int} corresponding to a {@code Function} of that
109+
* arity.
110+
* @return the {@code Function} of arity {@code arity}.
111+
* @throws IllegalArgumentException iff there is no known {@link Function} of
112+
* arity {@code arity}.
113+
*/
114+
public static Class<?> functionOfArity(int arity) {
115+
if (ALL_FUNCTIONS.containsKey(arity)) return ALL_FUNCTIONS.get(arity);
116+
throw new IllegalArgumentException("No Function of arity " + arity);
117+
}
118+
119+
/**
120+
* @param c the {@link Class} of unknown arity
121+
* @return the arity of {@code c}, or {@code -1} if {@code c} is <b>not</b> a
122+
* {@code Function}.
123+
*/
124+
public static int arityOf(Class<?> c) {
125+
return ALL_ARITIES.getOrDefault(c, -1);
71126
}
72127

73128
/**

0 commit comments

Comments
 (0)