-
Notifications
You must be signed in to change notification settings - Fork 85
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
switchOnNext #8
Comments
Thank you! These are great suggestions for Flyd modules and I'll be happy to create them. Would you please open a separate issue for I'm however, not too fond of you example. I think a simpler implementation could be made with mouseClicks = flyd.stream()
$('body').addEventListener('click', mouseClicks)
mouseDown = flyd.reduce(((acc, e) -> !acc), false, mouseClicks)
mouseMoves = flyd.stream()
$('body').addEventListener('mousemove', mouseMoves)
mouseDrags = keepWhen(mouseDown, mouseMoves)
flyd.map((e) ->
elem.css('translateX', e.pageX)
, mouseDrags) But that is obviously a matter of personal preference. |
I like that. Its actually better than adding and removing event listeners on the fly. |
Yes. That is what I think as well. You also get the advantage that the |
I realized I didn't make the best case for I was playing around with highland in this project integrating with React and Meteor. Meteor has its own way of doing reactivity. Basically, something like this c = Tracker.autorun(function() {
messages = Messages.find({username:'chet'}).fetch()
messagesStream(messages)
})
messagesStream = flyd.stream() Whenever a message with username "chet" is added, removed, or changes, this function reruns and adds all the messages to the messagesStream. Then I cause the messagesStream to render the messages accordingly. Now, the autorunStream = function(f) {
s = flyd.stream()
c = Tracker.autorun(R.compose(s, f))
s.onStop(c.stop)
} (P.S. I'm a huge Meteor fan if you couldn't tell) So, back to the point. searchStream = floyd.stream('')
searchStream.map(function(query){
autorunStream(function() {
Messages.find({text:query}).fetch()
})
}).map(R.map(renderMessages)) Now the problem with the following code is that the map returns a stream of streams. We could obviously flatten which would make sense. searchStream.fmap(function(query){
autorunStream(function() {
Messages.find({text:query}).fetch()
})
}).map(renderMessages) But when a new search query comes in, we create a new stream and the last query autorunStream doesnt get cleaned up! So we can do something like this: searchStream.fmap(function(query){
autorunStream(function() {
Messages.find({text:query}).fetch()
}).takeUntil(searchStream)
}).map(renderMessages) But now this is looking ugly. They way I had to do it with Highland and you'd have to do with RxJS is searchStream.map(function(query){
autorunStream(function() {
Messages.find({text:query}).fetch()
})
}).switchOnNext().map(renderMessages) That looks alright. But I think the syntax would be nicer like this: searchStream.switchMap(function(query){
autorunStream(function() {
Messages.find({text:query}).fetch()
})
}).map(renderMessages) Here, Anyways, I hope that provides some inspiration for this function. |
This is not documented yet but stream in Flyd has an end stream attached that emit a value when the stream ends. So you could do something like this: flyd.map(function() {
c.stop();
}, messageStream.end); Thanks for the write up. I'm not sure I understand all of your examples though. Flyd should definitely have I'm currently considering what to name the different functions. I'm not sure I like |
In other libraries, streams are more like events that are consumed. Thus, latest typically just grabs the latest value from the stream. In flyd, you can just call the stream again. As for naming, I think switch helps to imply that the previous streams are ended. In a way, we are creating a new stream that switches from stream to stream.
|
Yes, I see what you mean. That is a good point. I just had another idea. What about calling
I'm not opposed to |
Ok. I had to think on this for a while. Suppose
In In So here's my proposal. Thus P.S. How would you actually write function flatMap(f,x) {
return compose(flatten, map(f))(x)
} But is there a more formal way of doing this? |
Yes.
I don't think so. I agree that
That is a very good point. You're right that the naming in that case doesn't properly reflect which functions the functions a composed of.
You've convinced me. You make some splendid arguments. I agree that So instead of what I previously suggested we'd instead have this:
That is not my understanding of
How is that incorrect? With a proper function flatMap(f,x) {
return compose(flatten, map(f))(x)
} That is equivalent to: function flatMap(f,x) {
return compose(flatten, map)(f, x)
} And then we can make it point free: var flatMap = compose(flatten, map); |
Hmm. I think this will likely be a source of memory leaks for people. I'm trying to think of a case when you'd use flatten but wouldn't want it to end the underlying streams when the original stream ends...
Same thing here... Conceptually, I could see how you might use I believe you're correct though... I see no mention of ending the inner streams in the RxJS documentation. In Well that said, maybe we need something to handle that as well? Something like
I like it 👍
So this is what I'm a little confused about. I guess compose looks at the function.length and curries appropriately? It sees that map takes 2 arguments, then passed the result of map into flatten... that makes sense. So I could see this working so long as only the right-most function has more than one argument. What if we compose multiple functions with multiple arguments? How would this work? var doubleMap = compose(map, map);
doubleMap(f1, s, f2) = compose(map(f2), map(f1))(s) // ???
// or
doubleMap(f1, f2) = compose(map(f2), map(f1)) // ? |
This is an example from "The introduction to Reactive Programming you've been missing": var requestStream = Rx.Observable.just('https://api.github.com/users');
var responseStream = requestStream
.flatMap(function(requestUrl) {
return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
});
responseStream.subscribe(function(response) {
// render `response` to the DOM however you wish
}); Here the streams/observables created in the
I don't think There is only one, albeit huge, problem left. People would want to import switch like this
It could do. The
Yes, that is exactly how it is. Only the right-most function can take more than one argument. Ramda has useWith and converg. They can be used as alternatives in some cases. But otherwise you'd have to give up on point free and just create a "normal" function. |
The problem with this example is that the stream ends itself after it emits once. Suppose the internet is slow and you want to cancel the request or something like that. You may want to end the request stream. The response stream will be ended, but the underlying streams will be orphaned and cannot be ended. Try to imagine a case that doesnt end itself... Here's an example. Suppose we have a stream of chatroom names. When we get a new chatroom, we want to clear the messages rendered on the screen. Then we want to subscribe to messages from that chatroom -- creating a stream and using switchMap -- then we want to render all those message on screen. chatroom = flyd.stream('flyd');
compose(map(renderMessages), switchMap(createMessagesStream), map(clearMessages))(chatroom) If we close the chatroom window and end the chatroom stream, then the individual message streams for each chatroom visited will be orphaned and not ended! This doesn't seem like a good functionality...
I meant to say that f depend on the values emitted from x. Just as the messagesStream depends on which chatroom.
Hmm. Well wouldn't it be namespaced by |
If function(roomName) {
return flyd.endsOn(chatroom, doCreateMessageStream(roomName));
} Now the streams created by But I can see what you mean. This case might be common enough that having a version of
It would be a module, and people can import then how they want. I've often imported them directly for convenience. You could do What about naming it |
If switch is going to end streams when it moves on to the next, then its almost just "chaining" streams together. Maybe "stitch", "weave", "chain", "daisyChain", or something like that? I'm a big fan of single word names for these most basic functions though. I think it would be a good general practice that if a stream is created within another stream, then it ends when the outer stream ends... |
Consider this example. You have a stream that emits a stream of messages from current visible chatroom. I.e. whenever the user switches between a chat room tab the stream changes. var currentChatroomMessages = /* something */ You render these messages like in your last example. But the message streams in The
Then the only thing I can think of is |
+1 for |
Ok. So there are two difference scenarios here.
a = flyd.stream()
b = flyd.stream()
c = flyd.stream()
streams = [a,b,c]
which = flyd.stream(0)
current = switchLatest(function(i) {
return streams[i]
}, which)
all = lift(args2array, a,b,c)
// do whatever you want because a, b, and c are never ended This makes sense not to end those streams, and yes, this is a more general case.
chatrooms = flyd.stream('1')
messages = switchLatest(function(id) {
return createMessageStream(id)
})
So why can't we safeguard against that? Here's an interesting idea -- check to see if you are creating a stream within a stream and call Whenever you are running the function of a stream (not sure the lingo here, but by that I mean, the second argument of the stream function), then keep track of the current stream that is running. Something like this happens: prevStream = flyd.currentStream
flyd.currentStream = s
s.f()
flyd.currentStream = prevStream Then whenever we get a constructor for a stream, we check to see if there is a current stream (checking to see if we created a stream within a stream), and make sure it ends when its parent stream ends. if (flyd.currentStream) {
takeUntil(flyd.currentStream, this)
} This may seem like overkill. But on the other hand, it seems like a reasonable functionality to me. It actually reminds me of how Tracker works... |
|
@davidchase Thanks for jumping in! @ccorcos I can totally see where your coming from and what you suggests makes sense. But, in your example chatrooms = flyd.stream('1')
messages = switchLatest(function(id) {
return createMessageStream(id)
}) No one but |
Hmm. Good point... But if a stream is garbage collected, then its not necessarily ended... It seems like that could have some undesireable effects somehow. I see your point, just makes me feel a little uncomfortable. |
I think of it this way: I stream can either end, in which case it signals to all it's dependents that it's done. Or, it can become unnecessary in which case the garbage collector will get rid of it. It's two different things. I don't think a stream needs to end unless it actually really ends. |
|
I see. Sweet! Gotta love how simple it is in the end... |
👍 |
I just realized the problem with not ending the stream and letting garbage collection take care of it... In |
Sorry for the late reply :( That is a very valid problem. You could set up the end conditions on the created stream so that it ends when the source emits a new value. But the better solution would probably be for Flyd to introduce laziness. Laziness has disadvantages though so this is something I'll have to think about. |
I'm going to end up doing this everywhere: chatrooms = flyd.stream('1')
messages = switchLatest(function(id) {
return createMessageStream(id).takeUntil(chatrooms)
}, chatrooms)
Hmm. Not sure I follow how laziness is relevant here... Also, I think introducing laziness would ruin the KISS aspect of Flyd. |
Well, creating a version of
With lazyness a stream would only refer to its dependencies when it has active subscribers. So when Implementing laziness would be quite easy. But it definitely has some negatives impacts on user friendliness. |
Hmm. This sounds confusing. I would consider the example I explained earlier where the constructor checks to see if the stream is being created inside another stream and will endOn the parent accordingly...
|
I must say, I like the simplicity of this library.
Some functions that I think are missing are
takeUntil
andswitchOnNext
Suppose you are dragging an element. Its helpful to cleanup streams when they're done being used with
take
andtakeUntil
.The text was updated successfully, but these errors were encountered: