Add return convention choice to config and implement sqrt for all relevant types. #25

Closed
opened 2025-04-25 14:19:43 +00:00 by glen · 1 comment
Owner

As per the discussion in https://github.com/josdejong/mathjs/issues/3374, we want to have a "free" return mode in which nanomath can use the "best" type for the return value of an operation, which could be "simpler" than the input types (like a bigint resulting from some fraction operation) or more "complicated" than the input types (like a Complex coming from an operation on numbers). But we also need a return mode in which there is one single type that can result from a given operation on particular types. In fact, there's a presumption that there's a need for mathjs existing predictable = true mode, in which the output type matches the input types insofar as possible, perhaps using NaN in the case of number, and throws if that simply is not possible. But for the sake of chaining in the internal implementations of nanomath methods, there also appears to be a need for a "fully defined" mode, in which the single return type is chosen so that the operation will always have an appropriately mathematically accurate result regardless of the specific values of the arguments, so long as the arguments are of the particular types.

As per the discussion in https://github.com/josdejong/mathjs/issues/3374, we want to have a "free" return mode in which nanomath can use the "best" type for the return value of an operation, which could be "simpler" than the input types (like a bigint resulting from some fraction operation) or more "complicated" than the input types (like a Complex coming from an operation on numbers). But we also need a return mode in which there is one single type that can result from a given operation on particular types. In fact, there's a presumption that there's a need for mathjs existing `predictable = true` mode, in which the output type _matches_ the input types insofar as possible, perhaps using NaN in the case of number, and throws if that simply is not possible. But for the sake of chaining in the internal implementations of nanomath methods, there also appears to be a need for a "fully defined" mode, in which the single return type is chosen so that the operation will always have an appropriately mathematically accurate result regardless of the specific values of the arguments, so long as the arguments are of the particular types.
glen added the
feature
priority
labels 2025-04-25 14:19:43 +00:00
glen changed title from Add predictable to config and implement sqrt for all relevant types. to Add return convention choice to config and implement sqrt for all relevant types. 2025-04-26 01:10:25 +00:00
Author
Owner

Therefore, we are going to prototype the following system as an experiment. It differs from current mathjs, so if the prototype is deemed successful, we will have to contemplate how to "match up" the system as we adapt mathjs onto the nanomath engine. But it seems as though this is an opportunity to explore options, and that the system may offer worthwhile benefits, so it's worth a try.

We will set up a returnTyping enumeration, for now with three values: free, conservative, and full, corresponding to the three strategies described above for choosing return types from operations. The stored (factory-produced, final) behaviors for a given operation will be indexed not only by the argument types, but also by the returnTyping value. Therefore, resolve will have a second argument, a returnTyping value. It will default to the returnTyping property in the current configuration, but can also simply be specified directly, so that implementations can choose their desired return typing strategy for their dependencies without disrupting the configuration. Moreover, behaviors will not need to change when the configuration changes; simply different behaviors can be selected (and generated as needed).

Finally, there is an issue as to what the value of the .returns property will be on behaviors that were generated under the free strategy and so might return one of two or more different types. It appears there is no choice but to add something nanomath has resisted so far: true union types that represent the set-theoretic union of two or more types. That feature could create some complexity in resolution. Currently math.resolve() is used in two ways: (A) to choose which behavior to call on a specific list of arguments, and (B) for implementations to choose which behavior of their dependencies to compile into their own behavior when building it in a factory. In use (A), there can only be single concrete types for each argument, as each one is assigned a specific type by math.typeOf. However, in (B), imagine that an implementation wants to apply multiply to the result of sqrt under the free strategy. That result may well be a union. Since nanomath behaviors are meant to deal with specific types, there wouldn't really be a way to resolve that multiply with a single function. In other words, it seems like it will be necessary to maintain the convention that the type-list argument to resolve remains single types. In that case, implementations that want to deal with dependencies with the free strategy may need facilities to unpack the unions, and resolve multiple behaviors for their other dependencies, and select among them at runtime depending on the type that actually results from the free-strategy dependency. Of course, this may simply mean that implementations in practice never use the free return typing strategy for their dependencies. That's part of the point of this prototype.

Therefore, we are going to prototype the following system as an experiment. It differs from current mathjs, so if the prototype is deemed successful, we will have to contemplate how to "match up" the system as we adapt mathjs onto the nanomath engine. But it seems as though this is an opportunity to explore options, and that the system may offer worthwhile benefits, so it's worth a try. We will set up a `returnTyping` enumeration, for now with three values: `free`, `conservative`, and `full`, corresponding to the three strategies described above for choosing return types from operations. The stored (factory-produced, final) behaviors for a given operation will be indexed not only by the argument types, but also by the `returnTyping` value. Therefore, `resolve` will have a second argument, a `returnTyping `value. It will _default_ to the `returnTyping` property in the current configuration, but can also simply be specified directly, so that implementations can choose their desired return typing strategy for their dependencies without disrupting the configuration. Moreover, behaviors will not need to _change_ when the configuration changes; simply different behaviors can be _selected_ (and generated as needed). Finally, there is an issue as to what the value of the `.returns` property will be on behaviors that were generated under the `free` strategy and so might return one of two or more different types. It appears there is no choice but to add something nanomath has resisted so far: true union types that represent the set-theoretic union of two or more types. That feature could create some complexity in resolution. Currently `math.resolve()` is used in two ways: (A) to choose which behavior to call on a specific list of arguments, and (B) for implementations to choose which behavior of their dependencies to compile into their own behavior when building it in a factory. In use (A), there can only be single concrete types for each argument, as each one is assigned a specific type by `math.typeOf`. However, in (B), imagine that an implementation wants to apply `multiply` to the result of `sqrt` under the `free` strategy. That result may well be a union. Since nanomath behaviors are meant to deal with specific types, there wouldn't really be a way to resolve that `multiply` with a single function. In other words, it seems like it will be necessary to maintain the convention that the type-list argument to `resolve` remains single types. In that case, implementations that want to deal with dependencies with the `free` strategy may need facilities to unpack the unions, and resolve multiple behaviors for their other dependencies, and select among them at runtime depending on the type that actually results from the `free`-strategy dependency. Of course, this may simply mean that implementations in practice never use the `free` return typing strategy for their dependencies. That's part of the point of this prototype.
glen closed this issue 2025-04-28 16:29:34 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: StudioInfinity/nanomath#25
No description provided.