funkia / hareactive

Purely functional reactive programming library
MIT License
339 stars 29 forks source link

What else people call "behavior" in FRP :) #28

Open dmitriz opened 7 years ago

dmitriz commented 7 years ago

Might be interesting to take into consideration:

A Behaviour is an EventStream + Some value in memory.

http://stackoverflow.com/questions/1028250/what-is-functional-reactive-programming/1033066#1033066

Actually quite useful to describe e.g. the user's inputValue as you want both the value and to react to its changes.

paldepind commented 7 years ago

A behavior can't be explained as an event stream and a value in memory. A behavior is more powerful since it can change infinitely often.

dmitriz commented 7 years ago

@paldepind I've been thinking about the behavior model in the context of un, where I am using external renderer to update the dom, and it looks like there is some implementation difficulty with infinitely often changing values, as you need to know somehow when to run the renderer.

Having the change events included in the behavior solves this problem nicely that you can simply run it on every event. But if you throw away the events, how do you know when to update the dom?

Running some sort of request animation frame and then checking for changes every time for every behavior feels like replacing push by polling, which goes against the point of RFP :)

It is perhaps useful with animations, but with typical discrete states like selections, text input, expand-collapse, you always have their change events for free, are you throwing them away and then listening again for the value changes, in order to know when to update? Am I missing something here?

paldepind commented 7 years ago

Hareactive uses a combination of push and pull. Behaviors that changes at discrete points in time (for instance if they're based on browser events) are push and behaviors that changes continuously are pulled.

When you observe a behavior in Hareactive you'll be told if the behavior changes at discrete points in time or if it changes infinitely often. If the behavior changes infinitely often it is the observer's job to pull when it suits him.

You can see how we do it in Turbine here. If the behavior is continuous we update on every frame and if it pushes values we update whenever that happens.

dmitriz commented 7 years ago

Hareactive uses a combination of push and pull. Behaviors that changes at discrete points in time (for instance if they're based on browser events) are push and behaviors that changes continuously are pulled.

This sounds like you are splitting behaviors into 2 different kinds - discrete changing to be pushed, and continuous to be pulled.

Isn't the first effectively equivalent to having the separate memory stream type? :)

It seems to me, using the memory stream for both streams and discrete behaviours might lead to simpler api and easier usage, like not having to check for pulling.

Am I missing something?

paldepind commented 7 years ago

This sounds like you are splitting behaviors into 2 different kinds - discrete changing to be pushed, and continuous to be pulled.

Correct. But, we are going this internally precisely so that users don't have to. Complexity is moved away from the API and into hidden implementation details.

Isn't the first effectively equivalent to having the separate memory stream type? :)

Indeed. Expect that a behavior can switch between the two. So it might pull now but switch to pull a minute later.

It seems to me, using the memory stream for both streams and discrete behaviours might lead to simpler api and easier usage, like not having to check for pulling.

Easier for who? :smile: A normal user doesn't have to call observe. Hareactive is supposed to be as simple as possible for users. Users shouldn't have to worry about whether or not a behavior is push or pull.

Part of being simple means that both behavior and stream has a simple and precise semantic meaning. It also means that implementation details are hidden. We have a clear distinction between behaviors and streams. Adding an extra type that provides no extra power only seems to make the API more complex and muddy the distinction between behaviors and streams.

dmitriz commented 7 years ago

Indeed. Expect that a behavior can switch between the two. So it might pull now but switch to pull a minute later.

You mean from pull to push? So as the library consumer, how do I figure which one is it?

Easier for who? 😄 A normal user doesn't have to call observe. Hareactive is supposed to be as simple as possible for users. Users shouldn't have to worry about whether or not a behavior is push or pull.

Then how can I reliably attach my custom renderer to a behavior? It is not clear to me how this detail can be hidden from the consumer.

Like here I can take arbitrary renderer and subscribe to the event stream: https://github.com/dmitriz/un/blob/master/index.js#L30

But if the vnode arrives as behavior and there are no events, can I still use it in a way as simple as that?

I must be looking confused now 😕

paldepind commented 7 years ago

You mean from pull to push?

Yes :+1:

So as the library consumer, how do I figure which one is it?

I probably didn't make myself clear enough. As a library consumer, you have to worry about push vs pull. But only so that regular users don't have to. To do that one uses the observe method. I've just added JSDoc comments to the observe function. You can see it here.

But, let me warn you. If you have a behavior of vnode's and the behavior is pulling then you have to create a new vdom, diff it and patch it 60 times per seconds which may be problematic. That is one of the reasons why we don't use virtual dom in Turbine.

dmitriz commented 7 years ago

Thank you for the explanation.

Unfortunately, if I am to be honest, that makes the behavior in this form harder to use. Even for any normal user who would like to use it like flyd, where you need to push values and subscribe to changes. There isn't really a lot you need for "normal use" but these few core methods are nice to have being simple. I like the simplicity of flyd where functions are small with few arguments and things just work. :)

A possible slightly different type separation based on the pull/push would seem easier to use. The one type will always work with pulling, the other with pushing, you don't have to think and won't ever need to worry about switching. That would also make also the functions shorter (the number of arguments is a big usability concern).

It might not require major changes, just some convenience methods to wrap around.

paldepind commented 7 years ago

Unfortunately, if I am to be honest, that makes the behavior in this form harder to use.

It makes it harder for people who needs to call observe. I.e. it makes it harder for library writers.

A possible slightly different type separation based on the pull/push would seem easier to use.

Such a separation would make it easier for library writers. Harder for normal users. It would expose implementation details and break the behavior abstraction.

Normal should never call observe. What you suggest would make Hareactive harder to use and more complex for average users.

dmitriz commented 7 years ago

That probably means I was trying to use it the wrong way :) My idea was to try to use it as the main stream library in the un, which may be not the intended way indeed. I may need to understand the library better to see how and whether it was wrong...