math5
vs dispatch_refactor
approach
#1
Loading…
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Thanks for picking this up again @glen! The
math5
approach is interesting but like you describe it is really a trade-off with pros and cons.Some first thoughts on the
math5
vsdispatch_refactor
approach:Do I understand it correctly that the builder pattern is a requirement to make this work? Or would it be possible too to change the API of
math5
into a simpler factory function likefactoryIndependent<CommonSignature<number>>({ add: ... })
? The builder API is indeed more more complex which is a pity.The types generated in the .d.ts files in
math5
are beautiful! Really straightforward without any abstraction. I'm not sure though if this really improves on what we have indispatch_refactor
, since we have a (basically) working macro functionparseReflectedType
there already so all seems fine?Thinking through a separate build step: I'm not sure how we can relate the types generated in the .d.ts files to the right function implementation via a separate build step. We need to turn these types into something that is runtime available but also map them somehow to the right function. Using a macro solves this by injecting new code at the right place at compile time. Do you see options for such a separate build step?
You're right that there is less duplication in the typing, thanks to handling multiple functions as a group. That is nice indeed. To me, the amount of typing needed in
dispatch_refactor
is OK already, so this is not a big thing to me, though I understand that you quite like that :).The thing that I myself dislike most about
dispatch_refactor
is the need for the$reflect
at the bottom. Do you think we can get rid of that by moving it behind the scenes, inside.ship()
for example? Still, we could also put a$reflect
wrapper around every function which makes things more direct but if I remember well you didn't like that option?Let's think through how these two approaches will work from an end user point of view: what is needed to understand and write to import a custom function in mathjs. Will it be possible for a user to use this type reflection macro too? What is needed to import a plain JS function?
True, but to me by far the most powerful argument is that math5 actually found typing violations in the implementation code that dispatcher_refactor did not. That suggests that in the dispatcher_refactor version, we are going to lot of trouble to get TypeScript to even run on the implementation definitions, but then we are not getting the value that TypeScript has to offer: stringent type checking that helps uncover errors and potential errors early. In other words, why bother using TypeScript at all if it's not really going to be actively type-checking our code? This is the real reason that I do not feel we should use dispatcher_refactor without changes; either something like math5, or other adjustments (not sure what they would be, but I am always open to tinker) so that TypeScript finds the typing errors (that both we and TypeScript missed in dispatcher_refactor when we were last working on it!).
That said, you raised six specific points, which I will respond to in six comments rather than one über-long one.
The builder pattern is one way to get narrow typing, that I landed on after a StackOverflow exchange with jcalz, a well known TypeScript typing system guru. What he is pretty sure won't work is something like:
or in words, just one call to an exporter function with clauses for each of the things you want to export even when they have different dependencies and hence different types to check. Basically jcalz says that the current TypeScript typechecker can't "distribute" typechecking over the properties of an object in the way we would want, to combine each call to .dependent or .independent in the builder into one big call.
I expect that a scheme in which there is one template function call (and perhaps therefore one export) per implementation could also get the typing to be nice and narrow. I think that is basically saying that there would be a way to tweak the dispatcher-refactor less but beef up its typing. I didn't end up there because I was also trying to make the code more dry: not mention the name of each operation at least twice, not reiterate what types we were defining implementations for over and over in each implementation module, and be able to specify the actual factory function as just
dep => (a,b) => code
which is really simple-looking and clean. But we could look for something closer to dispatcher-refactor that gets the typing really working.I actually haven't gone back and checked, but my recollection is that parseReflectedType provided somewhat less "grounded" types in that some of our type operations were still embedded in the resulting expressions and we would have to recalculate what they were at runtime. But maybe I am misremembering. In any case, we are both in agreement that in math5, the .d.ts types are pretty much the best we could hope for: They do in places leave some
RealType<T>
calls in place, for example, but that seems unavoidable as in those places we don't necessarily knowwhat type
T
is, so there is no way to know its RealType. Otherwise, it seems to trace everything back to explicit types, with no extra work on our part. I am not sure that parseReflectedType did quite as well in dispatcher-refactor, and it certainly does much worse in math5.In any case, the main improvement in math5 is that TypeScript gets to these nice clean types in the .d.ts straight out of the box -- no dependencies, no macros, no remembering "When do I call $reflect!() and when do I call $reflectGen!()?" which frankly is one of the things I found most confusing when trying to look back at dispatcher-refactor and get going with it again.
The macros are just automated code generation that happens to be bundled with a call to a (patched!) tsc. All I would be proposing is a handwritten code-generation step. It could either modify files like arithmetic.js that had been created in the
tsc
step, or it could make companion files like arithmetic.types.js that we would import when assembling the dispatcher. The latter is slightly more straightforward because it would just be a 1-1 transformation of any blah.d.ts files we find into blah.types.js files, but we could totally do either -- and they will be quite easy, just some light parsing of the .d.ts files, since the types are all just there in pretty much the format we want, we just need to turn the strings into a data structure to be used by the dispatcher-assembling function(s). And that is no more work than in dispatcher-refactor, because we had to write the parseReflectedType mechanism anyway to deal with what the macros were putting out (and looking back at the code for it, that is definitely not trivial -- no reason to expect the second build step will be any worse, codewise).Ok I didn't really need to respond to this one, we are in mutual understanding on this. Just putting this here so it doesn't seem like I missed one ;-)
I am a little confused by this question, so let me just say:
(a) There is no need for the $reflect!() calls in math5 at all; I just added them to see what types they would produce now that TypeScript was itself producing essentially perfect types, in case that would automatically take care of the "second build step" without our having to explicitly write code for it. But in fact, in math5 $reflect!() produced much less good types; and writing that second build step would be no more work than the parsing we would have to do of $reflect!() output anyway. So in the end I consider $reflect!() in math5 a failed experiment, and would just take it out in favor of writing a simple .d.ts extractor. I only left it there in the repository at the moment so that you could see its operation and results.
(b) In dispatcher-refactor, it appears we need the $reflect!() calls somewhere because the types TypeScript is assigning in .d.ts do not seem adequate (which as I pointed out in my first response, now seems to me to be a very significant shortcoming of dispatcher-refactor as it currently stands, basically implying we are wasting our time using TypeScript there). As a stylistic matter, I somewhat prefer a single $reflect!() call at the end to avoid verboseness, and you prefer multiple $reflect!() calls to avoid repeating the list of implementations provided -- but I do see your point there, that is definitely non-DRY. So the macro call is a pain either way; if we end up with a solution that uses macro calls, I could go either way with where to place them.
My overriding desire is to try to get as close to "just state the dependencies and the code for each implementation factory" as possible. Ideally we would just minimize all the other noise/repetition. I think it is what we strove for in dispatcher-refactor, I just now feel we circumvented TypeScript too much to make it worthwhile, even though at the same time we had to repeat types over and over again! The builder pattern appears to fix those things, at the admitted cost of introducing its own sort of boilerplate verbosity unfortunately.
Well, as I said, if we go with a second build step, then we don't need macros in math5. If someone is cloning a math5-based mathjs and modifying it, the second build step will be in place. If someone is using mathjs as a package and wants to extend it by adding another module of TypeScript functions like in the core, we will have to make sure the package also exports the builder and they will have to call it in their build process. And finally, to add some more operations written in plain JavaScript, they will have to call one of the dispatcher-assembly functions with their factory function and the (likely hand-written) data structure describing its dependencies and signature -- that last bit seems exactly the same as in the the dispatcher-refactor case.
Correspondingly, in the dispatcher-refactor case, cloning&modifying mathjs should be fine, the new code just needs to follow the pattern of $reflect! calls of the old. Using mathjs as a package and extending it by new TypeScript modules would entail adding ts-macros to their build dependencies, and importing our $reflect!() (and $reflectGen!() if needed) macros, which would presumably be exported somewhere from the package, and calling them appropriately.
So I don't really see that there's too much difference between dispatcher-refactor and math5 on this particular score...
You didn't comment on the change from (for example)
divideReal
defined as an "alias" of divide but then treated like its own operation in dispatcher-refactor, vs. in math5, only havingdivide
and just explicitly specifying which signature it is being defined for in each case. Personally looking back, I found the former tricky to follow/read, and the latter clearer. I would suggest that even if we end up in a place closer to dispatcher-refactor, that we revisit (and try to eliminate) this "alias" system. I think it makes it more difficult for someone new to get into the code, so to speak.Agree, you're right, that is a strong argument in favor of
math5
for me too.(1, 7) Clear, thanks. In short: the builder pattern is a neccesity. That can be ok with me. How complex an API is I think is mostly how many concepts the user needs to userstand, needing to call a
.ship()
or.build()
is not perse more complex (and a chained API is easier discoverable if you use an IDE with intellisense, so it explains itself). Not needing special constructs like "alias" really makes the API simpler, thats indeed great.(2, 3, 5, 6) Thanks, I now better understand how
math5
works and that it does not neccesary need a TypeScript plugin and$reflect
macro at all. That can be awesome indeed, it was hard to find thists-macros
plugin that outputs all the type info we need and it feels a bit tricky. Ok let's see if we can work out a separate build step inmath5
to get the types available at runtime. It can indeed work out very neatly!I'm having quite a good feeling about where
math5
is heading.Thoughts about a build step to generate types: in the current
mathjs
there is also a build step in place which generates files. In./src/entry/
there will appear files named*.generated.js
. It is handy to put the generated files inside./src
to make it work with the normal build tools, and it is handy to give them a distinctive extension like.generated.js
to exclude them from commiting them to the source code.@glen I've pushed a branch
feat/reflect-types
where I experimented with a script./tools/reflectTypes.mjs
which injects type information from the .d.ts files into the corresponding.js
files in the/build
folder. If you now runpnpm go
, it will replace all occurrences of.ship()
with.ship({ reflectedType5: '...' })
. In the .js files in/build
you will now see occurrences ofreflectedType5: ...
.Note: you will only see
reflectedType5
occurrences of the number files printed in the terminal and not those of the Complex files. That is because these are defined as functions that are not (yet) executed. You can look up the generated files in the/build
folder yourself though to see them.Thoughts/remarks:
$reflect!(...)
cases from the code but I think we can do that.import("../Complex/type").Complex<number>
. I haven't thought through how to solve that.@josdejong Thank you! It is nice to see a working type injector. The windows version of the build script didn't work on linux, so I tried a third option that I hope will work on both, and I removed the ts-macros since it seems, at least in this prototype, we won't be using it and I thought it would be good to simplify as much as possible. I took the liberty of rebasing your branch on these changes, and it still seems to work fine.
Since ultimately we will need some data structure for the types, rather than just a string (your point #2), I am going to make a further injection experiment branched from yours (I will call it feat/parse-reflection) in which I will use the TypeScript parser itself to parse the .d.ts files, along the lines of this StackOverflow
P.S. Let me know if you get email notification of these comments; I think I have the code.studioinfinity.org mailer working again.
Yes I'm getting email notifications again!
Good idea to try use the TypeScript parser itself to parse the .d.ts files, that would be nice. That will save us quite some work, then we only have to transform the TypeScript AST in something more tailored to what we need in the Dispatcher.
All right, I have pushed a very preliminary parsed type injector on branch feat/parse-reflection. It is still very buggy/incomplete, but the basic structure of using the typescript parser and stripping the AST down to some simple-object specification of the type is there. I am not at all wedded to any of the particular representation choices, this is just a proof of concept. Anyhow, in the output of
pnpm go
(at the end of its reams of debugging text) you can see the structures that actually come out of the generated JavaScript and compare the reflectedType5 string property with the reflectedType6 structured property to see what you think. I will try to complete/polish it up a bit over the next week or so, and any time in the meantime if you have feedback/suggestions I am all ears.OK, now the parsed type injector covers all of the factories/implementations exported by any module in the current math5 (this is on the feat/parse-reflection branch). There are still some unhandled AST node types, but they are not used in the export of any particular factories/implementations.
So I thought of this as a good time to take stock. Please let me know your thoughts on:
Complex<T>
. But Complex is just a type in typescript; there is no corresponding value in the generated JavaScript. Hence there is no.ship()
call that ships it, nor even a valueComplex
to attach a property to. But we could export the type information in a value with a conventional name, likeComplex$type
(from which we could learn that a Complex has two fields, re and im, among other things). This is actually more important for us for things like at runtime looking up what the RealType or the ZeroType of a given type is.When we've sort of converged on these points, I think we can wrap up these branches into a PR and move on to implementing a Dispatcher :-)
That sounds promising! I've checked out
feat/parse-reflection
but I see the logging like:For some reason
ts2json(defFile)
always returnsundefined
on my machine 🤔. I'll try with a linux terminal (running Windows + nodejs 20 or 22 normally).O wow, works in Ubuntu via WSL. That is interesting.
This
math5
approach looks more and more promising Glen! Awesome. The separate build step is working out quite nicely.I love the
_reflectedType6
JSON structure! This should be easy to use in the Dispatcher.What is most important to me is that (a) it is easy and reliable to access the correct
reflectedType6
from anything that is imported into the Dispatcher, also when people do creative things with import, export, putting stuff inside objects, returning stuff from functions etc. And (b) that the way we inject the types cannot interfere with other users stuff. My first thought was that attaching the type information to.ship()
would be the most straightforward and reliable way to do that, but I'm totally fine with other solutions if that works fine. One thing that feels inconsistent to me is thatmixed._reflectedType6
is attached to the functionmixed
but I think it should be attached to the return value of the function instead. That return value is not explicitely defined as a variable though, so that is not possible. What if people go wild and do dynamic things like the following example? I have the feeling that attaching the type information to.ship()
is more robust, since that is guarenteed to be attached to the right chain of implementations.About
Complex
type vs actual implementation: We will need define the name and implementation of data types likeComplex
in the Dispatcher somehow, right? Something likeimplementations().defineType('Complex', Complex)
orimplementations().defineTypes({ 'Complex': Complex })
?Ideally we ship as little data as possible I guess, we should only use the information we need. I think we take the easy way for now, and optimize this once we have a basic POC up and running, so we have a better understanding of it?
I like the clear structure! We can see if there are ways to make the JSON structure more predictable, like parameters always being an object (right now it can be
"number"
but also{"_typeParameter": "T"}
for example). I think we can better see any opportunities/inconsistencies by defining TypeScript types for the generated JSON structure holding the reflected types.In the end we have to check the impact of all these data structures on the bundle size, since JSON is not very efficient. But there are ways to solve that, I think we can best look into that in a later stage.
Some thoughts on naming: is there a special reason to start all keys with an underscore, like "_parameters" and "_returns"? I associate that with unused parameters or private properties. Also, shall we rename
.ship()
to.done()
or.build()
?I have to figure out why this isn't working on Windows. Any ideas on where to look? Does
undici-types
need to be registered somehow likenpx ts-patch install
?What are the
undici-types
needed for?Wow, a lot of great discussion. As before, I will mostly reply to each item in a separate post rather than create a mega one.
Glad!
(a) "Easy and reliable to access types for anything that is imported into dispatcher" -- always having Dispatcher look on a specific property like
_reflected_type
or whatever seems easy and reliable. Then of course we would have to make sure it is always there. If we attach the information via .ship(), that creates a requirement to always use ship(). But the (only) point ofimplemenations()... .ship()
is to look up standard signatures and thereby free you up from having to reiterate types in your implementations/dependencies. So I had imagined that a one-off function that really just had a single signature, like for argument's sake factorial that can take a number or bigint and always returns a bigint (since it grows so fast), we would like to be able to justand then that could be loaded directly into the Dispatcher (and we wouldn't even need to put factorial in CommonSignature, etc. So the point is that for one-off functions, it's actually more DRY not to use ship). For that to work, the types of factorial have to be reflected somewhere accessible., but there is no ship() call here.
So the ts2json.mjs tool I've been developing is intended to just do type reflection of every symbol that's exported, that way it's always there for the Dispatcher if it needs it.
(b) "type injection can't interfere" -- I agree that this is a strength of injecting into ship(), and might even be an argument to absolutely require ship() to work with dispatcher, if that doesn't seem too cumbersome to require across the board. With the ts2json.mjs method, we have to pick a property that is "unlikely" that others will use, and then let people know not to use it -- or, actually, perhaps we could create a Symbol and put the reflected types on the property named by that Symbol, to prevent any chance of conflict. Do you think that would work?
On where to attach type information to mixed() -- it depends on what is going to be fed into the dispatcher to specify those implementations. The Dispatcher has to know that those implementations are generic. In TypeScript, you have to call mixed with a specific type for T, say
number
to getComplex<number>
operations, but then the returned item is no longer generic -- it's specialized to provideComplex<number>
implementations for add/divide. I recognize that from the point of view of the object JavaScript code, there is no distinction (it returns precisely the same JavaScript regardless of what type mixed() is called with), but conversely at the JavaScript layer, there are no types either.The upshot of these thoughts is that it made more conceptual sense to me to pass the mixed() function itself to the dispatcher, and then when it needs to instantiate Gaussian integers
Complex<bigint>
, say, it will call mixed(). From that point of view, the annotation should be on the mixed() function, which aligns nicely with the fact that these are generic implementations, and that mixed() function is a generic function. I do realize that there is an alternate world in which something calls mixed(), and even though that result is now in some sense concrete rather than generic, since there is no difference at the JavaScript level, it just marks that result with generic type descriptions, and then Dispatcher can use the results when it needs to instantiate any particularComplex<SomeType>
. But the other obstacle to this alternate view is what would that thing be that calls mixed()? Why wouldn't that be the Dispatcher? The convenience of just importing a bunch of implementation modules and then just dumping whatever they export into the Dispatcher seems really attractive, in which case mixed would get sent to the Dispatcher as a function, and it would be responsible for calling mixed to get the implementations (when it saw fit), in which case the type annotation should be on mixed, not on what it returns, so that the Dispatcher knows what to do with this 'mixed' thing it got. If there's no annotation at the level of mixed, it wouldn't be able to know what to do with it...Your example of a conditional export of two different groups of implementations is an interesting and challenging one. Let's say something like this is useful and should be supported. We should recognize that with either the injecting into ship() or injecting as a property on exported symbols, these latest two approaches only have access to the type information that is recorded in the .d.ts files (that is one argument in favor of going back to ts-macros -- although in the experiment they were producing less detailed type information, at least there we have the potential to enhance them and get access to finer-grained type information, like the separate type of each branch of an if-statement, that is lost at the top level .d.ts files).
OK, so supposing we are pursuing a .d.ts-based solution. Then all we get in the .d.ts file is the type of the function
conditional
. It doesn't record separate types for inner blocks of the code forconditional
(although of course that type information is generated during the compilation, which is why something like ts-macros might be necessary if we want to try to capture that information). And what is the type ofconditional
? It is something likeboolean => ({add: NumberSig, ...} | {add: GenericSig, ...})
So in the ship()-injection solution, the injector will have to look at the code for
conditional
, see that there are multiple instances of ship() inside its code, and then guess that that they correspond to the different branches of the union return type of the type ofconditional
, and hope that they are listed in the same order as the different ship() calls inconditional
, and then inject them. If it gets all these guesses right, then whatever callsconditional()
will have an easy time: it will get an object with appropriate type reflection.In the add-a-property-to-exports solution, the conditional function itself will be labeled with the above type (but in JSON form). The code for Dispatcher to know when to even call this function will be a bit confusing -- if it wants an implementation in either listed collection, it should just go ahead and try calling it, and hope it get back what it needs? Will it be able to check? How will it know what arguments to call
conditional
with, and where will it get them?So I do kind of agree that the only path that has much of a chance of being able to do something with an implementation supplier like
conditional
in a .d.ts-only scheme is something like ship()-injection, but even with such a plan it is not clear to me (I) who will be responsible for calling conditional() and where it will get the arguments from; (II) what the use-case for this facility would be; (III) that we could reliably do the chopping of the return type into separate type specifications and injection of each piece into the proper ship() call (or that we want to try to code this). So to me it's unclear that we want to base our decision on thisconditional()
example; but I trust your experience and intuition, so if you think we need to plan to support something likeconditional()
then we should pursue ship()-injection. Note we can use the type objects generated by ts2json.mjs in a ship()-injection construction; we just need to analyze the .js files as well as the .d.ts files (I had been hoping to eliminate all analysis of the .js output of the first (tsc) build step since I fear just naive regexp-matching ultimately won't be enough, and it would be nice not to have to pull in a js parser as well, although they certainly exist.)Finally: "We will need define the name and implementation of data types like Complex in the Dispatcher somehow, right?"
Yes, the concept in the current prototype is that there are objects Complex_type and number_type exported by the respective modules, and these exports are dumped into Dispatcher, and as it is combing through everything it got and assembling it into a collection of actually callable functions, it recognizes these objects as adding data types rather than as supplying implementations, either because of the _type suffix of their names or because of the collection of properties that they have (agnostic at the moment as to which). But anyhow, to me that's why it's important that these type-designation objects also have reflected type information. In the "attach a property" scheme they will; in the ship() scheme I think we will need to add a .addType() method alongside .independent() and .dependent() that can come before ship() so that they are included in the type information that gets generated.
OK, I think that's everything raised in your item (2) -- it's kind of big bundle!
Right, although the "easy way" is different for add-a-property and ship()-injection; in the former, it is easiest just to add the property to all exports, whereas in ship()-injection the only thing one can do is analyze the types that have been found to determine which correspond to ship() calls and then find the proper ship() calls in the .js code to insert them into. (I.e., I feel ship()-injection is more work at the second-build-step level for sure.)
Sure, we could write
{_intrinsic: 'number'}
if you prefer that to just'number'
. Let me know if you want me to make that change.Yes, I suppose that the Dispatcher will be written in TypeScript (and just use vague types for the implementations) and will need to do that.
You don't know of any other specification of the TypeScript type system in a JSON-like notation out there?
Well, I do feel like the type information is sort of private to Dispatcher, not really intended for general purpose use. But more importantly, currently the type of a plain object like
{x: 1.0, y: -2.5, z: 0.1}
is encoded as{x: 'number', y: 'number', z: 'number'}
so the underscores are to distinguish the type-specification metaproperties from just regular properties that are part of a particular type, not to mention the names of operations that the Dispatcher will collect. (And we will have to say that to use Dispatcher, your types can't have properties that start with_
, nor can you use operation names that start with_
, or we will have to reserve some other special spelling for our type-specification metaproperties, or, like I suggested for the property on which to put the reflected type itself, we could try switching to a collection of Symbols that can't conflict with property names or operation names or even other symbols (because no two invocations can ever make the same Symbol). I think that would work -- would you prefer switching to that?Yeah, I agree
ship()
isn't a super name. What would make sense to me is to rename bothimplementations
andship
to things that conceptually "match" like braces orif
andendif
for languages that go for that sort of thing. So maybe, since it's all for the dispatcher anyway,dispatcherGroup()
andendGroup()
? Or if you preferimplementationGroup()
andendGroup()
? orstartImplementations()
andendImplementations()
? Really anything that reads like a matching pair would be fine with me...Not really. I can access Windows to try to debug if it becomes really critical, but I have never even cloned a git repo on a Windows machine, so I would literally be starting from zero.
I am only doing "npm install" when I clone the math5 repo, so I don't think anything beyond that should be needed.
Well, the answer is either "I don't know" or "they shouldn't be", but the fact is without them added, the whole shebang does not work. I am certainly not using them directly. I only added them because of errors/failures I was getting, and then because of finding issues like https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/67406 when I was trying to debug the (very mysterious) situation. I think it is an unresolved problem with
@types/node
, unless I am misunderstanding. In any case, since this was a POC, I wasn't too concerned, I just wanted everything to run.OK, I think I've covered everything you've raised. When you've had a chance to digest it, let me know your thoughts on what immediate direction to go and then I/we can try to bundle up these branches into a proper PR into main() (with only one set of reflected type information, etc.).
Ha ha, you managed to produce a "mega post" for point 2 alone 😉
👍
(b) In the past, I have avoided using Symbol for such cases and used a "unique enough" property because Symbols where harder to debug and cannot be serialized. I have a slight preference for using a "unique enough" property name over Symbols to keep things simple, even with the theoretic possibility of a naming conflict. If you have a strong preference though I'm totally fine with trying out Symbol to see if it works out nicely.
When zooming out: the automatic type reflection will have certain limitations, we will have to document that. Like: maybe we should not try to support dynamic conditionals if we attach the type information as property to the root objects/functions in all files.
I think we're on the same page in wanting a solution with as little verbosity for the user as possible, and not needing
.ship()
in may cases sounds very good :).I propose the following: let's not try to think through all and every (theoretic) edge cases upfront. Let's go for attaching as a property (and not injecting in
.ship(...)
) for now, built a first version of the Dispatcher, and see how it works out. It probably works out nicely, but if not, it will not be that much work to rewrite it to a different solution like injecting inside.ship(...)
. But at that point we will have a more clear view on the actual limitations and edge cases and can easier see the pros/cons. Ok?👍
Let's not change the JSON structure upfront, but see how it works out when we define the types as JSON and implement the Dispatcher, ok? Only make changes if we see concrete room for improvement.
I don't know of any existing JSON-like notations, and also, I expect we want to write it in such a way that it is aligned to exactly what the Dispatcher needs. Let's just roll our own.
Thanks, I understand the reason for using _ to distinguish the type information structure itself from the data. I expect there is no real need for it when the data structure of the type information is explicitely defined, but it may be of help when reading and debugging it ourselves. Like with (4) I propose we keep it as it is for now (with underscores). And at some point I may try out refactoring it to see how an alterative without underscores works out.
good to know for sure that there are no special installation steps needed. I'll first do more debugging on my own, and try search on internet for this kind of issue. It is a critical thing in the end to have this work on any machine (Windows/Linux/Mac). But I assume it is solvable.
got it, thanks. Maybe the Windows issue (6) has to do with this too, that's why I asked.
So, in short: let's keep the POC as it is right now, built a first version of the dispatcher, and after that see if there is need to finetune the API's.
(5) O: and about the naming of the methods: I don't think it's really needed to try have a matching "start" and "end" name since the builder pattern is quite a common and well known one. Some more ideas:
dispatch() ... .done()
define() ... .done()
implementations() ... .done()
create() ... .done()
build() ... .done()
Ok the Windows issue is solved 😁, I've pushed a commit in
main
:2a8823c5f7
OK, I think we're set to write a dispatcher. Should this issue be closed?
Yes let's close this issue. I'm looking forward to the next step :)