-
Notifications
You must be signed in to change notification settings - Fork 30
-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Split overloaded 'use' method into multiple methods. #102
Comments
so "adopt" is like "use and defer"? |
|
hmm - so is |
Not quite: I had the same question on Wednesday, and if I understood @rbuckton correctly, it's intended for cases like this let resource = stack.adopt(getResource(), handleResourceDisposal); Rewriting that to use let resource = getResource();
stack.defer(() => handleResourceDisposal(resource)); which has the problem that there is now a place you can insert code between where the resource is acquired and the place where it is registered for disposal (i.e. between the first and the second lines). Using |
|
The argument to |
ahhh i see, because it allows "defer" but with eager evaluation of the expression. thanks. |
Yeah. Also, to be clear, the function passed to |
|
Ok, so why isn’t defer and adopt the same method, with the value as a second optional argument? |
For several reasons:
|
I don't agree with the first point at all - there's tons of APIs, array or not, that aren't It seems silly to me to have two separate methods (even if they had names that actually seemed related, and that actually conveyed what they do, which "defer" kind of does but "adopt" doesn't at all) when the only difference is the |
It seemed silly to me to have three, which is why they were overloaded. Regardless, I'm not in favor of ["a", "b", "c"].reduce((memo, value) => ¦ As you type and reach the ["a", "b", "c"].reduce((memo, value) => ¦, new Map())
Aside from |
If you're making a consistency argument, then the |
I think it's important to consider the effects on tooling, given that many people are using tools. We absolutely should not assume everyone is using a tool, but we also shouldn't give considerations about tooling literally zero weight. |
that's fair that we should give it nonzero weight. However, I think defer/adopt are confusing names, and it took a lot of questioning before i realized that they're actually the same method with an optional value argument squished into the front. I think it would be very, very confusing unless we combine them into one |
Honestly they don't really seem like the same method to me? One is "I wish to use this value, and here's the function used for disposing of the value's resources [because it doesn't implement As to the naming question, (That said, I don't feel that strongly that those two operations need to be separate methods; I see the type inference concern but don't know if it warrants having an extra method. I am fine either way.) |
That has consistently been a pain, and a proposal to do just that was created and put on the agenda at one point, though it was withdrawn and wasn't presented.
I'm more than happy to consider alternatives. I chose
But what would you call that? I personally like the idea that the subject of the operation is the first argument and the return value, hence why the signature for
I still prefer the overloaded class DisposableStack {
use<T extends Disposable | null | undefined>(value: T): T;
use<T>(value: T, onDispose: (value: T) => void): T;
use(onDispose: () => void): void;
} But if we were to break this into two methods instead of three, I'd prefer this breakdown: class DisposableStack {
use<T extends Disposable | null | undefined>(value: T): T;
use<T>(value: T, onDispose: (value: T) => void): T;
defer(onDispose: () => void): void;
} Where the |
There's two bad things about that overload (to my eyes):
These are both bad, but the first one is more serious. The three-method form eliminates both of these problems. Having Having Having I would be happiest if we eliminate both of the problems with the originally proposed overload. I can live with only eliminating the first problem. I would be unhappy if we don't eliminate either problem. |
If we are to break this down, then my preference would still be for the three-method form to better support type inference. @ljharb:
If you have suggestions or alternatives, I'd be glad to hear them. |
I haven't thought all the way through this yet, but I'm very interested in how incremental migrations would work here. Specifically, what does it look like when a pre-disposable API updates to become an actual disposable? Relatedly, what might it look like if anybody is polyfilling or ponyfilling disposable or DisposableStack? For the former question, I imagine you'd start with code that looks like Aside about polyfills/ponyfills (tl;dr: I think it's not an issue)I have a harder time reasoning about polyfills/ponyfills, since I'm not quite sure what they might look like. Presumably a polyfill would actually define |
Aside from just adopting non-disposables, using stack = new DisposableStack();
const res = stack.adopt(getResource(), res => {
disposeCounter++; // some extra work we want to do.
res[Symbol.dispose]();
}); As such, I wouldn't want |
I'm strongly opposed to doing this kind of switching. It's harder to read, and it makes it a lot more likely that adding the Symbol.dispose will be a breaking change (in particular if it's not just an alias for the thing the user was doing previously). |
Actually, my plan for TypeScript is to implement with nested |
Regarding naming,
|
The implementation of class DisposableStack {
// ...
adopt(resource, callback) {
this.defer(() => callback(resource));
return resource;
}
} So your snippet doesn't do the same thing as |
I'm not sure why it needs to be chained - |
Because then you're mentioning let resource = stack.adopt(getResource(), x => x.close()); makes acquiring the resource and registering it for cleanup a single operation, which is important. Without let resource = getResource();
stack.defer(() => callback(resource)); which (as mentioned above) has the problem that there is now a place you can insert code between where the resource is acquired and the place where it is registered for disposal (i.e. between the first and the second lines). |
I'm open to suggestions for an alternative name, but I strongly disagree with having the value as the 2nd argument. It's not that it's optional, per se, but that it passes through the argument (including |
That you can insert code doesn't mean you will; this seems like a very unnecessary guardrail/convenience to me compared to the cost of an entirely distinct API method. |
I'm not sure I understand what you mean by "can insert code"? The point of |
That you can do it in two lines seems perfectly acceptable to me. |
I strongly disagree. There are other APIs for which "just do it in two steps" is totally fine, but not this one. |
It isn't to me. When this ships, there will be a long tail of APIs between the DOM, NodeJS, Electron, third-party packages, etc. that will not yet support |
I'm going to go ahead and merge #103 and we can discuss this again in plenary. |
I really hope we won't repeat |
Per the September 2022 plenary discussion, the
DisposableStack.prototype.use
method should not be overloaded and instead split into multiple methods:use(disposable)
to add a disposable resource to be disposed when the stack is disposed.adopt(value, onDispose)
to add a non-disposable resource (value
), such thatonDispose(value)
is called when the stack is disposed.defer(onDispose)
to add a callback to execute when the stack is disposed.The text was updated successfully, but these errors were encountered: