Does Signal.merge discard or queue the second signal if both come in at the same time?

99 views
Skip to first unread message

Do Bi

unread,
Dec 15, 2014, 9:37:35 AM12/15/14
to elm-d...@googlegroups.com
Hi,

my question is as simple as stated in the topic: Does Signal.merge discard or queue the second signal if both come in at the same time?
(I'm not sure, what is meant by "the left update wins. ^_-)

Regards,
Tobias

Janis Voigtländer

unread,
Dec 15, 2014, 9:48:18 AM12/15/14
to elm-d...@googlegroups.com
It discards.
--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Do Bi

unread,
Dec 15, 2014, 10:32:21 AM12/15/14
to elm-d...@googlegroups.com
Oh, OK, thanks. I was hoping the opposite. ;-)

If I understand correctly, this means that in this example ( http://codepad.org/RA7OpnAu ) it is possible to miss a second if a key is pressed in the wrong moment.
Is there a possibility to make sure that this can not happen?

Tobias



Am Montag, 15. Dezember 2014 15:48:18 UTC+1 schrieb janis.voigtlaender:
It discards.

Am Montag, 15. Dezember 2014 schrieb Do Bi :
Hi,

my question is as simple as stated in the topic: Does Signal.merge discard or queue the second signal if both come in at the same time?
(I'm not sure, what is meant by "the left update wins. ^_-)

Regards,
Tobias

--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+unsubscribe@googlegroups.com.

Janis Voigtländer

unread,
Dec 15, 2014, 11:19:08 AM12/15/14
to elm-d...@googlegroups.com
No, you cannot miss a second in that example. The reason is that a signal derived from the every-function and a signal derived from Keyboard.keysDown will never fire at exactly the same moment. At least not in the current implementation of the runtime. Basically, this is the same as discussed in this recent thread: https://groups.google.com/d/msg/elm-discuss/FxlfqoXGyY8/7RlIETUZq3YJ

To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.

Do Bi

unread,
Dec 15, 2014, 11:27:10 AM12/15/14
to elm-d...@googlegroups.com
Oh yes, this is the same question. Thank you.
Then I have to hope that the runtime will not be changed regarding this. (At least not without an announcement. ^_-)

Jason Merrill

unread,
Dec 15, 2014, 2:33:18 PM12/15/14
to elm-d...@googlegroups.com
What's the case where dropping duplicates like this is desirable?

It seems like this is at least a common source of anxiety, if not actually a common source of bugs.

If merge didn't drop anything, a user could always wrap it in a Signal.dropRepeats if they wanted. That wouldn't be exactly the same as the current behavior, but I'm curious if there's a case where that matters.

Do Bi

unread,
Dec 15, 2014, 2:48:11 PM12/15/14
to elm-d...@googlegroups.com
Since relying on unspecified/undocumented features is almost always not a good idea, I tried to come up with something that should work in any case: http://codepad.org/Jsb2as3l
It is not beautiful but should be safe for this special case.

Despite that, I also think letting merge never drop but queue could be a good thing.

Janis Voigtländer

unread,
Dec 15, 2014, 2:56:55 PM12/15/14
to elm-d...@googlegroups.com
Well, first of all, you probably got the two branches of the "if keysChanged" the wrong way around.

More importantly, your code also relies on undocumented behavior. Namely, you assume that there will never be two consecutive events on Keyboard.keysDown with the same value. Don't you?





--

Do Bi

unread,
Dec 15, 2014, 3:07:30 PM12/15/14
to elm-d...@googlegroups.com
Oh, yes. There was something wrong with keysChanged. The branches were not really mixed, since the code works as expected. I only named keysChanged wrongly. I should be called keysStillTheSame. ;-)

And yes, you are right again. I was relying on Keyboard.keysDown not firing two consecutive equal events. But this is of course easy to fix: http://codepad.org/23pJqQAe
So this version should be safe, right?

Janis Voigtländer

unread,
Dec 15, 2014, 3:23:52 PM12/15/14
to elm-d...@googlegroups.com
I don't think even this version would work in general without the assumption that keysDown and the every-function never fire at the same time.

Consider: after 1.5 seconds some keys are pressed, after 2.0 seconds some other keys are pressed. Assume that that second occurrence is so exactly at 2.0 seconds that keysDown and the every-signal have an event at exactly the same time. Then your code will not update the passedSeconds-field, instead it will stay at 1 second. Which is not what you want, right?

That your new code "works as expected" relies on the very same "undocumented runtime guarantee that keysDown and the every-function are never in competition" as the merge-based code did.

In summary, Signal.map2 (plus comparison hacks) is not a good replacement for merge.

Do Bi

unread,
Dec 15, 2014, 3:46:10 PM12/15/14
to elm-d...@googlegroups.com
Thanks for the explanation. Yes, in this case the time update at 2.0s could be dropped, which would not be wanted in my case.

But I will not give up so easily. ;-)
http://codepad.org/hgPtj3OY
I think this should do the job, right? :D

Do Bi

unread,
Dec 15, 2014, 3:52:43 PM12/15/14
to elm-d...@googlegroups.com
Damn, I just realized, the version above can make mistakes in the very first second of the program running.
But this should also be fixed with that version: http://codepad.org/B96Bbf6H

Janis Voigtländer

unread,
Dec 15, 2014, 3:55:31 PM12/15/14
to elm-d...@googlegroups.com
Well, yes, but now you rely on particular properties of the every-function, namely that it is indeed correlated to time. If you want to go that way, why not simply defining passedSeconds by dividing input.newTime by 1000 (after substracting the initial time stamp at program start)? So this seems very specific to exactly *that* combination of these specific two signals. It's not something that is useful as a strategy for replacing Signal.merge.


Janis Voigtländer

unread,
Dec 15, 2014, 3:57:11 PM12/15/14
to elm-d...@googlegroups.com
See my other just sent mail. Maybe the new version fixes the specific case. But frankly, I think you should go with Signal.merge.

Do Bi

unread,
Dec 15, 2014, 4:25:53 PM12/15/14
to elm-d...@googlegroups.com
Yes, but this particular property of Time.every is documented: "Takes a time interval t. The resulting signal is the current time, updated every t."
Of course just using Signal.merge would result in much more readable code, but now I just wanted to know if there is a possibility to solve it without relying on undocumented things at all. And I guessed the last version did exactly this, even though using "(currentTime - startTime) / 1000" would be simpler.

But as far as I can see both version have to rely on the fact that "Time.every second" does not fire at second 0 but starts at second 1 (or vice versa) which is not documented. Argh. Luckily in the particular application I'm writing at the moment I do not need the timer to start with the application but at a specific action, so this is another topic.

So, thanks again for the interesting discussion. I learned a lot. And I still vote for making Signal.merge to queue instead of drop by definition. ;-)

Janis Voigtländer

unread,
Dec 16, 2014, 12:31:44 AM12/16/14
to elm-d...@googlegroups.com
Re the:


> If merge didn't drop anything, a user could always wrap it in a
> Signal.dropRepeats if they wanted.

There must be some confusion here. Note that dropRepeats drops by comparing values, not times. It's not the case that two events arriving at the same time as inputs to merge will always have the same value (and so could be sorted out by dropRepeats).


Jeff Smits

unread,
Dec 17, 2014, 6:38:48 AM12/17/14
to elm-discuss
FYI: the current implementation of merge is easier to implement and has a fairly obvious special case to worry about. A queueing kind of merge would have even more subtle behaviour consequences with some events becoming unsynchronised by the delay the merge imposes on a collision. Depending on the implementation of such a queuing merge, in particular the synchronous one that would be possible to implement in JS, it could even affect throughput and synchronisation of events in multiple rounds after the collision. 

In a version of Elm that supports async, you can choose for yourself to do an asyncMerge l r = merge l (async r). Then the merge is guaranteed not to collide by the semantics of Elm and it's obvious that r loses synchronisation. In the current implementation of Elm I'm afraid there it no such solution (unless I misunderstand how the JavaScript event loop works). 

Jason Merrill

unread,
Dec 17, 2014, 11:19:06 AM12/17/14
to elm-d...@googlegroups.com
On Wednesday, December 17, 2014 3:38:48 AM UTC-8, Jeff Smits wrote:
A queueing kind of merge would have even more subtle behaviour consequences with some events becoming unsynchronised by the delay the merge imposes on a collision.

Why does merge impose a delay?

It sounds like there's some piece of time ordering semantics in Elm that I don't know about yet. Is this documented somewhere?

Jeff Smits

unread,
Dec 17, 2014, 11:26:37 AM12/17/14
to elm-discuss

The current implementation of merge doesn't impose any delay.

I was talking about the hypothetical queuing merge that's mentioned in this thread as a way to avoid having merge drop the event of the right signal when both left and right update in the same round (which I'm calling a collision)

--

Jason Merrill

unread,
Dec 17, 2014, 11:36:33 AM12/17/14
to elm-d...@googlegroups.com
Ok, but why would the hypothetical queuing merge impose a delay? Is there a rule that a signal doesn't fire twice in the same event loop?

Jeff Smits

unread,
Dec 17, 2014, 1:44:50 PM12/17/14
to elm-discuss
Every "round" a signal has one and exactly one event, an update or a non-update. The non-update is part of the synchronisation system built into the runtime.
Merge let's the left event win if it's an update. If right in an update at the same time (collision, the current merge would drop this event), the queuing merge will delay the update to a later round.

If the queuing merge is synchronous it will have to wait around for the next round, which requires one of the inputs to fire. In that round there can be more updates coming from the two input signal which will also have to delayed as the queue shouldn't be starved. You can see trouble arising there already. By delaying the update from the right signal you're also breaking the synchronisation because the delayed event is dislodged from its round.

If the queuing merge is asynchronous (which I think isn't possible in JS right now), as I defined in an earlier post, any update from the right signal forces a new round to occur for that event. So you don't really have a queue that can builds up and no strange cascading round delays. You do still break synchrony, but now explicitly in a way that's also named in the theoretical model of Concurrent FRP.

If you're interested in these kinds of things, Evans thesis is a great resource, easy to read too.

Yu He

unread,
Dec 17, 2014, 3:54:20 PM12/17/14
to elm-d...@googlegroups.com
By default, I think signals should not have any implicit relation to time. It should be synchronous, atomic events. In a future implementation that supports asynchronicity, there can be time-sensitive functions for merging signals (discard/delay/whatever you want).
Reply all
Reply to author
Forward
0 new messages