-
Notifications
You must be signed in to change notification settings - Fork 206
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
delay() not functioning as expected. #200
Comments
I think you have to const mult2$ = mMmult.x.result.map(v => {
mMmult.x.product2 = ret(v).x
});
const mult3$ = mMmult.x.result.delay(2000).map(v => {
mMmult.x.product2 = ret(v)
.bnd(add, 1000)
});
const mult4$ = mMmult.x.result.delay(10000).map(v => {
mMmult.x.product2 = ret(v)
.bnd(add, 1000)
.bnd(double).x
});
const mult5$ = mMmult.x.result.delay(15000).map(v => {
mMmult.x.product2 = ret(v)
.bnd(add, 1000)
.bnd(double)
.bnd(add, 1).x
}); As the example in the README shows: // After 4 seconds, logs 10
most.from([1, 2, 3, 4])
.delay(1000)
.reduce(function(result, y) {
return result + y;
}, 0)
.then(function(result) {
console.log(result);
}); |
That looks better. I think I tried doing it the right way and still didn't get a good result. Lots of late night, frustrating experimentation has receded into a distant fog. Algorithms that worked in my plain Snabbdom application all flopped. I opted for doing things more in the Cycle/Motorcycle way. See http://schalk.net:3099, repo at https://github.com/dschalk/JS-monads-part4. The relevant code is: const unitDriver = function () {
return periodic(1000, 1); // Creates a stream of 1-1-1-1..., in one-second intervals.
}
const sources = {
DOM: makeDOMDriver('#main-container'),
WS: websocketsDriver,
UNIT: unitDriver // Added unitDriver to the sources object.
}
const unitAction$ = sources.UNIT.map(v => { // unitDriver, cycled back around from "run".
mMunit.ret(mMunit.x + v) // mMunit is a monad dedicated to calling "next" with its bnd method.
.bnd(next, 2, mMZ26) // Releases mMZ26 (below) after two seconds.
.bnd(next, 4, mMZ27)
.bnd(next, 6, mMZ28)
console.log('mMunit.x ', mMunit.x)
})
const mult2$ = mMmult.x.result.map(v => {
mMmult.x.product2 = v;
let mMtemp = new Monad(v);
mMZ26.bnd(() => mMtemp.bnd(add, 1000).bnd(mMtemp.ret).bnd(x => mMmult.x.product2 = x));
mMZ27.bnd(() => mMtemp.bnd(double).bnd(mMtemp.ret).bnd(x => mMmult.x.product2 = x));
mMZ28.bnd(() => mMtemp.bnd(add, 1).bnd(x => mMmult.x.product2 = x));
mMunit.ret(0);
}) I put this comment in JS-monads-part4: "Algorithms that worked in JS-monads-part3, a plain Snabbdom application, don't work in Motorcycle.js. But Motorcycle.js is a whirling wonder, awe-inspiring to behold and to use." |
Seems like you forgot to flatten your inner stream(assuming map is related to most's operator).
BTW.. you might wanna look at the new |
I experimented some more with "dalay". Streams did not resume until after the specified milliseconds had elapsed, but then I could not depend on computation sequences to run only once. It is as though delay(n) means "continue once, or twice, or maybe more times after n milliseconds have elapsed." I have also been unable to get reliable results, or any results at all, using setInterval or setTimeout. I am very pleased with Motorcycle, and will continue to use it. My impression at this point is that my slolution (above) is not too convoluted. Defining a driver like my unitDriver and an action stream that receives values from it is the only way to get dependable results, as far as I can tell. My solution didn't require very many lines of code, and the driver can be re-used any time I want to delay the appearance of something in the DOM. But if there is a simpler way to reliably postpone future actions, I would much appreciate it if somebody would clue me in. |
This isn't the best place for this discussion, but I'll add what is probably my last comment here. I just want to show how the result is calculated in http://schalk.net:3099 without using MonadIter. Here it is: const mult3$ = mMmult.x.result.map(v => {
mMtem.ret(v)
mMmult.x.product3 = v;
mMpause.ret(0);
})
const mult4$ = sources.UNIT.map(v => {
mMpause.ret(mMpause.x + v)
if(mMpause.x ===1) {
mMtem.bnd(add, 1000).bnd(mMtem.ret).bnd(x => mMmult.x.product3 = x)
}
if(mMpause.x === 2) {
mMtem.bnd(double).bnd(mMtem.ret).bnd(x => mMmult.x.product3 = x)
}
if(mMpause.x === 3) {
mMtem.bnd(add, 1).bnd(x => mMmult.x.product3 = x)
}
}) This uses "unitDriver", defined above. mult3$ and mult4$ are merged into the main stream that maps data into the virtual DOM description. mMmult.x.product3 is placed in a paragraph. |
Hey @dschalk. Appreciate all the examples in this thread. Is it possible you can provide a simplified example that reproduces the problem(s) you've described? For example, you could use requirebin or something that could be pasted directly into a js file and run with node locally. I'm quite confident in delay's behavior (the circles example is a seriously intense stress test of it), but I'd like to see if there may be an edge case we've missed. It could also be that your expectations about what delay is supposed to do don't match what it actually does. That could mean that we need to improve our documentation. Anyway, it'd be really helpful to have a simple, runnable test case. I've noticed that in your examples, you seem to be using shared mutable state and side effects. For example, the purpose of Using side effects in a highly asynchronous system can be very difficult to reason about. One goal of reactive programming is to forego side effects, in favor of purely transformative data flow style. That made me wonder if some of the oddities you are experience are due to using a side-effectful approach. I hope that didn't come across as critical, but rather as just trying to understand the situation, with the goal of trying to help! |
The erratic behavior occurs only when I make a change that starts setTimeout or delay before a previous setTimeout() or delay has completed. window.clearTimeout() is ignored in my up-to-date versions of Chrome and Firefox. When I change a number in http://schalk.net:3099 using BACKSPACE and then entering a substitute number, a computation starts immediately after the backspce key is pressed and then again when the new number is entered. The base number gets 1000 added to it twice and gets doubled twice. This code works perfectly if I wait for completion after pressing the backspace key: const mult5$ = mMmult.x.result
.filter(x => x !== 0).map(v => {mM27.ret(v)}).delay(1000)
.map(() => mM27.bnd(add, 1000).bnd(mM27.ret)).delay(1000)
.map(() => mM27.bnd(double).bnd(mM27.ret)).delay(1000)
.map(() => mM27.bnd(add, 1).bnd(mM27.ret)).delay(1000) If I use only single digit numbers, I don't need to wait because the filter prevents a computation from starting when a box is empty. mM27 is an instance of Monad. Using its "bnd" method with add and double does what you would expect, but they return an anonymous monad holding the computation result in the "x" attribute. "bnd(mM27.ret)" uses mM27's "ret" method to fetch the result from the anonymous monad. clearTimeout's not working in Motorcycle doesn't seem to be relevant to 'most', although it might make it hard to fashion an "abort delay" function that works in Motorcycle. I think an "abort delay" function might be a useful addition to For a while, my monad My problem stems from the fact that I use You certainly didn't come across as being unfairly critical. Would the immutable library that people use with React be useful in a Motorcycle application? |
The code below is now part of the app running at http://schalk.net:3099 . It consistently gives the desired results because const mult5$ = mMmult.x.result
.debounce(3200).map(v => {mM27.ret(v)}).delay(1000)
.map(() => mM27.bnd(add, 1000).bnd(mM27.ret)).delay(1000)
.map(() => mM27.bnd(double).bnd(mM27.ret)).delay(1000)
.map(() => mM27.bnd(add, 1).bnd(mM27.ret)).delay(1000) I'm starting work on JS-monads-part5. It will feature immutable data structures using the Facebook immutable-js library. |
@dschalk v0.18.2 just landed with a fix for a very subtle issue related to
Sure. Immutable.js is a general purpose immutable data structures library, so it's quite useful in almost any context :) |
I upgraded to v0.18.2. Nothing changed. If I don't wait for an already-started computation sequence to finish, I get larger than desired results due to both computation sequences calling I don't know if it is feasible, but a "clearWait" function at the beginning of my computation sequence would make |
@dschalk It was a rare situation, so I'm not surprised nothing changed. It's still a bit tough to understand what you're seeing without a runnable example. Any chance you could put one together? |
@briancavalier I changed the definition of Monad's It is all set forth at the bottom of the page at http://schalk.net:3099. For your convenience, I will repeat the relevant definitions: var test$ = sources.DOM.select('input#addF').events('input');
const testAction$ = test$.map(e => mMtest
.ret(e.target.value*1)).delay(1000)
.map(() => mMtest.ret(mMtest.x + 1000)).delay(1000)
.map(() => mMtest.ret(mMtest.x * 2)).delay(1000)
.map(() => mMtest.ret(mMtest.x + 1)).delay(1000) By the way, substituting I don't think there is much I need to add to what I presented at the bottom of the page at http://schalk.net:9093. As I mentioned there, I got 240514 in a computation that should have produced 2003 by pressing "1" seven times in rapid succession and then rapidly pressing BACKSPACE seven times. Running simultaneous computation sequences causes the number to double and have "1" and "1000" added to it multiple times. This does not happen when I substitute I don't know if you would consider this behavior a bug. Afterthought: |
Update: Nothing is being mutated in the app running online at http://schalk.net:3099. I found an aberration in this: const mult5$ = mMmult.x.result
.map(v => {mM27.ret(v)})
.map(() => mM27.bnd(add, 1000).bnd(mM27.ret)).debounce(1000)
.map(() => mM27.bnd(double).bnd(mM27.ret)).debounce(1000)
.map(() => mM27.bnd(add, 1).bnd(mM27.ret)).debounce(2000)
I said it was perfect, but I was able to cause it to give a result slightly greater than what is was expected. I didn't figure out a way to reliably reproduce the aberation, although fooling around with the demo did cause it to happen three times. I say it is an "aberration", but all I mean is that it is an aberration in my monad demo app. And by that I mean that if I re-start the three-step demo calculation while a previously-started calculation is still running, I get side effects from the previously started calculation. Another way of looking at it is to say that the side effects from a previously started sequence of calculation steps using debounce sometimes propagate, thereby skewing the outcome of the most recent sequence of calculation steps. That sort of behavior would be a bug in a database, where a query must either run to completion or completely reverse, assuring that ongoing queries have no side effects on subsequently initiated queries. This atomic behavior in a database involves reversing the steps in a query that has been superseded, or else letting it run to completion before the next query begins. My algorithms that work, the ones using
Here is one of the ways I avoided side effects from ongoing previously-initiated sequences : mMmult.x.addA = sources.DOM.select('input#addA').events('input');
mMmult.x.addB = sources.DOM.select('input#addB').events('input');
mMmult.x.result = combine((a,b) => a.target.value * b.target.value, mMmult.x.addA, mMmult.x.addB);
const unitDriver = function () {
return periodic(1000, 1); // Creates a stream of 1-1-1-1..., in one-second intervals.
}
const sources = {
DOM: makeDOMDriver('#main-container'),
WS: websocketsDriver,
UNIT: unitDriver // Added unitDriver to the sources object.
}
const mult$ = mMmult.x.result.map(v => {
mMmult2.ret(v); // Not relevant here.
mMtem.ret(v);
mMtem2.ret(v); // Not relevant here.
mM28.ret(v); // Not relevant here.
mMpause.ret(0);
mMpause2.ret(0); // Not relevant here.
});
const mult4$ = sources.UNIT.map(v => {
mMpause.ret(mMpause.x + v)
if(mMpause.x ===1) {
mMtem.bnd(add, 1000).bnd(mMtem.ret)
}
if(mMpause.x === 2) {
mMtem.bnd(double).bnd(mMtem.ret)
}
if(mMpause.x === 3) {
mMtem.bnd(add, 1).bnd(mMtem.ret)
}
})
I will leave the |
@dschalk Thanks, I appreciate the effort you're putting into your app and into learning algebraic structures. FWIW, most.js Streams are a (or more properly, "have a") Functor, Applicative Functor, Monoid, and Monad per the fantasy-land specification for JS algebraic structures There's quite a bit there on your app's page. That's cool. However, it also makes debugging extremely difficult. I think our goal needs to be to find a way to create a smallest possible case that reproduces a problem. Without doing that, it seems impossible determine whether most, motorcycle, or your own code is the source of the issue. As a next step, can you take the last debounce example that you mentioned and try to isolate it? For example, are you able to reproduce the same issue if you remove motorcycle from the picture and implement a similar example using on most and |
I eliminated mutating code for the game TAKE BACK and FORWARD features at http://schalk.net:3099.. I don't think any mutating remains now. I am not able to reproduce the I doubt that it was the precise numbers I entered or deleted that caused the unexpected behavior. It doesn't make sense that only certain numbers would do it. I just now tried rapidly adding and deleting numbers and every time I got the expected result. I could use a function that runs a million computations in rapid succession comparing the debounce result with the results produced by the algorithms that have, so far, consistently produced the expected results. I could make it stop if and when the results don't match. If a discrepancy occurs, I could write a more elaborate function to record the history of everything it did up to the point of failure. I doubt that there would be any discrepancies in a simple test environment. I will gladly create a debounce example that prints to the browser console instead of the DOM, or prints to the system console if you prefer. I don't understand what you mean by "using on most". Did you mean to write "using most"? As I explained above, I wouldn't expect to observe any anomalies in a simple test environment. By the way, I am looking forward to studying the most code. I am somewhat familiar with Haskell Functor, Applicative Functor, Monoid, and Monad. My http://schalk.net:3099 server is built on the wai-websockets server. Game state is held in a TMVar. When it came time to segregate text messages and scoreboards into user-defined groups, adding the feature was easy. It was as though I just had to tell Haskell what I wanted. Everything is so intuitive and simple to code with pattern matching and list comprehension. I verified that my monads obey the Haskell monad rules. I was amazed at how the simple MonadIter instances were able do what generators and promises do, only without error handling, which doesn't seem to be necessary in what I have been doing. This week I was delighted to discover that mutation can easily be avoided by putting changeable values in Monad instances and using their After what I said about the debounce situation, you might be thinking that the aberrations were artifacts of motorcycle static, some noise I created, or some such thing and not a debounce flaw. If, however, you think I can help you track down something that makes debounce less than perfect, please tell me how you would like for me to proceed. Thanks. |
I tried some more, but the algorithm using |
I have been having trouble with postponing changes to the dom in my motorcycle.js app running at http://schalk.net:3099, repo at https://github.com/dschalk/JS-monads-part4. I haven't been able to get functions that rely on setTimeout to work at all, or to update the DOM when they are supposed to. I'll try promises next.
I am raising an issue here because, when I tried `most.delay()', every instance of its use was triggered at the time specified in the first instance. Here is the code:
These are all merged into the stream that initiates the virtual dom. The first result displays correctly. Then, after two seconds, the result from the last stream (delay(15000)) displays. It is as though the first time interval specified is applied in all subsequent instances.
The text was updated successfully, but these errors were encountered: