Update Item Specifications
parent
587be22dd5
commit
ffe5d5b587
1 changed files with 51 additions and 4 deletions
|
@ -11,7 +11,7 @@ In this concept, we think of the TypeDispatcher object `math` simply as a big ma
|
|||
How would this look in practice? Let's start by considering a `square` function that can operate on any type and depends on the `multiply` entry for that same type, and just calls that entry with both arguments set to the original single argument. We might specify this as:
|
||||
```
|
||||
export const square = (math, [T]) => {
|
||||
const mul = math.get('multiply', [T, T])
|
||||
const mul = math.multiply.resolve(T, T)
|
||||
return a => mul(a, a)
|
||||
}
|
||||
```
|
||||
|
@ -21,17 +21,64 @@ export const square = (math, [T]) => {
|
|||
return a => math.multiply(a, a)
|
||||
}
|
||||
```
|
||||
That's because in order to work as a dependency, the dependent items must be extracted within the body of the function that provides the final behavior/value for the item, not within that behavior.
|
||||
That's because in order to work as a dependency, the dependent items must be extracted within the _factory_ for the item, not within the behavior that the factory returns.
|
||||
|
||||
There's another wrinkle; we want to record the return type of all of the methods, both for writing TypeScript type definition files and for making it easier to select the best implementations for other methods. So we will really need to write something like:
|
||||
```
|
||||
export const square = (math, [T]) => {
|
||||
const mul = math.get('multiply', [T, T])
|
||||
const mul = math.multiply.resolve(T, T)
|
||||
return Returns(mul.returns, a => mul(a, a))
|
||||
}
|
||||
```
|
||||
|
||||
This seems a trifle "heavy" but I'm not seeing clear ways at the moment to streamline it.
|
||||
|
||||
Another point here is that this implementation of square works for all types, so there is not any type matching going on in the definition. So let's take a look at what we would have to do for the `multiply` implementation on two numbers -- a plain function with no dependencies. Something like
|
||||
Note the above implementation of square works for all types, so there is not any type matching going on in the definition. So let's take a look at what we would have to do for the `multiply` implementation on two numbers -- a plain function with no dependencies. Something like
|
||||
```
|
||||
export const multiply = [[Number, Number], Returns(Number, (a, b) => a * b)]
|
||||
```
|
||||
Here, the export being an array alternating signature patterns and export items signals that specific types for the `multiply` key are being populated; and the fact that a
|
||||
return-type-labeled item is immediately supplied indicates that the item has no dependencies.
|
||||
|
||||
Next, let's look at a method like addition of two complex numbers that accepts many types (any instantiation of Complex) but not all types, and has dependencies:
|
||||
```
|
||||
export const add = [[Complex, Complex]], (math, [T, U]) => {
|
||||
const add = math.add.resolve(T.Base, U.Base)
|
||||
const complex = math.complex.resolve(add.returns, add.returns)
|
||||
return Returns(
|
||||
complex.returns,
|
||||
(w, z) => complex(add(w.re, z.re), add(w.im, z.im))
|
||||
)
|
||||
}]
|
||||
```
|
||||
Note that the Complex types in the signature specification are not "ground" types -- they are generic types, and so serve as patterns that say that this export specifies the value for any two-entry signature in which each entry is an instance of complex. We then get the actual call types `T` and `U` in the factory function -- they could conceivably be `Complex(BigInt)` and `Complex(Number)`. Any two complex types will be fine, so long as we can add their base types, which we will discover when we try to fetch the `add` implementation on those two types.
|
||||
|
||||
Putting a few features together, consider `sqrt` of a number, which produces different implementations depending on what types are available and the configuration:
|
||||
|
||||
```
|
||||
// don't bother with the signature argument in the factory, since we know it's [Number]
|
||||
export const sqrt = [[Number], math => {
|
||||
if (math.config.predictable || !math.types.Complex) {
|
||||
return Returns(Number, Math.sqrt)
|
||||
}
|
||||
const complex = math.complex.resolve(Number, Number)
|
||||
return Returns(Union(Number, Complex(Number)), a => {
|
||||
if (isNaN(a)) return NaN
|
||||
if (n >= 0) return Math.sqrt(a)
|
||||
return complex(0, Math.sqrt(-a)
|
||||
})
|
||||
}]
|
||||
```
|
||||
|
||||
Note here that ideally the accesses to `math.config.predictable` and `math.types.Complex` in this factory mean that the resulting implementation of `sqrt` will depend on the `predictable` property of `math.config`, and so only be invalidated with that property changes, not on any change to `math.config`, just the way that the other possible implementation would only be invalidated if the implementation of `complex` on `Number, Number` were updated, but unaffected if the implementation of `complex` on `bigint, bigint` changed.
|
||||
|
||||
Looking at how other config properties might work, let's suppose that the `constants` property gives the numeric type that should be used for named constants (e.g., Number or BigNumber). We want `math.tau` to give a scalar entity, but we'd also like to record different possible entities for different types. Since we can't put properties on a number, we use a resolve function directly on the math object that takes the identifier to resolve:
|
||||
|
||||
```
|
||||
export tau = [
|
||||
[Number], 6.2831853,
|
||||
[BigNumber], math => math.bignumber('6.28318530717958647692'),
|
||||
[], math => math.resolve('tau', [math.config.constants])
|
||||
]
|
||||
```
|
||||
A few things to note in this example: now clients of the eventual bundle can use `math.resolve('tau', math.BigNumber)` to get the BigNumber value of tau regardless of the current type in the config.constants setting, the plain `math.tau` should resolve to the correct scalar entity, and the plain `math.tau` should be reset if either config.constants changes or if the setting of math.tau on the type config.constants changes.
|
Loading…
Add table
Add a link
Reference in a new issue