{"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":47,"created_at":"2015-05-29T05:29:03Z","updated_at":"2015-06-29T01:03:58Z","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/116365675","html_url":"https://github.com/zenparsing/es-observable/issues/14#issuecomment-116365675","issue_url":"https://api.github.com/repos/zenparsing/es-observable/issues/14","id":116365675,"user":{"login":"domenic","id":617481,"avatar_url":"https://avatars.githubusercontent.com/u/617481?v=3","gravatar_id":"","url":"https://api.github.com/users/domenic","html_url":"https://github.com/domenic","followers_url":"https://api.github.com/users/domenic/followers","following_url":"https://api.github.com/users/domenic/following{/other_user}","gists_url":"https://api.github.com/users/domenic/gists{/gist_id}","starred_url":"https://api.github.com/users/domenic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/domenic/subscriptions","organizations_url":"https://api.github.com/users/domenic/orgs","repos_url":"https://api.github.com/users/domenic/repos","events_url":"https://api.github.com/users/domenic/events{/privacy}","received_events_url":"https://api.github.com/users/domenic/received_events","type":"User","site_admin":false},"created_at":"2015-06-29T01:03:58Z","updated_at":"2015-06-29T01:03:58Z","body":"> 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\nThis is not at all clear to me, as @benjamingr's arguments always manage to express during times when I'm on planes :). I am a TC member as well, and apologize for not being there at the meeting to voice my doubts, but it's been good to get some offline time to express them as well. We can't just steamroll over them by saying they're \"clearly falsified.\" As @benjamingr says,\r\n\r\n> I'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.\r\n> ...\r\n> If 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---\r\n\r\n@benjaminr, I am curious why you do not reverse return as well. I also disagree with your assertion that return() is different from return(undefined); the fact that these are distinguishable is, in my mind, an edge-case artifact of the language and not something you'd want to encode into anything type-system-esque. There's also the issue that errors are not of type T. (And that generators aren't typed anyway.)\r\n\r\nAs such I think a proper dual of an ES2015 generator would be\r\n\r\n```\r\ninterface ES2015Generator {\r\n  Try[IterationResult] next(any);\r\n  Try[IterationResult] throw(any);\r\n  Try[IterationResult] return(any);\r\n}\r\n\r\ninterface DualOfES2015Generator {\r\n  any next(Try[IterationResult]);\r\n  any throw(Try[IterationResult]);\r\n  any return(Try[IterationResult]);\r\n}\r\n```\r\n\r\n---\r\n\r\nNow for my take on it, while at my computer and not on mobile.\r\n\r\nI think perhaps the duality that @jhusain is trying to express is not in fact one that has anything to do with generators, besides punning on their names. Here is one path that seems plausible to me.\r\n\r\nFirst, ignore generators. Focus only on unidirectional iterators, without the ability for consumers to send values backward. In that case the interface is\r\n\r\n```\r\ninterface ES2015Iterator {\r\n  Try[IterationResult] next(); // note: no parameter\r\n}\r\n```\r\n\r\nThe dual is then of course\r\n\r\n```\r\ninterface DualOfES2015Iterator {\r\n  void next(Try[IterationResult]);\r\n  // maybe the return type is actually Try[void]?\r\n}\r\n```\r\n\r\nOur goal, because we like C# and think that its observables provide a productive way of programming, is to somehow justify the interface\r\n\r\n```\r\ninterface Observable {\r\n  void onValue(any);\r\n  void onComplete(any);\r\n  void onError(any);\r\n  // maybe these should return Try[void]?\r\n}\r\n```\r\n\r\nwith an appeal to \"duality\".\r\n\r\nTo do this, it suffices to produce a bijection between Observable and DualOfES2015Iterator. I believe the intention is that you biject via\r\n\r\n- Exceptions `e` sent to `iterator.next` (via the `Try[]` type; we can't actually express this in JS directly, since JS uses exceptions instead of functional-style `Try` or `Either`) map to calls to `observable.onError(e)`\r\n- IterationResults in the form `{ value, done: true }` sent `iterator.next` map to calls to `observable.onComplete(value)`\r\n- IterationResults in the form `{ value, done: false }` sent to `iterator.next` map to calls to `observable.onValue(value)`.\r\n\r\nThe final step, which I think introduces most of the confusion, is to simply pun on the names that generators use, and rename `onValue` to `next`, `onComplete` to `return`, and `onError` to `throw`. These names in the observable context have nothing to do with generators. (And indeed, generators and observables are not a great match, as can be seen from the awkward example in the README.md, with its \"priming\".) There's some appealing \"hey look they match\"---but it's not a sign of anything deeper. No duality with generators. It's entirely about iterators.\r\n\r\nThat's my take. What it says about abrupt termination is then pretty clear: there is no capability for that built in to the system. Since iterators are a completely unidirectional protocol, with no way to communicate cancelation back to the source, then of course their dual observables will have the same limitation.\r\n\r\nSo that's the starting point. The question then, for observable fans, is how do you tack on cancelation semantics into something which does not have them---and which certainly does not arise naturally out of the duality with iterators, since iterators don't have such semantics either.\r\n\r\nMy guidance would be to do one of two things:\r\n\r\n1. Embrace cancelation tokens. They have issues, but are very very simple to integrate into any model.\r\n2. Figure out what adding cancelation semantics to iterators looks like. Then dualize that. Remember to separate the steps of dualization, bijection to something more convenient, and finally renaming into something punny.\r\n"}}