Implement basic dispatch. #10

Open
opened 2024-10-21 22:03:27 +00:00 by glen · 6 comments
Owner

I believe all is ready to write a simple dispatcher.

I believe all is ready to write a simple dispatcher.
Collaborator

Yes! Are you going to give this a shot? As soon as there is a bare-bone implementation, I think I can start working on implementing more functions for it.

Yes! Are you going to give this a shot? As soon as there is a bare-bone implementation, I think I can start working on implementing more functions for it.
Author
Owner

Indeed, I have already begun playing around with the code. Since this is a prototype/proof of concept, I was going to take an initial stab at a slightly different architecture for the dispatcher:

In typed-function, signatures are sorted, and then selected by type-testing the actual arguments one-by-one against each signature. The operation "typeOf" is then implemented by assembling a number of constant functions with different signatures.

In the initial prototype I am trying, the operation "typeOf" will be more fundamental, and implemented by special-purpose code that goes through the sorted list of registered types. Then to dispatch a call, typeOf is applied to the actual arguments to produce a string encoding the actual argument call signature, e.g. number,Complex<number>,string. This string is then used as a key with which to look up the proper implementation. If the lookup fails, there will be a backup implementation that for example checks if any new template instantiations need to be created to handle this argument list (updating the lookup table for the operation, if so, before dispatching to that implementation).

A couple of questions:

(A) Do you know of any reason why actual-call-signature based hash map lookup to get the implementation, rather than sequential run-the-type-tests selection, is a bad idea?

(B) If not, do you have any intuition as to whether it's better to use a JavaScript Map or a plain object for the hash map from signature keys to implementations?

Indeed, I have already begun playing around with the code. Since this is a prototype/proof of concept, I was going to take an initial stab at a slightly different architecture for the dispatcher: In typed-function, signatures are sorted, and then selected by type-testing the actual arguments one-by-one against each signature. The operation "typeOf" is then implemented by assembling a number of constant functions with different signatures. In the initial prototype I am trying, the operation "typeOf" will be more fundamental, and implemented by special-purpose code that goes through the sorted list of registered types. Then to dispatch a call, typeOf is applied to the actual arguments to produce a string encoding the actual argument call signature, e.g. `number,Complex<number>,string`. This string is then used as a key with which to look up the proper implementation. If the lookup fails, there will be a backup implementation that for example checks if any new template instantiations need to be created to handle this argument list (updating the lookup table for the operation, if so, before dispatching to that implementation). A couple of questions: (A) Do you know of any reason why actual-call-signature based hash map lookup to get the implementation, rather than sequential run-the-type-tests selection, is a bad idea? (B) If not, do you have any intuition as to whether it's better to use a JavaScript Map or a plain object for the hash map from signature keys to implementations?
Author
Owner

OK, I have pushed https://code.studioinfinity.org/glen/math5/src/branch/feat/dispatch_type. If you run it (with pnpm go), you should see that it is correctly typing numbers and (possibly nested, i.e. quaternion) Complex numbers. That's all it does so far; it ignores all implementations. Next step is to record all of the implementations and then build the actual behaviors, or at least a first pass at them. In addition to the questions in the previous comment:

(C) Any comments/thoughts so far?

(D) Rather than just build up a long index.ts that tries various things, I thought it would be good right away to switch to a test harness and implement pnpm test and put all of the expected outputs, like the type tests for these sample values, into the tests. Do you have a recommended test harness for us to use? I checked to refresh my memory and mathjs uses mocha; is that your preference, or is there something you feel would be better in this prototype situation? I see I also used mocha in the pocomath prototype, which was the last one to have a test harness, if I recall. So I am assuming that's the way to go, but thought I'd get your input before setting one up here, in case your preferences have changed.

OK, I have pushed https://code.studioinfinity.org/glen/math5/src/branch/feat/dispatch_type. If you run it (with `pnpm go`), you should see that it is correctly typing numbers and (possibly nested, i.e. quaternion) Complex numbers. That's all it does so far; it ignores all implementations. Next step is to record all of the implementations and then build the actual behaviors, or at least a first pass at them. In addition to the questions in the previous comment: (C) Any comments/thoughts so far? (D) Rather than just build up a long index.ts that tries various things, I thought it would be good right away to switch to a test harness and implement `pnpm test` and put all of the expected outputs, like the type tests for these sample values, into the tests. Do you have a recommended test harness for us to use? I checked to refresh my memory and mathjs uses mocha; is that your preference, or is there something you feel would be better in this prototype situation? I see I also used mocha in the `pocomath` prototype, which was the last one to have a test harness, if I recall. So I am assuming that's the way to go, but thought I'd get your input before setting one up here, in case your preferences have changed.
Author
Owner

P.S. Note that pocomath was strictly limited to a single type parameter for any generic type. You will see a couple of code changes in this branch attempting not to lock math5 into this same restriction. I don't specifically have a use case for a generic type with more than one type parameter, but I do have the sense that we might regret the restriction if we bake it in. One example that is not too contrived is that mathjs has a "function" type; math5 might conceivably want to have a Function<Domain, Range> type. Of course, to make that work for multi-argument functions, we would need to be able to deal with TypeScript tuple types, so that we could write Function<[number,number,string], boolean> for a function that takes 3 arguments, two numbers and a string, and returns a boolean. Anyhow, that's all very speculative; at the moment just mentioning that I am also trying to allow for multiple type parameters in this prototype.

P.S. Note that pocomath was strictly limited to a single type parameter for any generic type. You will see a couple of code changes in this branch attempting not to lock math5 into this same restriction. I don't specifically have a use case for a generic type with more than one type parameter, but I do have the sense that we might regret the restriction if we bake it in. One example that is not too contrived is that mathjs has a "function" type; math5 might conceivably want to have a `Function<Domain, Range>` type. Of course, to make that work for multi-argument functions, we would need to be able to deal with TypeScript tuple types, so that we could write `Function<[number,number,string], boolean>` for a function that takes 3 arguments, two numbers and a string, and returns a boolean. Anyhow, that's all very speculative; at the moment just mentioning that I am also trying to allow for multiple type parameters in this prototype.
Collaborator

(A) About the actual-call-signature idea: I think a drawback may be performance, but it really requires a benchmark to figure that out (creating strings is slow, but a lookup in a map is fast). It could also be that it actually is faster.

(B) I think it would be a good idea to use Map, since using object comes with some security risks (object contains proto, constructor, toString, etc and you have to use hasOwnProperty and stuff like that). Also, Map seems to be as fast or faster as object.

(C) No other thoughts so far (though really happy to see the first actual code doing stuff :) )

(D) Yes I love setting up tests in an early stage. I have a preference for using vitest. Vitest works out of the box with TypeScript, ESM, etc. For bundling we use the Library Mode of vite.

P.S. Note that pocomath was strictly limited to a single type parameter for any generic type.

Sounds good. Let's keep an eye on the complexity that it adds, if that grows unwieldy it may help to choose some limitation. I don't expect that though, and you're right, it's easy to add now but hard to add in a later stage.

(A) About the actual-call-signature idea: I think a drawback may be performance, but it really requires a benchmark to figure that out (creating strings is slow, but a lookup in a map is fast). It could also be that it actually is faster. (B) I think it would be a good idea to use `Map`, since using object comes with some security risks (object contains __proto__, constructor, toString, etc and you have to use `hasOwnProperty` and stuff like that). Also, Map seems to be as fast or faster as object. (C) No other thoughts so far (though really happy to see the first actual code doing stuff :) ) (D) Yes I love setting up tests in an early stage. I have a preference for using [`vitest`](https://vitest.dev/). Vitest works out of the box with TypeScript, ESM, etc. For bundling we use the Library Mode of [`vite`](https://vite.dev/guide/build.html#library-mode). > P.S. Note that pocomath was strictly limited to a single type parameter for any generic type. Sounds good. Let's keep an eye on the complexity that it adds, if that grows unwieldy it may help to choose some limitation. I don't expect that though, and you're right, it's easy to add now but hard to add in a later stage.
Author
Owner

OK, given that all looks good so far, and signature-based dispatch isn't necessarily a totally stupid idea, I am going to just go ahead and merge into main. Next step will be to add tests with vitest, I'll file an issue for this and then do a PR that we will make sure runs for you before it's merged. Thanks!

OK, given that all looks good so far, and signature-based dispatch isn't necessarily a totally stupid idea, I am going to just go ahead and merge into main. Next step will be to add tests with vitest, I'll file an issue for this and then do a PR that we will make sure runs for you before it's merged. Thanks!
Sign in to join this conversation.
No Label
No Milestone
No project
No Assignees
2 Participants
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: glen/math5#10
No description provided.