Update Item Specifications

Glen Whitney 2025-04-02 05:17:19 +00:00
parent 7d95c72f70
commit 6c1b3f062c

@ -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.