On Monday, December 12, 2016 at 11:56:19 PM UTC-6, Janis Voigtländer wrote: > > Okay, so I think that your implementation strategy is viable and addresses > the use cases that the earlier tick batching trick addressed. One could do > this as you propose with tasks, handling the “what about chained > continuations?” concern (that I had brought up) by deferring any andThened > tasks that are not Task.succeeds to later, not even attempting to include > their result messages into the current “epoch”. > +1 This is a cleaner and simpler model (a related question from a while ago <https://groups.google.com/d/msg/elm-discuss/AF1U1FqVjJc/CKxDOKWzEAAJ>).
+1 for reducing Cmd and Task to a single primitive concept if that is doable. Not sure if others have seen this, but I really like the design of Effects in Python <https://effect.readthedocs.io/en/latest/>, including chaining, effect managers (dispatchers + performers), and opaque vs. transparent types. > I wonder how predictable this implementation would be for a user of > animations/ticks. They would have to keep a model in mind of what happens > when. The situation with non-chainable tick effects is conceptually > simpler. Or, to put it differently, maybe the actual use cases for tick > effects are all such that one only wants to “trigger something in the next > epoch directly”, and giving the user the ability to andThen on a tick may > tempt them into bad structuring of their code? Constraints in an API can be > very valuable, so if there are certain effects (ticks and possibly others) > for which better user code will result from taking the ability of > andThen-chaining > away from the user, then that would be justification for having a > separation between Task and Cmd (independently of the question of whether > andThen *can* be given some involved semantics on those effects). But I’m > not making a strong statement on that being the case here. And in any case, > you could reply that such a separation, taking andThen away from the user > for certain effects, could be put on top of an only-Tasks implementation > via a wrapper type that exposes only some of the Task operations; so it > would not require making both Task and Cmd primitive types. And you would > probably be right. > > > 2016-12-13 1:54 GMT+01:00 Mark Hamburg <[email protected] <javascript:>>: > >> More "implementation" notes for this theoretical discussion. If you >> dropped off earlier, you can stay dropped off and leave it to Janis and me. >> If you are following along, here's another little tweak: >> >> I just looked at the implementation of tasks and saw that they make very >> heavy use of andThen. They probably don't need to be implemented this >> way, but they are. So, I would simplify the logic I present above and and >> get rid of the deferred queue. When an animation frame occurs, it enqueues >> all of the active tasks for the tick and then runs the execution queue >> until it is empty before actually invoking the view function. The moment >> something triggers code that does not resolve immediately, it ceases to be >> an issue ahead of the view rendering, but until that happens we chew >> through everything that is outstanding. This could be a bit more work >> before doing the animation rendering, but it's simpler code and simpler to >> explain. >> >> Mark >> >> On Mon, Dec 12, 2016 at 6:03 AM, Mark Hamburg <[email protected] >> <javascript:>> wrote: >> >>> Ah. Sorry. My misunderstanding. I went looking for the Effects.tick >>> replacement in 0.17+ and only found AnimationFrame. >>> >>> Mark >>> >>> On Sun, Dec 11, 2016 at 10:32 PM Janis Voigtländer < >>> [email protected] <javascript:>> wrote: >>> >>>> Mark, wow, a lot of material. I’m not able to digest all of it right >>>> now. But something quick I want to say, because I have the impression that >>>> there is a misunderstanding about the current state of tick batching >>>> support. >>>> >>>> >>>> This: >>>> >>>> >>>> >>>> >>>> Since I wasn’t proposing to get rid of subscriptions and since this >>>> functionality is covered through subscriptions, my proposal arguably >>>> doesn’t cause a problem. >>>> >>>> >>>> >>>> >>>> baffles me. I’m not sure what you mean by “this functionality is >>>> covered through subscriptions”. Tick batching wasn’t covered through >>>> signals (the closest predecessors of subscriptions), and isn’t covered >>>> through subscriptions (or at all!) right now. >>>> >>>> >>>> Specifically and relatedly, concerning this: >>>> >>>> >>>> >>>> >>>> The special behavior for tick wasn’t particularly documented for the >>>> 0.16 Effects module and isn’t really documented for the 0.17+ >>>> AnimationFrame module. >>>> >>>> >>>> >>>> >>>> I should clarify that the tick batching trick as existed in the 0.16 >>>> Effects module is *not implemented* for Elm 0.17+. Apparently since >>>> gamey stuff is not anymore in Elm’s focus, Evan didn’t bring the “first >>>> ever effect manager” over to the new world when effect managers as such >>>> became a thing. >>>> >>>> >>>> (Did I indicate previously that tick batching as existed before is a >>>> thing currently in core or other 0.17/0.18 packages? I don’t think so. >>>> I was only bringing it up as a thing that can be done with effect managers >>>> and benefits from, maybe even is absolutely dependent on, having separate >>>> concepts of Task and Cmd. To decide about maintaining that latter >>>> stance, will require digesting your material more fully.) >>>> >>>> >>>> >>>> >>>> 2016-12-11 21:26 GMT+01:00 Mark Hamburg <[email protected] >>>> <javascript:>>: >>>> >>>>> Since I wasn't proposing to get rid of subscriptions and since this >>>>> functionality is covered through subscriptions, my proposal arguably >>>>> doesn't cause a problem. But that's just getting through the loophole of >>>>> this specific example to dodge this particular case, so let me see if I >>>>> can >>>>> get straight what the special property is that we need to preserve if we >>>>> were still trying to implement a tick command in the same manner of the >>>>> 0.16 tick effect. (It's worth noting as a side note that in 0.16, all >>>>> effects other than tick were tasks, so obviously tasks are close to >>>>> covering the role of commands — née effects — but tick may point to extra >>>>> wrinkles.) I'm not going to assume a constraint by the current task >>>>> implementation, but any revised implementation has to have a clear >>>>> behavior >>>>> in conjunction with the existing task APIs since its those APIs that make >>>>> tasks an attractive alternative to commands. >>>>> >>>>> The special behavior for tick wasn't particularly documented for the >>>>> 0.16 Effects module and isn't really documented for the 0.17+ >>>>> AnimationFrame module. Am I correct that the concern is that when the >>>>> animation frame "arrives": >>>>> >>>>> >>>>> 1. We run all of the update functions before actually calling the >>>>> view function; and >>>>> 2. Any tick effects that result from those updates will be batched >>>>> together for the next animation frame >>>>> >>>>> Was there anything else that I'm missing? >>>>> >>>>> The first point means that any implementation is going to have to be >>>>> built in conjunction with the Elm runtime since coordinating with the >>>>> call >>>>> of the view function requires knowledge of when and how the view function >>>>> gets run. But since the whole point of this is to coordinate with the >>>>> view >>>>> function that shouldn't be surprising nor is it particularly odd. It does >>>>> mean that my simple example using port tasks wouldn't work because it >>>>> wouldn't enjoy that coordination, but extra requirements add extra >>>>> implementation details. >>>>> >>>>> So, what we want is that we can divide time up with a series of >>>>> animation frame events and at each animation frame event, we want to >>>>> start >>>>> a new epoch for collecting tick effects, run the updates driven by all >>>>> effects collected for the previous epoch, and then do the view rendering. >>>>> A >>>>> naive implementation will just keep listening for all animation frames. A >>>>> more sophisticated implementation will take steps to shutdown that >>>>> observation when it is no longer necessary. >>>>> >>>>> The vague handwaving in the above is seemingly around what it means to >>>>> say "run the updates driven by all effects collected for the previous >>>>> epoch". In particular, how does this interact with task chains built >>>>> using >>>>> andThen, etc.? I am going to make a distinction between two ways to >>>>> specify successor computation on a task. In one case with map the results >>>>> or the error coming from the task to produce a new result or error. In >>>>> the >>>>> other, we map the results or the error coming from the task to produce a >>>>> new task. I would argue that the first category is what leads to actual >>>>> messages for the model. The second can be viewed as making no changes to >>>>> the model but queueing new task executions. >>>>> >>>>> So, how could this work in a task-based scenario? I'm going to speak >>>>> of tasks initiating and resolving to distinguish between when they have >>>>> code invoked v when they deliver values. >>>>> >>>>> When a tick task initiates, it adds itself to the set of tick task >>>>> executions for the current tick epoch. >>>>> >>>>> When we receive an animation frame call, we do the following: >>>>> >>>>> 1. We grab the contents of the tick epoch set and reset the set to >>>>> empty >>>>> 2. For each of the tick executions in the tick epoch set grabbed >>>>> in (1), we resolve the execution using the current time. This means >>>>> running >>>>> down the network of tasks chained onto the executions. For cases where >>>>> we >>>>> map the result or error to a new result or error, we do so and keep >>>>> working >>>>> down the chain. For cases where we map the result or error to a new >>>>> task, >>>>> we queue that task for execution. >>>>> 3. We execute the view function. >>>>> >>>>> In this way, we deliver all of the updates that are simply dependent >>>>> on the tick event or a mapping thereof while initiating any further >>>>> computations that were waiting for the tick event. >>>>> >>>>> The other piece of concern is how task queueing works and when tasks >>>>> get to execute. I would probably go for a structure on the task queue in >>>>> which it is also divided into epochs. We process all of the tasks within >>>>> an >>>>> epoch. Any tasks produced during this processing as a result of >>>>> andThen, etc. go in the next epoch. All tasks produced during an >>>>> update go in the current epoch which we process at the end of the update. >>>>> This design keeps chains of tasks from blocking other update processing. >>>>> We >>>>> could go further and pull tasks generated by tasks off of a queue that >>>>> executes even more incrementally. The key point is that all of the tasks >>>>> from an update get initiated immediately following the update and tasks >>>>> initiated from other tasks happen as we have time to process them. >>>>> >>>>> In the case of ticks, this means that a tick task that initiates a >>>>> chain will execute immediately following the update but a tick task that >>>>> is >>>>> initiated from some other task might get run at an arbitrary later time. >>>>> (How arbitrary determines whether we can do things like write Task.tick >>>>> |> Task.andThen (always Task.tick) in order to wait two ticks as >>>>> opposed to at least two ticks.) >>>>> >>>>> To make animation frame updates as efficient as possible, we probably >>>>> also want to avoid draining the task-initiated task queue until after the >>>>> view function is run. That's not critical to the semantics but it could >>>>> matter for performance. >>>>> >>>>> Now, maybe I've missed some other detail that matters, but as I've >>>>> said the documentation for both Effects.tick and AnimationFrame is >>>>> relatively thin on detailed semantics and requirements. But if I've >>>>> gotten >>>>> the concerns right, then I believe the above shows how a task-based >>>>> system >>>>> could do what is called for here. Am I missing something? I know I'm >>>>> handwaving through some of the execution machinery, but my intuition from >>>>> writing lots of promise systems in Lua says that this sort of structure >>>>> would work. >>>>> >>>>> Mark >>>>> >>>>> On Sat, Dec 10, 2016 at 10:20 PM, Janis Voigtländer < >>>>> [email protected] <javascript:>> wrote: >>>>> >>>>>> >>>>>> >>>>>> In the case of ticks, what I gather from a read through of the code >>>>>> that it does is guarantee that all of the tick requests placed between >>>>>> animation frame strobes will all be delivered at the next animation >>>>>> frame >>>>>> strobe. Is that a correct read? >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> Yes, that is a correct read. But no, I don’t think that your post >>>>>> shows the same can be done with tasks instead of commands. The reason >>>>>> is, >>>>>> as previously mentioned, the existence of andThen for tasks. And >>>>>> your “P.S.” does not address this, because you are not there considering >>>>>> what the real complication with andThen is. The complication is not >>>>>> whether a tick task involved in two andThen chains is run once or >>>>>> twice, the complication is how to deal with the “continuations” in those >>>>>> two chains. Let’s look at this with some example: >>>>>> >>>>>> >>>>>> In the Effects/Cmd world, using ticks would be like this: >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> 1. At some point, a command tick tagger1 reaches the runtime >>>>>> system, where tagger1 : Time -> Msg for Msg being the program’s >>>>>> message type. The runtime system will not do anything at that point, >>>>>> except >>>>>> for registering in some internal state that a tick request was >>>>>> issued, and >>>>>> that the tagger to use for it is tagger1. So essentially, the >>>>>> runtime system (specifically, the effect manager) at that point >>>>>> stores the >>>>>> function tagger1 in some list. >>>>>> >>>>>> 2. At some point after that, but before the next animation frame >>>>>> happens, a command tick tagger2 reaches the runtime system. At >>>>>> that point, the effect manager adds the function tagger2 to said >>>>>> internal list. >>>>>> >>>>>> 3. Some time later, the next animation frame is due. So the >>>>>> runtime system looks at its internal list, sees that there are >>>>>> [tagger1, >>>>>> tagger2], takes the current time stamp t, evaluates msg1 = >>>>>> tagger1 t and msg2 = tagger2 t, and passes msg1 and msg2 to the >>>>>> program’s update function one after the other *but without any >>>>>> intermediate view rendering*. If the update function creates >>>>>> additional effects/commands, the ones created from the calls of >>>>>> update with msg1 and msg2 are batched (so that actually the >>>>>> grouping together of updates will propagate to the future). >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> What about in your hypothetical world in which no Cmd abstraction >>>>>> exists, but instead tick has type Task Never Time? Now instead of >>>>>> just using tick with functions of type Time -> Msg, tick can be used >>>>>> in task chains, like tick |> andThen cont with cont : Time -> Task >>>>>> Never Msg. Let’s look at such a scenario: >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> 1. At some point, a task tick |> andThen cont1 reaches the >>>>>> runtime system, where cont1 : Time -> Task Never Msg. As above, >>>>>> the runtime system shouldn’t do anything at that point, except for >>>>>> registering in its internal state that there is this tick request and >>>>>> what >>>>>> to do when it eventually becomes active. The difference to above is >>>>>> that >>>>>> now instead of storing a function Time -> Msg for later use, the >>>>>> system has to store a function Time -> Task Never Msg. Fine >>>>>> enough. >>>>>> >>>>>> 2. At some point after that, but before the next animation frame >>>>>> happens, a task tick |> andThen cont2 reaches the runtime system, >>>>>> where cont2 : Time -> Task Never Msg. Again, the effect manager >>>>>> should simply add that to the internal list of open tick requests. >>>>>> >>>>>> 3. Some time later, the next animation frame is due. Now what? >>>>>> The runtime system knows that two tick requests are open. So of >>>>>> course it >>>>>> takes the current time stamp t and passes it to the two functions >>>>>> stored in its internal list. But instead of getting some msg1 : >>>>>> Msg and msg2 : Msg as above, it now gets some task1 = cont1 t : >>>>>> Task Never Msg and task2 = cont2 t : Task Never Msg. So *unlike >>>>>> above*, the strategy now cannot be to pass the relevant two >>>>>> messages to the program’s update function while ensuring that no >>>>>> view rendering happens in between (which was the whole point of tick >>>>>> batching in the animation scenario). Because those messages are >>>>>> simply not >>>>>> available right now. What is available are two tasks that when run >>>>>> will >>>>>> eventually return with messages (maybe after some http requests or >>>>>> whatever). So the runtime system can now fire off task1 and task2, >>>>>> but there is no way to tell when they will return, and certainly no >>>>>> assumption can be made that they will return still before the view >>>>>> rendering that is supposed to happen right now because we have an >>>>>> animation >>>>>> frame just due. In consequence, the whole point of tick batching is >>>>>> lost. >>>>>> We don’t get to ensure that groups of update calls get performed >>>>>> “atomically” without intermediate view rendering. >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> What do you think about the above? To me, it means that in the “just >>>>>> tasks” world tick batching as in the “separate tasks and commands” world >>>>>> is >>>>>> not possible (with the same quality). >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> -- >>>>>> >>>>>> >>>>>> 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 [email protected] <javascript:>. >>>>>> >>>>>> >>>>>> For more options, visit https://groups.google.com/d/optout. >>>>>> >>>>>> >>>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> -- >>>>> >>>>> >>>>> 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 [email protected] <javascript:>. >>>>> >>>>> >>>>> For more options, visit https://groups.google.com/d/optout. >>>>> >>>>> >>>>> >>>> >>>> >>>> >>>> >>>> >>>> >>>> >>>> >>>> -- >>>> >>>> >>>> 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 [email protected] <javascript:>. >>>> >>>> >>>> For more options, visit https://groups.google.com/d/optout. >>>> >>>> >>>> >> -- >> 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 [email protected] <javascript:>. >> For more options, visit https://groups.google.com/d/optout. >> > > -- 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 [email protected]. For more options, visit https://groups.google.com/d/optout.
