Coder Social home page Coder Social logo

TodoMvc perfs comparison about snabbdom HOT 25 CLOSED

snabbdom avatar snabbdom commented on May 9, 2024
TodoMvc perfs comparison

from snabbdom.

Comments (25)

avesus avatar avesus commented on May 9, 2024 1

Events could be queued. At the moment of their firing they should trigger global time tick wich drives FRP pulls. Right?

from snabbdom.

paldepind avatar paldepind commented on May 9, 2024

Very interesting. I know that the Elm app uses Elms Lazy and the Mercury app uses virtual-dom's Thunks. A simlair optimization could be made with Snabbdom's Thunks.

Can you tell more about your FRP prototype?

from snabbdom.

yelouafi avatar yelouafi commented on May 9, 2024

I've been thinking since our last discussion on flyd if there is an alternative way to implement FRP behaviors without automatic dep. tracking. The idea is to use a pull method instead of the push method based on callbacks and change propagation.

For example in a React like application,

1- a dom event is fired
2- event listeners are notified (the push phase)
3- event listeners updates the application state
4- the system re-evaluates the view function and patches the DOM

In the FRP prototype

1- a dom event is fired
3- a message is published on the app
4- the system re-evaluates the view function and patches the DOM (the pull phase)

you'll see how it works better through the basic counter example

const app = new App();

const counter = app.when(0, [
  app.on('inc$'), (acc, _) => acc+1,
  app.on('dec$'), (acc, _) => acc-1
]);

app.view = () =>
  h('div', {style: countStyle}, [
    h('button', { on: {click: app.publish('dec$') } }, '–'),
    h('div', {style: countStyle}, counter()),
    h('button', { on: {click: app.publish('inc$') } }, '+'),
  ]);

window.addEventListener('DOMContentLoaded', () => {
  app.mount('#container');
});

counter is a behavior that subscribes to the inc$ and dec$ messages on the app instance. when the user clicks for example on the - button, the app publishes the dec$ message: there is no listeners or listener notification, the app just update a globally visible current message object. Then proceed immediately to the evaluation of the top app view function. When the view function invokes the counter() getter, the counter behavior matches the current message with its 2 subscriptions, since the the current message dec$ matches its second subscription, it'll update itself.

The benefit is that a behavior can invoke other behaviors during its evaluation without being forced to list explicit dependencies. The order of things is automatically maintained by the functional dependencies and there is also no memory leaks since there is no leaky references, all references are from the consumer (behavior) (as you said GC favours pull based evaluation)

To test the prototype i've reimplemented all your examples from the no-name-frontend framework, you can see the examples here.

from snabbdom.

paldepind avatar paldepind commented on May 9, 2024

I've been thinking since our last discussion on flyd if there is an alternative way to implement FRP behaviors without automatic dep. tracking.

I still don't understand how you think automatic dependency tracking helps implement behaviors. The semantics of behaviors is just a function from time to a value. Automatic dependency tracking can certainly be convenient. It is not a bad feature. But it brings us no closer to the semantics of behaviors!

The example you posted leaves me with a question. What if I do this:

const app = new App();

const counter = app.when(0, [
  app.on('inc$'), (acc, _) => acc+1,
  app.on('dec$'), (acc, _) => acc-1
]);

const doubleCounter = counter.map(n => 2 * n);

app.view = () =>
  h('div', {style: countStyle}, [
    h('button', { on: {click: app.publish('dec$') } }, '–'),
    h('div', {style: countStyle}, doubleCounter()),
    h('button', { on: {click: app.publish('inc$') } }, '+'),
  ]);

window.addEventListener('DOMContentLoaded', () => {
  app.mount('#container');
});

Now the view function never invokes counter. Then how is counter updated? If doubleCounter does not invoke counter every time it is invoked then the value isn't updated and if it does then you have one of the recurring problems with pull based architectures: needles recomputation of values that most often will not have changed.

from snabbdom.

yelouafi avatar yelouafi commented on May 9, 2024

I still don't understand how you think automatic dependency tracking helps implement behaviors.

in a nutshell, a behavior is t => a; if i have 2 behaviors a and b then a behavior c = t => a(t) + b(t) will have at every moment t the value a + b; behaviors ensure functional relations are kept on each point of time, and automatic deps tracking helps implementing these semantics by propagating changes automatically to dependents. in the form above, we can express more complex relations. but as we've discussed they can present some memory issues

Now the view function never invokes counter. Then how is counter updated?

app.view invokes doubleCounter which invokes counter, counter is updated lazily when it's invoked.

If doubleCounter does not invoke counter every time it is invoked then the value isn't updated and if it does then you have one of the recurring problems with pull based architectures: needles recomputation of values that most often will not have changed.

You've touched on the heart of the problem. but this happens only in dynamic switching cases, for example

view = () => {
  dyn = test() ? a() : b()
  h('p',   dyn)

the dyn dynamically switches between a and b based on test value. since behaviors are lazy by default (in my lib) a isn't updated by default when inactive. I'm aware of this, and this is inevitable in a pull based model. the direct solution is not practical, w'll have to keep the history of all events so the inactive behavior can update itself accordingly. the famous space/time leaks.

So either w'll have to be eager on evaluating a behavior which means needless recomputation, or stick to the lazy evaluation and try to workaround it

the 1st solution is presented in this switch example. the app switch between 2 views : a counter and a static view, but by default the counter will not be updated when switched off (something similar to the problem of lazy evaluation on Kefir and Bacon, i know, i'm contradicting myself :) ). so one can choose to make the counter eager by adding counter.keepAlive() and the app will evaluate the counter on each event. but i really don't fell this solution.

The 2nd solution i prefer is simply whenever in presence of a dynamic switching situation we can force the evaluation of all possible paths, this is only possible in strict languages like JavaScript when we can ourselves decide when to force an evaluation.

view = () => {
  test = testB(), a = aB(), b = bB()
  h('p',   test ? a : b)

it's a bit unfortunate, but this allows us to keep all the advantages of a pull model including automatic dep. management with simple function calls.

Another solution i'm considering is mixing the pull/push approach. on each event, notify all subscribers in a 1st phase so all reactive behaviors relying on events will be updated, then in a 2nd phase evaluates the view. But this solution comes with a restriction, during the 1st phase we can't invoke a behavior, because it may not have update itself yet. whereas in the pull model this is not a problem because calling a behavior will cause it to update. I don't feel this solution either because it'll restrict the expressiveness of the model. which is the main reason i've considered the pull approach.

from snabbdom.

paldepind avatar paldepind commented on May 9, 2024

in a nutshell, a behavior is t => a; if i have 2 behaviors a and b then a behavior c = t => a(t) + b(t) will have at every moment t the value a + b; behaviors ensure functional relations are kept on each point of time, and automatic deps tracking helps implementing these semantics by propagating changes automatically to dependents. in the form above, we can express more complex relations. but as we've discussed they can present some memory issues

That has nothing to do with the semantics of behaviors. What you're describing is a way to combine two behaviors. So you're not talking about how to implement behaviors – your talking about how to combine them which the semantics of behaviors says nothing about. Classic FRP offered lift for combining behaviors and that can handle your example. Behaviors is a very well defined thing and the concept has nothing to do with automatic dependency tracking.

app.view invokes doubleCounter which invokes counter, counter is updated lazily when it's invoked.

So on every update doubleCounter invokes counter (correct me if I'm wrong)? Then you already have a needless recomputation right there because in most cases counter will not have changed. If the dependency chain is much deeper the problem grows. This is the problem with demand driven (pull) approaches. You don't now what might have changed so you have to reevaluate all behaviors and the dependencies.

The 2nd solution i prefer is simply whenever in presence of a dynamic switching situation we can force the evaluation of all possible paths, this is only possible in strict languages like JavaScript when we can ourselves decide when to force an evaluation.

Yes. You're evaluating all possible paths at each render. That is needless recomputations.

from snabbdom.

yelouafi avatar yelouafi commented on May 9, 2024

Behaviors is a very well defined thing and the concept has nothing to do with automatic dependency tracking.

I think you're confusing again denotational and operational concerns

then you already have a needless recomputation right there because in most cases counter will not have changed.

Yes this is unavoidable helas,

You don't now what might have changed so you have to reevaluate all behaviors and the dependencies.

That's not exact. we don't go and evaluate all existing behaviors in the app. We evaluate the view function and the view function evaluates needed behaviors, if behaviors depends on other behaviors they will invoke them. It's simple as function calls, you call a top function which may call other functions and so on.

Yes. You're evaluating all possible paths at each render. That is needless re-computations.

As i said this is unavoidable. but not quite as you describe it. in typical UI apps, the app consist of a top view switching between child views by a client side router. views are most often stateless views are removed from the DOM when they are inactive which means they don't need to be evaluated (they can't receive events out of the DOM). so we're only evaluating the main view and the current view. The cases of eager behaviors inside dynamic switching are quite rare (at least in the cases i have in mind) and as i said this issue is also present in other push libs that relies on lazy evaluation like Bacon and Kefir

The reason i tested the prototype with Evan's benchmark is because i was wondering what's the cost of this needless re-computation. To check if the results were due to snabbdom or to the FRP prototype pull model i integrated also your implementation based on flyd and union-type. From what i've seen there is no significant performance penalty.

The cost of invoking a reactive behavior is really cheap. all it does is matching is own subscription with the current event, ie it's O(1). Of course the behavior may invoke other behaviors inside it but this is what we do in pure FP, functions invoke other functions right ?

from snabbdom.

paldepind avatar paldepind commented on May 9, 2024

I think you're confusing again denotational and operational concerns

No. I think you are confusing the denotational semantics of behaviors with something that has nothing to with it. What you described in you example had nothing to do with the semantics of behaviors. Automatic dependency tracking is of course an implementation concern but it doesn't get us any closer to the semantics of behaviors.

As i said this is unavoidable. but not quite as you describe it. in typical UI apps, the app consist of a top view switching between child views by a client side router. views are most often stateless views are removed from the DOM when they are inactive which means they don't need to be evaluated (they can't receive events out of the DOM). so we're only evaluating the main view and the current view.

But in a huge application with a lot of visible behaviors, long dependency chains and behaviors based on potentially expensive calculations I'd fear the overhead.

as i said this issue is also present in other push libs that relies on lazy evaluation like Bacon and Kefir

They don't rely on lazy evaluation. They implement a form of lazy subscription.

The cost of invoking a reactive behavior is really cheap. all it does is matching is own subscription with the current event, ie it's O(1). Of course the behavior may invoke other behaviors inside it but this is what we do in pure FP, functions invoke other functions right ?

It doesn't sound that cheap to me. It matches the current event with its own subscriptions and then invokes all the behaviors on which it depends. That is not O(1). It depends on the number of dependencies, their dependencies and so on. If you cache values and only invalidate them if you detect a change then the overhead might be small.

Right now this look very much tied to virtual DOM. Have you though about events originating elsewhere than in the DOM?

from snabbdom.

paldepind avatar paldepind commented on May 9, 2024

Let me also point out that originally FRP was implemented with a pull based architecture since that is easier to implement in a purely functional language and because it made it easier to implement the continuous time semantics.

In a non-pure language like JavaScript and without continuous time (i.e. all changes are caused by events) I think a push based architecture is the most attractive. Garbage collection is an issue but the solution you have here doesn't seem any better than the laziness found in Kefir, BaconJS and RxJS.

On the other hand pull based approaches are to my knowledge unexplored in JavaScript. So I still consider this very interesting work and I'm very curious about it.

from snabbdom.

yelouafi avatar yelouafi commented on May 9, 2024

Automatic dependency tracking is of course an implementation concern but it doesn't get us any closer to the semantics of behaviors.

(i hope you're not restricting the behaviors to those time only dependent. behaviors can depend only on time but can also depend on other behaviors themselves either constants, reactives, time dependents or even a combination of both)

In a purely pull based implementation we sample the behaviors at a defined interval. Now how do you implement behaviors in a purely push approach ?, you gave the answer

In a non-pure language like JavaScript and without continuous time (i.e. all changes are caused by events)

ie the implementation relies on change detection and propagation, you push new changes to dependents (instead of pulling a goal value (generally a view) and relying on function calls to pull all the rest).

But in order to propagate changes, you'd have to know the dependency graph. So one solution is to let the library user construct the dep. graph in an explicit way, ie. specify dependencies whenever it construct a (step) behavior with dependencies. that's awkward because the user has know about the deps. at creation time. so this will limit the range of expressions he can write whereas in the pull model, we can express arbitrary expressions. so the pull model is closer to the semantics. Now if we add automatic dep. tracking to our push implementation, the user has no longer to list deps. and better we greatly extend the range of expressions he can write, so this will brings us closer to the semantics: not a as purely based implementation but closer than a push implementation without automatic tracking, ie. with static deps.

Until now i was talking about step behaviors, ie that varies discreetly over a (still) continuous time. Now to mention what commonly called continuous behaviors. Continuous time as Conal said can be also described with resolution independent. which means that a time dependent behavior expression (like the position of a ball in motion) is, semantically, independent from any notion of sampling, you can sample the behavior 60 time/sec or 120 time/sec it doesnt matter, the finer the sampling interval the fine value you get from the behavior.

A purely pull based approach allows also expressing those behaviors. but i think also continuous time can be captured in push implementations, by using a timer as a driving event (although instantaneous predicate events are a little bit harder to implement).

But in a huge application with a lot of visible behaviors, long dependency chains and behaviors based on potentially expensive calculations I'd fear the overhead.

Yes you're right. Push implementations are generally considered more efficient than Pull ones. But i'd rather put things into perspective. there can be situations where pulling can be more efficient than pushing, ie when the view is small subset of the model.

For example in the case of a spreadsheet with millions of rows, in a push implementation changing a cell can cause a huge number of cells to recalculate even if the cells are not visible. In a pull implementation you'll only evaluate the visible cells and their direct and indirect depdendencies. the cost of recalculating visible cells can be much more smaller than pushing all changes to a huge model. And with the actual browser perfs the recalculation of the view can even un-noticed by the user.

They don't rely on lazy evaluation. They implement a form of lazy subscription.

Of course not, i meant that in some situations with dynamic switching, signals/streams stops updating themselves by missing events.

It doesn't sound that cheap to me. It matches the current event with its own subscriptions and then invokes all the behaviors on which it depends. That is not O(1).

the O(1) was meant to event detection. and i also added

Of course the behavior may invoke other behaviors inside it but this is what we do in pure FP, functions invoke other functions right ?

.

Have you though about events originating elsewhere than in the DOM?

Right now the interface to the world is `app.publish(message)´. The idea is borrowed from flyd streams that could be plugged like simple callbacks.

from snabbdom.

paldepind avatar paldepind commented on May 9, 2024

(i hope you're not restricting the behaviors to those time only dependent. behaviors can depend only on time but can also depend on other behaviors themselves either constants, reactives, time dependents or even a combination of both)

I'm not. But you're confusing the semantics of behaviors with methods for combining these behaviors. The denotation semantics for behaviors only describes the meaning of behaviors. Not how to create or combine behaviors (which is what you're talking about). As I mentioned classic FRP has lift for combining behaviors. lift is also defined with denotational semantics. But we can certainly have the semantics of behaviors without lift.

But in order to propagate changes, you'd have to know the dependency graph. So one solution is to let the library user construct the dep. graph in an explicit way, ie. specify dependencies whenever it construct a (step) behavior with dependencies. that's awkward because the user has know about the deps. at creation time. so this will limit the range of expressions he can write whereas in the pull model, we can express arbitrary expressions. so the pull model is closer to the semantics. Now if we add automatic dep. tracking to our push implementation, the user has no longer to list deps. and better we greatly extend the range of expressions he can write, so this will brings us closer to the semantics: not a as purely based implementation but closer than a push implementation without automatic tracking, ie. with static deps.

I'm not claiming that automatic dependency tracking is not useful. But you keep claiming that it brings us closer to the semantic model which is don't think is true. The semantics of behaviors is just a function from time to a value. Flyd gives you that with the time implicitly being Date.now() as you're mentioned. Adding automatic dependency tracking on top of that doesn't bring us closer to the semantics. It just gives us a more powerful way to construct behaviors.

A purely pull based approach allows also expressing those behaviors. but i think also continuous time can be captured in push implementations, by using a timer as a driving event (although instantaneous predicate events are a little bit harder to implement).

I agree. But instantaneous predicate events are also tricky to implement with a pull based approach.

For example in the case of a spreadsheet with millions of rows, in a push implementation changing a cell can cause a huge number of cells to recalculate even if the cells are not visible. In a pull implementation you'll only evaluate the visible cells and their direct and indirect depdendencies. the cost of recalculating visible cells can be much more smaller than pushing all changes to a huge model. And with the actual browser perfs the recalculation of the view can even un-noticed by the user.

That is a good point. But I think there generally will be more cases where the pull approach is favorable. One could probably also get the best of both worlds by combining push and pull where the push merely invalidates cached values and only the pull triggers recomputations.

Of course not, i meant that in some situations with dynamic switching, signals/streams stops updating themselves by missing events.

Great! That was just a minor precision.

the O(1) was meant to event detection. and i also added

No problem. I just wanted to emphasize that the O(1) part is only half the picture.

Right now the interface to the world is `app.publish(message)´. The idea is borrowed from flyd streams that could be plugged like simple callbacks.

What does app.publish(message) return? By looking at the example it looks like it returns a function? Why is that?

from snabbdom.

yelouafi avatar yelouafi commented on May 9, 2024

I'm not. But you're confusing the semantics of behaviors with methods for combining these behaviors. The denotation semantics for behaviors only describes the meaning of behaviors. Not how to create or combine behaviors

if you mean that the the semantics of a behavior is just a function of time : t -> a, then i think you're confusing the notions of semantic domain, and the denotational model as a whole. the semantic domain of behaviors Behavior a in the CFRP paper is defined by the semantic function at

at : Behavior a -> Time -> a

But the semantics of behaviors, ie how they are composed, and how they interact with events forms the whole denotational model (as for the what this is not just about what behaviors are but also about what values do we expect from the combinators of the model).

So when i say semantics of behaviors i'm not just looking at the basic definition t -> a, that's just the semantic domain. i'm looking at the implications of this definition. the essence of CFRP is the composition of simple behaviors into rich expressions. As said in the CFRP paper (section the Essence of modelling)

Behaviors are first-class values, and are buit up compositionally

speaking of lift, In the CFRP model lift is defined by

lift f b1...bn t = f( b1(t).....bn(t) )

look how lift is defined by combining existing behaviors. ie. it's defined on top of the combining feature offered by behaviors (in other terms lift is not a primitive like for example until which can not defined by simply combining behaviors but relies on the underlying implementation to povide it).

lift is not the only way to combine behaviors, it`s just a special case of combining. i can use the same feature to combine arbitrary behavior in arbitrary expressions like Conal did in his CFRP paper

wiggle = t => sin(PI * t) 
wiggleRange = (lo, hi) => t =>  lo + (hi - low) * (wiggle(t) + 1)/2
scaleInterval = wiggleRange(0.5, 1)
pBall = t => scale( scaleInterval(t), circle ) 
rBall = t => move( (vectorPolar(2.0, t), bigger(0.1, pBall) ) 

The underlying implementation must ensure that the relations defined by the above expressions are well maintained on every moment of the program.

IMHO, this is the essence of the behaviors. they don't just define time-varying values but also offer a great flexibility and richness in combining them (this simply denotes the power of composition). and the point of the FRP implementation is whatever are complex your combinations (expressions) it'll always ensure that the above relations are kept in sync at each point of time.

The semantics of behaviors is just a function from time to a value. Flyd gives you that with the time implicitly being Date.now() as you're mentioned. Adding automatic dependency tracking on top of that doesn't bring us closer to the semantics. It just gives us a more powerful way to construct behaviors.

As i said above, semantic domains (time, behaviors, events) are just part of the whole semantical model.

What does app.publish(message) return? By looking at the example it looks like it returns a function?

yes

Why is that?

the returned function acts as a callback that handles the incoming event by publishing it in the App.

from snabbdom.

paldepind avatar paldepind commented on May 9, 2024

if you mean that the the semantics of a behavior is just a function of time : t -> a, then i think you're confusing the notions of semantic domain, and the denotational model as a whole.

Well. The semantic of a behavior is just a function of time. Do you disagree with that? It is correct that behaviors also form a semantic domain. But that doesn't change what behaviors are. It is true that the model as a whole also specify the denotational semantics for functions that creates new behaviors from other behaviors. But those are part of the model as a whole they are not part of the semantics of behaviors.

lift is not the only way to combine behaviors, it`s just a special case of combining.

Please tell me about the other ways?

i can use the same feature to combine arbitrary behavior in arbitrary expressions like Conal did in his CFRP paper

All the constructed behaviors in your example are created with lifting. The code uses Fran and Fran provided implicit lifting of functions and constants. We can make the lifting explicit:

wiggle = t => lift1 sin (constantB PI `lift2 (*)` t)
wiggleRange = (lo, hi) => t => (constantB lo) `lift2 (+)` (constantB hi - low) `lift2 (*)` (lift1 (+ 1) wiggle(t)) `lift2 (/)` (constantB 2)

I probably slightly screwed up the wiggleRange above but you get the idea. If one was doing the lifting manually from the beginning one would of course not do it like shown above.

the returned function acts as a callback that handles the incoming event by publishing it in the App.

At least with Snabbdom doing that is not necessary. Instead of returning a function it could publish directly with publish and then do:

app.view = () =>
  h('div', {style: countStyle}, [
    h('button', { on: {click: [app.publish, 'dec$'] } }, '–'),
    h('div', {style: countStyle}, doubleCounter()),
    h('button', { on: {click: [app.publish, 'inc$'] } }, '+'),
  ]);

That will invoke publish with the respective string on button click. Now, that might be a problem because you're using methods. Have you considered using functions instead of methods? Methods are really annoying when doing functional programming in JavaScript.

from snabbdom.

paldepind avatar paldepind commented on May 9, 2024

Btw, if you are unfamiliar with the automatic lifting that Fran uses (it is only mentioned briefly in the Functional Reactive Animation paper) Conal explains it in greater depth in "Functional Implementations of Continuous Modeled Animation".

from snabbdom.

yelouafi avatar yelouafi commented on May 9, 2024

Well. The semantic of a behavior is just a function of time. Do you disagree with that?

No. But that'not all the semantics of behaviors. But anyway, i think arguing on terminology is not that important. Maybe i misused some terms, maybe you did, the idea is that the CFRP model allows a rich expressiveness and that automatic tracking brings us closer to this richness

Please tell me about the other ways?

how do you define this in terms of lift

beh : Behavior  (Behavior a)
res = t => beh(t-1)(t-2)

At least with Snabbdom doing that is not necessary. Instead of returning a function it could publish directly with publish...That will invoke publish with the respective string on button click

That's a nice feature. can this work with multiple parameters ? sometimes you need to pass contextual information to the publish function, like on : { click: app.publish('remove$', elm) }

BTW don't you find [app.publish, 'dec$']. instead of app.publish('dec$') less intuitive, this is just a subjective feeling. But probably your method performs slightly better as we don't have to create the publish function on view evaluation.

Btw, if you are unfamiliar with the automatic lifting that Fran uses (it is only mentioned briefly in Functional Reactive Animation paper) Conal explains it in greater depth in "Functional Implementations of Continuous Modeled Animation".

i've looked briefly at this paper sometimes ago. I think it definitely deserves a thorough reading

from snabbdom.

paldepind avatar paldepind commented on May 9, 2024

No. But that'not all the semantics of behaviors. But anyway, i think arguing on terminology is not that important. Maybe i misused some terms, maybe you did, the idea is that the CFRP model allows a rich expressiveness and that automatic tracking brings us closer to this richness

This is a quote from the push-pull paper: "Semantically, a (reactive) behavior is just a function of time". Then later on the paper separately specifies the semantics of other functions that maps into the semantic domain of behaviors.

how do you define this in terms of lift

You can't do that with Fran either. Actually the code makes no sense. First of all you passing a behavior t - 1 to a behavior, secondly you would have to use at to get the value of a behavior at a certain time (like this b at t) and thirdly you can't have a behavior of behaviors (semantically you can but it is not very useful).

It sounds to me like you think classic FRP allows one to use behaviors everywhere as if they where plain values and then have the implementation do it's job. That is not the case.

That's a nice feature. can this work with multiple parameters ? sometimes you need to pass contextual information to the publish function, like on : { click: app.publish('remove$', elm) }

It only works for one argument. If you need the target element from the event object you will have to pass an event listener.

BTW don't you find [app.publish, 'dec$']. instead of app.publish('dec$') less intuitive, this is just a subjective feeling. But probably your method performs slightly better as we don't have to create the publish function on view evaluation.

I'm used to the syntax, so no, nut really. I on the other hand find in unintuitive that app.publish('dec$') doesn't actually publish an event but instead returns a function that does.

i've looked briefly at this paper sometimes ago. I think it definitely deserves a thorough reading

It is not bad but is mostly useful if you're implementing FRP in Haskell or a similar language.

from snabbdom.

yelouafi avatar yelouafi commented on May 9, 2024

Actually the code makes no sense. First of all you passing a behavior t - 1 to a behavior, secondly you would have to use at to get the value of a behavior at a certain time (like this b at t)

i don't get it, why are not we allowed to do that. AFAIK time transforms are part of the CFRP (am i missing something). In a first implementation of my prototype using explicit time parameter i used that to implement the scan function.

and thirdly you can't have a behavior of behaviors (semantically you can but it is not very useful).

I think rather it's very useful : the simple experssion beh(t)(t) denotes the switchToLatest operation.

It sounds to me like you think classic FRP allows one to use behaviors everywhere as if they where plain values and then have the implementation do it's job. That is not the case.

Exact, that's what FRP states: "Behaviors are first class values". And this is one of the semantic problems of CFRP, you can define nonsensical behaviors that can depend on future and breaks causality. But my point is that behavior expressions, even without time transforms, are much simpler and powerful: for example in my prototype you can simply write beh()() to get the dynamic switching mechanism. i can even write beh()()() although i can't figure out of use case for this

I on the other hand find in unintuitive that app.publish('dec$') doesn't actually publish an event but instead returns a function that does.

i'm surprised to hear that from someone used to currying :)

from snabbdom.

yelouafi avatar yelouafi commented on May 9, 2024

It sounds to me like you think classic FRP allows one to use behaviors everywhere as if they where plain values and then have the implementation do it's job. That is not the case.

it seems i misunderstood the things. re-reading the original paper of FRAN, i effectively noticed that there is no example where behaviors are accessed directly as functions of time but only through automatic lifting. i was thinking that the model allows one to write arbitrary expressions using behaviors as simple functions and apparently this is not the case.

from snabbdom.

paldepind avatar paldepind commented on May 9, 2024

it seems i misunderstood the things. re-reading the original paper of FRAN, i effectively noticed that there is no example where behaviors are accessed directly as functions of time but only through automatic lifting.

It is an easy thing to miss. The paper only mentions it very briefly and the automatic lifting was done exactly to make it look like one was manipulating behaviors directly.

i was thinking that the model allows one to write arbitrary expressions using behaviors as simple functions and apparently this is not the case.

That is the goal. But that the problem is that when you do that creating time and space leaks is just too easy. Fran returns a new behavior when you access a behavior hoping that you will use the new instead so the old can be garbage collected.

from snabbdom.

ismail-codar avatar ismail-codar commented on May 9, 2024

Another todomvc implementation with snabbdom here https://github.com/ismail-codar/snabbdom/tree/master/examples/todomvc (Angular2 todomvc code based)

And benchmark result: snabbdom performance result

Snabbdom is fast but benchmark result depends imlementation and other libraries. My code using zone.js for detecting user events and automatic re-rendering. https://github.com/ismail-codar/vdom-patch-live very usefull library.

This todomvc implementation can be %10-20 speed with some optimization. For example todo items is array and finding an item requires loop. Object keys can be eligible. Or manuel re-renderin rather then vdom-patch-live. Thunks may be tried etc.

from snabbdom.

paldepind avatar paldepind commented on May 9, 2024

@ismail-codar Very cool! And you're not using thunks?

How do you do those pretty graphics?

from snabbdom.

ismail-codar avatar ismail-codar commented on May 9, 2024

@paldepind Yes thunks not used and nothing for other optimizations. Graphics created using the google visualization api in the original repo. https://github.com/ismail-codar/todomvc-perf-comparison/blob/gh-pages/todomvc-benchmark/resources/manager.js#L144-L182

You can test yourself. https://github.com/ismail-codar/todomvc-perf-comparison#runing-on-your-locale

from snabbdom.

yelouafi avatar yelouafi commented on May 9, 2024

hi @paldepind it's been a while. the results of @ismail-codar where a bit surprising to me, the difference with my results is so high.

I think the performance boost is mainly due to debounced updates. the vdom-patch-live used by @ismail-codar is based on vdom-live which is said to "use requestAnimationFrame to queue and debounce re-renders". To verify that i modified unReact to debounce patch calls within requestAnimationFrame. here are the new results

sampleresults

Note i still redo the entire wasteful view recomputation on each event and i dont use thunks. only the patch operation is delayed.

BTW worked same small projects with snabbdom. I must say i really like it, the different hooks are pretty handy;

benchmark source is here https://github.com/yelouafi/todomvc-perf-comparison

from snabbdom.

paldepind avatar paldepind commented on May 9, 2024

@yelouafi Thank you for sharing these results! They are very interesting. I'm glad you enjoy using Snabbdom.

from snabbdom.

TylorS avatar TylorS commented on May 9, 2024

I don't mean to shamelessly plugin here, but I have a DBMON benchmark for the framework that I'm building, which uses Snabbdom for it's view rendering. Thought this might be a decent place to share it.

http://tylors.github.io/js-repaint-perfs/motorcycle/

from snabbdom.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.