{"action":"created","issue":{"url":"https://api.github.com/repos/zenparsing/es-observable/issues/14","labels_url":"https://api.github.com/repos/zenparsing/es-observable/issues/14/labels{/name}","comments_url":"https://api.github.com/repos/zenparsing/es-observable/issues/14/comments","events_url":"https://api.github.com/repos/zenparsing/es-observable/issues/14/events","html_url":"https://github.com/zenparsing/es-observable/issues/14","id":82271479,"number":14,"title":"Should unsubscribe invoke return on the observer?","user":{"login":"zenparsing","id":5995084,"avatar_url":"https://avatars.githubusercontent.com/u/5995084?v=3","gravatar_id":"","url":"https://api.github.com/users/zenparsing","html_url":"https://github.com/zenparsing","followers_url":"https://api.github.com/users/zenparsing/followers","following_url":"https://api.github.com/users/zenparsing/following{/other_user}","gists_url":"https://api.github.com/users/zenparsing/gists{/gist_id}","starred_url":"https://api.github.com/users/zenparsing/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zenparsing/subscriptions","organizations_url":"https://api.github.com/users/zenparsing/orgs","repos_url":"https://api.github.com/users/zenparsing/repos","events_url":"https://api.github.com/users/zenparsing/events{/privacy}","received_events_url":"https://api.github.com/users/zenparsing/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":46,"created_at":"2015-05-29T05:29:03Z","updated_at":"2015-06-28T18:18:37Z","closed_at":null,"body":"In Rx, observables created with `Observable.create` have the following unsubscribe (dispose) semantics:\r\n\r\nWhen dispose is called:\r\n \r\n- Close the outer observer so that no more notifications will be sent to the inner observer.\r\n- Call the cleanup function and drop the reference to it so that it won't be called again.\r\n- The default cleanup function is a no-op.\r\n\r\nThe current spec allows the user to call `return` from the cleanup function in order to shut down the observer's generator.  If the user does not provide a cleanup function, then the default behavior will be to invoke \"return\" on the generator.\r\n\r\nThe rationale is that, when using a generator function's generator as the observer, we want to give the generator function a chance to clean up resources before we drop the reference to it.\r\n\r\nFor example:\r\n\r\n```js\r\nfunction* generatorFunction() {\r\n    let resource = getResource();\r\n    try {\r\n        while (true) resource.use(yield);\r\n    } finally { resource.release() }\r\n}\r\n\r\nlet generator = generatorFunction();\r\ngenerator.next(); // Prime the generator\r\n\r\nlet subscription = sequenceOfLines.subscribe(generator);\r\n\r\n// Unsubscribe after subscription has started\r\nPromise.resolve().then(_=> subscription.unsubscribe());\r\n\r\n// We would expect resources held by the generator to be released\r\n```\r\n\r\n@erights seemed to agree with this intuition.\r\n\r\nThe other point of view (the \"Rx\" view) is that calling \"unsubscribe\" is a way of notifying the producer that no more notifications should be sent (even \"return\").  Under this interpretation, it wouldn't make much sense to invoke \"return\", or anything else, after the consumer has explicitly given instruction not to.\r\n\r\nWhat arguments can we come up with to resolve this apparent dilemma?"},"comment":{"url":"https://api.github.com/repos/zenparsing/es-observable/issues/comments/116307055","html_url":"https://github.com/zenparsing/es-observable/issues/14#issuecomment-116307055","issue_url":"https://api.github.com/repos/zenparsing/es-observable/issues/14","id":116307055,"user":{"login":"benjamingr","id":1315533,"avatar_url":"https://avatars.githubusercontent.com/u/1315533?v=3","gravatar_id":"","url":"https://api.github.com/users/benjamingr","html_url":"https://github.com/benjamingr","followers_url":"https://api.github.com/users/benjamingr/followers","following_url":"https://api.github.com/users/benjamingr/following{/other_user}","gists_url":"https://api.github.com/users/benjamingr/gists{/gist_id}","starred_url":"https://api.github.com/users/benjamingr/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/benjamingr/subscriptions","organizations_url":"https://api.github.com/users/benjamingr/orgs","repos_url":"https://api.github.com/users/benjamingr/repos","events_url":"https://api.github.com/users/benjamingr/events{/privacy}","received_events_url":"https://api.github.com/users/benjamingr/received_events","type":"User","site_admin":false},"created_at":"2015-06-28T18:18:37Z","updated_at":"2015-06-28T18:18:37Z","body":"> It has been suggested that the only way to get the dual of Iterable is to take the dual of each individual method on the ES2015Iterable and the ES2015Generator. This is not sufficient to evaluate duality.\r\n\r\nWell, assuming you're talking about my messages - I've said in two separate messages, https://github.com/zenparsing/es-observable/issues/14#issuecomment-115986229 and https://github.com/zenparsing/es-observable/issues/14#issuecomment-116114115 that this is not actually required. We can take the iteration protocol and prove isomorphism (that is, that we've only renamed stuff - that there exists a bijection between the protocols) to another protocol and then dualize that  Any approach we can prove maps to the current protocol is fine.\r\n\r\n> I can see that the confusion stems from the fact that @headinthebox takes the method-by-method approach when explaining the duality of Iterable and Observable to C# developers.\r\n\r\nAlso in his papers - the transition is very straightforward. Duality is simply inverting the arrows, at least if we use that word the way Erik does or the way Math does - Take a function `f:: v -> u` and return a `f':: u' -> v'`. \r\n\r\n> This works because the C# Iterator explicitly encodes all of the important semantics of Iteration in its type signature. As I will demonstrate this is not the case with ES2015Generators, which rely on convention to express co-routine semantics.\r\n\r\nOnly if we claim the dual of the smaller (`.next` only) iteration protocol is the \"\"ES2015Generator\"\" protocol. It's entirely possible to start with the fuller iteration protocol (with .next .return and .throw) and dualize that into observables as done in https://github.com/zenparsing/es-observable/issues/14#issuecomment-115557631 . The type information seems to encode everything I need that way - so it doesn't appear (to me) to be a problem of the protocol since if we dualize it we get a meaningful result with an API we dislike we can improve.\r\n\r\n> Now I'm going to do exactly what @headinthebox did when he took the Observable/Iterable dual: bring exceptions into the type system. The fact that JavaScript can throw when val() is invoked needs to be captured in the type system if we are to consider it in the dual operation. This can be accomplished easily enough by adding an \"error\" to the IterationResult. If the \"error\" property is defined in the IterationResult we have an error and iteration/observation is complete.\r\n\r\nI agree with the idea but I don't agree with the construction. I don't think a `Try[IterResult]` is the same thing as the extended `IterResult` with an error property since that would mean it can indicate completion if it threw outside which is a semantic the dualization doesn't have. An object with a next, done and error property and having an object with a next and done property __or__ an exception is two different types.\r\n\r\n> Note that the IterationResult can be in one of three mutually exclusive states. The JS class is not expressive enough to capture this, so I will model it as a discriminated union. My notation was criticized for not being a real PL, so I will use F#:\r\n\r\nI'm fine with any notation I understand, thanks :)\r\n\r\nNow, this is the part that confuses me, I'm not sure why that maps all the possible states of an iteration result - but you get to that later so let's do that.\r\n\r\n> Now let's arbitrarily split the \"val\" function into three individual methods a la ES2015 Generator:... Note the following equivalency table: ... \r\n\r\nWe only mapped a very small subset of what `.next` `.return` and `.throw` can do. We have not (but can) distinguish return and next with and without a value.\r\n\r\n> Note that both of these types have all of the important semantics required for a coroutine: bidirectional communication of IterationResult.\r\n\r\nYes, I see that, they behave semantically different for this goal but they both accomplish it.\r\n\r\n> Now here comes the important point: **These two Generator definitions are only semantically equivalent if we rely on convention.**\r\n\r\nThey don't sound semantically equivalent to me at all. The semantics of the first one and the type information is different. If we _change_ the ES2015 actual protocol and _remove semantics_ from it to the point it's _no longer the ES2015 protocol_ but something else and then say we call it the ES2015 protocol and only use it as a subset - then we can show that they're equivalent.\r\n\r\n> In this case the convention is that the callee infers the IterationResult of the caller based on the method that was invoked. \r\n\r\nIt's more than that though, it's the fact we're explicitly prohibiting a lot of semantics `Try[IterResult]` captures, it also depends on how we type `next` and `return` and whether or not we capture `return` semantics with or without \"not passing an argument\". \r\n\r\n> Note that this convention is not be captured in the type system of any individual method. The notion is completely outside of the type system and is captured nowhere. Therefore we should not expect to see it in the dual.\r\n\r\nWhy is that necessary though? Why did we pick up a notation for our protocol (we got to pick the type system too) and then said our duality is not captured anywhere in it?\r\n\r\n> Here's the franken-type we end up with if we take the dual of each method on our convention-based ES2015Generator:\r\n\r\nI thought F# interfaces were defined with `type` and not the interface keyword which implements them, so I'm probably missing on newer syntax here. I'm also not aware of a `Try` and `Maybe` type in F# (because my personal lack of experience with it) So I'm going to do my best to work with the definition and let me know if I missed something:\r\n\r\n```js\r\ninterface DualOfEachMethodOnES2015Generator<T> {\r\nT next(Try[IterationResult]);\r\nT throw(Try[IterationResult]);\r\nTry[IterationResult] return(Maybe[T]); // note this was not inverted\r\n}\r\n```\r\n\r\n> By taking the dual of each method in a interface which relies on convention to convey key semantics, we end up with something less semantically expressive than the both Generator type definitions. \r\n\r\nI'm not saying that this protocol should be used for the user facing API, but that the user facing API should be based on something isomorphic to it _or that we should stop claiming duality_. I don't think it makes for a good API, but that it shows us interesting properties if we pursue duality. \r\n\r\n> In this supposed dual, the caller conveys their IterationStatus twice, both in their decision of which method to invoke, and the information in their input IterationResult.\r\n\r\nOnly if you add the `error` field like in your construction, this is something I believe that dualizing the type more accurately avoids.\r\n\r\n>  However we also see that the receiver has no way of conveying iteration status back to the caller, which reflects the semantics lost to convention. \r\n\r\nI'm not sure what you mean by this - how would the receiver convey iteration status? They have the return value - we can claim that the sent value of `.next` in the iteration protocol is `Maybe[T]` in fact (since we can call it with no arguments) and capture it like that. I'm not sure I understand what the `T` return value means here (maybe @headinthebox can shed some light).\r\n\r\n>  The type has become dysfunctional, because important semantics have been lost to convention and you can't take the dual of a convention.\r\n\r\nIf we think that then duality is not something we're doing or a goal of this proposal. The proposal can safely not claim it and diverge more freely from the semantics of a dual API. \r\n\r\n> This example clearly falsifies the notion that taking the dual of the each method on the arbitrary design of an ES2015Generator type is instructive in any way. With all due respect to dissenting parties, this approach to evaluating duality already stood up to significant scrutiny at the last TC-39 meeting.\r\n\r\nHonestly I don't see how the scrutiny of TC-39 members is relevant here. I have mad respect to a lot of TC members (you included) but the fact people scrutinised the design doesn't mean we should not address issues with it as they arise (as in this case).\r\n\r\n> I'm confident that it will continue to stand up to more intense scrutiny, and I'm capturing my argument here in the event that this happens. I believe it is possible to compartmentalize this argument, move on to discussing whether return(undefined) is sufficiently expressive to describe terminate semantics. I will do this in a future post.\r\n\r\nYes, discussing the expressiveness of `return` would be nice :)"}}