|
1 | | -The SciJava Ops projects offers a framework for typed operations, or Ops. |
| 1 | +# SciJava Ops Engine: A default implementation of SciJava Ops API |
2 | 2 |
|
3 | | -Each op implements a particular functional interface, possessing typed inputs |
4 | | -or outputs. The system provides the ability to request ops matching particular |
5 | | -constraints, including implementing interface, input types and output types. |
| 3 | +This module ([currently](https://github.com/scijava/scijava/issues/55)) uses SciJava Common's `Plugin` framework to create the `DefaultOpEnvironment`. By discovering Op implementations at compile time, there is no need to provide an explicit list of Op classes. |
6 | 4 |
|
7 | | -It is like Java's method overloading, but more powerful, and more extensible. |
| 5 | +The `DefaultOpEnvironment` utilizes many different routines to match as many Ops as possible. These routines are outlined below, along with a given example Op: |
8 | 6 |
|
9 | | -More documentation coming later. |
| 7 | +```java |
| 8 | +@OpField(names = "math.add") |
| 9 | +public final BiFunction<Number, Number, Double> fooOp = (in1, in2) -> in1.doubleValue() + in2.doubleValue(); |
| 10 | +``` |
10 | 11 |
|
11 | | -See also [ImageJ Ops](https://github.com/imagej/imagej-ops), a collection of |
12 | | -ops focused on scientific image processing and analysis. |
| 12 | +## Direct matching |
| 13 | + |
| 14 | +The simplest type of Op matching involves a direct comparison on the functional types of the requested Op and the Ops known to the environment. Thus our example `math.add` Op might be returned as a direct match for the following `OpBuilder` call: |
| 15 | + |
| 16 | +```java |
| 17 | +BiFunction<Number, Number, Double> op = OpEnvironment.op("math.add") // |
| 18 | + .inType(Number.class, Number.class) // |
| 19 | + .outType(Double.class) // |
| 20 | + .function(); |
| 21 | +``` |
| 22 | + |
| 23 | +## Runtime-safe matching |
| 24 | + |
| 25 | +`DefaultOpEnvironment` can also return Ops whose assignment would throw compile-time errors, but are safe to call at runtime. These situations often arise with function generics, when the requested input types are more specific than those defined by the Op, or the requested output type more generic than that declared by the Op. Runtime-safe matching provides much flexibility, allowing users to deviate from the Op's signature when more convenient. Thus our example `math.add` Op might be returned as a runtime-safe match for the following `OpBuilder` call: |
| 26 | + |
| 27 | +```java |
| 28 | +BiFunction<Double, Double, Number> op = OpEnvironment.op("math.add") // |
| 29 | + .inType(Double.class, Double.class) // |
| 30 | + .outType(Number.class) // |
| 31 | + .function(); |
| 32 | +``` |
| 33 | + |
| 34 | +Since `Double` is a subtype of `Number`, it is assignable to `Number` and that the inputs we wish to provide to the Op will satisfy the Op we have. A similar argument can be made about the output type. |
| 35 | + |
| 36 | +## Adaptation |
| 37 | + |
| 38 | +`DefaultOpEnvironment` is able to retype Ops whose functional type is not the same as the requested type when it is aware of the Ops needed to do the retyping. For example, a `BiFunction<I1, I2, O>` can be converted into a `Computer.Arity2<I1, I2, O>` iff there exists a `Computers.Arity1<O, O>` `copy` Op to copy the output of the `BiFunction` into the output provided by the `Computer`. Thus, supposing we have some Op: |
| 39 | + |
| 40 | +```java |
| 41 | +@OpField(names = list.populator) |
| 42 | +public final BiFunction<Double, Double, List<Double>> barOp = (in1, in2) -> Arrays.asList(in1, in2); |
| 43 | +``` |
| 44 | + |
| 45 | +it might be returned as an adaptation for the following `OpBuilder` call: |
| 46 | + |
| 47 | + |
| 48 | +```java |
| 49 | +Computers.Arity2<Double, Double, List<Double>> op = OpEnvironment.op("math.add") // |
| 50 | + .inType(Double.class, Double.class) // |
| 51 | + .outType(new Nil<List<Double>>() {}) // note the need for a Nil, because we need to specify a generic type. |
| 52 | + .computer(); |
| 53 | +``` |
| 54 | + |
| 55 | +## Simplification |
| 56 | + |
| 57 | +`DefaultOpEnvironment` is able to retype Ops whose input and output parameter types are not the same as the requested arguments types when it is aware of the Ops needed to do the retyping. For example, a `BiFunction<A1, A2, O>` can be converted into a `BiFunction<B1, B2, P>` iff there exists a chain of Ops to convert from `A1` to `B1`, from `A2` to `B2`, and from `O` to `P`. Thus, supposing we have some Op: |
| 58 | + |
| 59 | +```java |
| 60 | +@OpField(names = list.populator) |
| 61 | +public final BiFunction<Double, Double, List<Double>> barOp = (in1, in2) -> Arrays.asList(in1, in2); |
| 62 | +``` |
| 63 | + |
| 64 | +it might be returned as a simplification for the following `OpBuilder` call: |
| 65 | + |
| 66 | + |
| 67 | +```java |
| 68 | +BiFunction<Integer, Integer, List<Integer>> op = OpEnvironment.op("math.add") // |
| 69 | + .inType(Integer.class, Integer.class) // |
| 70 | + .outType(new Nil<List<Integer>>() {}) // note the need for a Nil, because we need to specify a generic type. |
| 71 | + .function(); |
| 72 | +``` |
| 73 | + |
| 74 | +## Reduction |
| 75 | + |
| 76 | +`DefaultOpEnvironment` supports optional parameters by enabling the retrieval Ops **with or without** their optional parameters (denoted with the `@Optional` annotation). Parameters can only be optional when **all input parameters to its right in the signature are also optional**. When a parameter is declared as optional, `null` is passed to the parameter and it is the **Op's** responsibility to replace that `null` value with a reasonable default. To prevent confusion, an optional parameter can **only** be omitted when all optional parameters to its right are **also omitted**. Thus, supposing we have some Op: |
| 77 | + |
| 78 | +```java |
| 79 | +@Plugin(type = Op.class, name = math.add) |
| 80 | +public class bazOp implements BiFunction<Double, Double, Double> { |
| 81 | + |
| 82 | + @Override |
| 83 | + public Double apply(Double in1, @Optional Double in2) { |
| 84 | + if (in2 == null) in2 = 0.0; |
| 85 | + return in1 + in2; |
| 86 | + |
| 87 | + } |
| 88 | + |
| 89 | +} |
| 90 | +``` |
| 91 | + |
| 92 | +it might be returned as a reduction for the following `OpBuilder` call: |
| 93 | + |
| 94 | + |
| 95 | +```java |
| 96 | +BiFunction<Double, Double, Double> op = OpEnvironment.op("math.add") // |
| 97 | + .inType(Double.class) // |
| 98 | + .outType(Double.class) // |
| 99 | + .function(); |
| 100 | +``` |
0 commit comments