chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
# Inheritance
|
||||
|
||||
## Status
|
||||
ACCEPTED
|
||||
|
||||
Discussed by Alex Black and Paul Dubs on 29. November 2019 and 2. December 2019.
|
||||
|
||||
## Context
|
||||
In many cases ops have a similar interface. For example all transform ops take a single input, but some of them take
|
||||
additional arguments; all pairwise ops take two inputs, and so on. The documentation of those ops is often the result
|
||||
of copy & paste with just a few little modifications, and changing anything later on suddenly becomes a huge undertaking
|
||||
because what should effectively be a change in a single place, has to be changed in many places.
|
||||
|
||||
Another issue that copy & paste based definitions bring to the table is that this practice effectively makes any
|
||||
relationship between those ops implicit.
|
||||
|
||||
When defining ops with the DSL we prefer to make things as explicit as possible, while also reducing repetition and
|
||||
boilerplate.
|
||||
|
||||
The existing inheritance mechanism, added without a formal proposal at the beginning of the project, allows the
|
||||
definition of an abstract base op. The new op based on it, can copy any of the given parts before it gets to define its
|
||||
own properties. This approach has the problem that we can't inherit from multiple base ops, and that we do not get any
|
||||
direct access to its fields for ease of use.
|
||||
|
||||
# Decision
|
||||
We introduce an explicit mixin mechanism `Mixin("name") {...}` which can define any parts of any op, but isn't an Op
|
||||
definition on its own. Mixins can be defined at top-level, thereby being usable across namespaces.
|
||||
|
||||
A mixin is mixed into an op with `useMixin(mixinReference, ...options...)` within an op context. It will add all (if
|
||||
not otherwise configured) definitions of the mixin to the current op as if they were copied into its place.
|
||||
If `useMixin(ref)` is used as the first thing within an op definition, then it will behave exactly like the old
|
||||
inheritance mechanism.
|
||||
|
||||
`useMixin(ref)` returns a holder object, that can be used to reference its parameters.
|
||||
|
||||
The available options on `useMixin` are `keepInputs`, `keepArgs`, `keepOutputs`, `keepSignatures`, `keepDoc`,
|
||||
`keepConstraints`. They default to `true`.
|
||||
|
||||
If there is a naming conflict between mixins or between mixin and op definition, the last definition wins.
|
||||
|
||||
### Example
|
||||
```kotlin
|
||||
|
||||
val indexAccum = Mixin("indexAccum"){
|
||||
legacy = true
|
||||
javaPackage = "org.nd4j.linalg.api.ops.impl.indexaccum"
|
||||
val input = Input(NUMERIC, "in") { description = "Input variable" }
|
||||
val keepDims = Arg(BOOL, "keepDims") { description = "If true: keep the dimensions that are reduced on (as length 1). False: remove the reduction dimensions"; defaultValue = false }
|
||||
val dims = Arg(INT, "dimensions"){ count = AtLeast(1); description = "Dimensions to reduce over. If dimensions are not specified, full array reduction is performed" }
|
||||
Output(NUMERIC, "output"){ description = "Reduced array of rank (input rank - num dimensions)" }
|
||||
|
||||
Signature(input, dims)
|
||||
AllParamSignature(withOutput = false)
|
||||
}
|
||||
|
||||
|
||||
Namespace("math"){
|
||||
Op("firstIndex") {
|
||||
val idxAccum = useMixin(indexAccum, keepSignatures=false)
|
||||
var c = Arg(CONDITION, "condition") { description = "Condition to check on input variable" }
|
||||
Signature(idxAccum.input("in"), c, idxAccum.arg("dimensions"))
|
||||
Signature(idxAccum.input("in"), c, idxAccum.arg("keepDims"), idxAccum.arg("dimensions"))
|
||||
|
||||
Doc(Language.ANY, DocScope.ALL){
|
||||
"""
|
||||
First index reduction operation.
|
||||
Returns a variable that contains the index of the first element that matches the specified condition (for each
|
||||
slice along the specified dimensions)
|
||||
Note that if keepDims = true, the output variable has the same rank as the input variable,
|
||||
with the reduced dimensions having size 1. This can be useful for later broadcast operations (such as subtracting
|
||||
the mean along a dimension).
|
||||
Example: if input has shape [a,b,c] and dimensions=[1] then output has shape:
|
||||
keepDims = true: [a,1,c]
|
||||
keepDims = false: [a,c]
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Consequences
|
||||
### Advantages
|
||||
* We can have multiple inheritance
|
||||
* We can share op similarities across namespaces
|
||||
* We get explicit access to parameters defined in mixins
|
||||
|
||||
### Disadvantages
|
||||
* We have to adapt our current usage
|
||||
Reference in New Issue
Block a user