Alternatives for the $reflect macro at the bottom of the files #21

Closed
opened 2024-10-03 13:45:36 +00:00 by josdejong · 2 comments
Collaborator

@glen just thinking aloud here (without remembering all the prototype variants we tried out a year ago): I quite like the dispatch_refactor API except for the need for the separate $reflect macro at the bottom of files:

export const add = (): Signature<'add', number> => (a, b) => a + b
 
// ...

$reflect!([add, ...])

Did we try out something like the following last year? Basically having the macro enclosing the function and optionally define multiple functions in one go:

export const add = $reflect!<'add', number>(() => (a, b) => a + b)

// or more in the spirit of `math5`
export const arithmetics = $reflect!<number>({
  add: (a, b) => a + b,
  // ...
})

If not I may see if I can get something like that to work (unless such an API is something that you would strongly dislike).

@glen just thinking aloud here (without remembering all the prototype variants we tried out a year ago): I quite like the `dispatch_refactor` API except for the need for the separate `$reflect` macro at the bottom of files: ```ts export const add = (): Signature<'add', number> => (a, b) => a + b // ... $reflect!([add, ...]) ``` Did we try out something like the following last year? Basically having the macro enclosing the function and optionally define multiple functions in one go: ```ts export const add = $reflect!<'add', number>(() => (a, b) => a + b) // or more in the spirit of `math5` export const arithmetics = $reflect!<number>({ add: (a, b) => a + b, // ... }) ``` If not I may see if I can get something like that to work (unless such an API is something that you would strongly dislike).
Owner

I am not aware of any obstruction to a format in which a macro call is made on each implementation-factory definition. I believe when we were working on dispatcher-refactor, I was extremely keen on each export from an implementation module being a plain function named by the operation name. Obviously that's something I gave up in math5 for the sake of getting the TypeScript typing to actually be effective. Given that, if there is a wrapper around each implementation-factory definition, and if we are using macros, then quite possibly the wrapper could in fact be an "ordinary" TypeScript function call (likely a generic function of some sort), with the actual macro call inside the definition of that wrapper function so that the code-extender wouldn't have to worry about writing the $reflect! call themselves (and possibly this would take care of the confusing $reflect/$reflectGen distinction, if the wrapper function were able to pick which macro to use itself).

I just didn't pursue any of these possibilities because I found the output of $reflect!() in math5 to be disappointing.

And one final comment I will make here: If we did switch to a wrapper on every call, then the verbosity difference between

export const add = independentWrapper<number>((a,b) => a+b))
export const subtract = independentWrapper<number>((a,b) => a-b)
export const sqrt = dependentWrapper<number>(['conservativeSqrt', 'complex'], dep => a = > {...code...})

vs.

export const arithmetic = implementations<number>()
   .independent({add: (a,b) => a+b})
   .independent({subtract: (a, b) => a-b})
   .dependent(['conservativeSqrt', 'complex'])
   .ship()

really seems to weigh in favor of the latter: you can reduce N repetitions of number to just 1 at the cost of adding one call to implementations() at the beginning and one call to .ship() at the end. For files with a lot of implementations, that seems like a real win. I would be happy if the .ship() were not needed, but I don't see a way to dispense with it. (There are lots of possible other names for it if we prefer, though, like .end(), .done(), .finalize(), .final(), .finish(), .wrap(), etc. etc. etc.)

I am not aware of any obstruction to a format in which a macro call is made on each implementation-factory definition. I believe when we were working on dispatcher-refactor, I was extremely keen on each export from an implementation module being a plain function named by the operation name. Obviously that's something I gave up in math5 for the sake of getting the TypeScript typing to actually be effective. Given that, if there is a wrapper around each implementation-factory definition, and if we are using macros, then quite possibly the wrapper could in fact be an "ordinary" TypeScript function call (likely a generic function of some sort), with the actual macro call inside the definition of that wrapper function so that the code-extender wouldn't have to worry about writing the $reflect! call themselves (and possibly this would take care of the confusing $reflect/$reflectGen distinction, if the wrapper function were able to pick which macro to use itself). I just didn't pursue any of these possibilities because I found the output of $reflect!() in math5 to be disappointing. And one final comment I will make here: If we did switch to a wrapper on every call, then the verbosity difference between ``` export const add = independentWrapper<number>((a,b) => a+b)) export const subtract = independentWrapper<number>((a,b) => a-b) export const sqrt = dependentWrapper<number>(['conservativeSqrt', 'complex'], dep => a = > {...code...}) ``` vs. ``` export const arithmetic = implementations<number>() .independent({add: (a,b) => a+b}) .independent({subtract: (a, b) => a-b}) .dependent(['conservativeSqrt', 'complex']) .ship() ``` really seems to weigh in favor of the latter: you can reduce N repetitions of `number` to just 1 at the cost of adding one call to `implementations()` at the beginning and one call to `.ship()` at the end. For files with a lot of implementations, that seems like a real win. I would be happy if the `.ship()` were not needed, but I don't see a way to dispense with it. (There are lots of possible other names for it if we prefer, though, like `.end()`, `.done()`, `.finalize()`, `.final()`, `.finish()`, `.wrap()`, etc. etc. etc.)
Author
Collaborator

Thanks! From your comments at glen/math5#1 (comment) I understand that the builder pattern is indeed necessary. So no need to work this idea out right now. But you're right, the builder pattern isn't that bad actually :).

I'll close this issue for now, we can always reopen if we want to explore this idea again.

Thanks! From your comments at https://code.studioinfinity.org/glen/math5/issues/1#issuecomment-1635 I understand that the builder pattern is indeed necessary. So no need to work this idea out right now. But you're right, the builder pattern isn't that bad actually :). I'll close this issue for now, we can always reopen if we want to explore this idea again.
Sign in to join this conversation.
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/typocomath#21
No description provided.