Update Item Specifications
parent
7d95c72f70
commit
6c1b3f062c
1 changed files with 11 additions and 12 deletions
|
@ -35,21 +35,20 @@ This seems a trifle "heavy" but I'm not seeing clear ways at the moment to strea
|
|||
|
||||
Note the above implementation of square works for all types, so there is not any type matching going on in the definition. (Presumably this creates a fallback universal implementation, and you could still add specialized implementations, say on a Boolean argument.) 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)]
|
||||
export const multiply = onType([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.
|
||||
Here, onType creates one or more "patterns" (an array of types to match against the actual arguments) with associated behaviors, which can be exported to produce implementations for a method that will be dispatched when the arguments match the pattern. The fact that the behavior is return-type-labeled 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]) => {
|
||||
export const add = onType([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)`, say. 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.
|
||||
|
||||
|
@ -57,7 +56,7 @@ Putting a few features together, consider `sqrt` of a number, which produces dif
|
|||
|
||||
```
|
||||
// don't bother with the signature argument in the factory, since we know it's [Number]
|
||||
export const sqrt = [[Number], math => {
|
||||
export const sqrt = onType(Number, math => {
|
||||
if (math.config.predictable || !math.types.Complex) {
|
||||
return Returns(Number, Math.sqrt)
|
||||
}
|
||||
|
@ -70,14 +69,14 @@ export const sqrt = [[Number], math => {
|
|||
}]
|
||||
```
|
||||
|
||||
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 when 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.
|
||||
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 when 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. Also note the convenience that for unary signatures, you can supply just a type; you don't have to put it in braces.
|
||||
|
||||
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 const tau = [
|
||||
[Number], 6.2831853,
|
||||
[BigNumber], math => math.bignumber('6.28318530717958647692'),
|
||||
export const tau = onType(
|
||||
Number, 6.2831853,
|
||||
BigNumber, math => math.bignumber('6.28318530717958647692'),
|
||||
[], math => math.resolve('tau', [math.config.constants])
|
||||
]
|
||||
```
|
||||
|
@ -85,10 +84,10 @@ A few things to note in this example: now clients of the eventual bundle can use
|
|||
|
||||
Finally, let's see how the troubling example of absquare in https://code.studioinfinity.org/glen/pocomath/issues/55 would pan out:
|
||||
```
|
||||
export const absquare = [[Complex], (math, [T]) => {
|
||||
export const absquare = onType([Complex], (math, [T]) => {
|
||||
const absq = math.absq.resolve(T.Base)
|
||||
const add = math.add.resolve(absq.returns, absq.returns)
|
||||
return Returns(add.returns, z => add(absq(z.re), absq(z.im))
|
||||
}
|
||||
})
|
||||
```
|
||||
This seems like a success of this scheme to me. Hence, the current plan is to recast the prototype into this format.
|
Loading…
Add table
Reference in a new issue