Open yelouafi opened 8 years ago
Sorry for the delay.
No problem at all. There is no hurry and I actually have exams at the moment so I don't have much time either.
I'd be happy to take part on this but unfortunately I cant find more time to work on another project actually. But I'll be glad to discuss any issue with you on this repo. I could also contribute later If I find the time
Being available for discussion is to me the most important thing. In a way coding is easy, but figuring out what to code is the hard part. Having knowledgeable people around that will share opinions is very beneficial in that regard.
I think it's important to define a denotational model before starting the implementation ( As Conal mentioned there are 2 fundamental requirements to define an FRP system: Precise denotation and Continuous time.). It's not required to be a formal model with denotational semantics. One can do it simply with a pure language (Haskell) or even with a pure subset of JS to TypeScript. For example take a look at reactive banana model.
I agree. I however had not though of using an actual implementation as the model. But I do in fact have a TypeScript implementation of the semantics from the push and pull paper. It is entirely pull based and pure. I.e. the code is as close as possible to the semantics. I've written this FRP implementation because I want to use it in a blog post I intent to write about classic/original FRP. But I assume it could be useful for this purpose as well.
IMO, it'd be better to take the semantic model described in this paper (the model is described in the first 4 pages) instead of the Classic model. It uses standard type classes (Functors, Applicatives, ...). It's not necessary to follow the implementation defined in the rest of the paper.
That is exactly the semantics we're aiming for. I do however consider it to be classic FRP. Maybe that is a misunderstanding but to me it's just classic FRP in a modernized form. I consider non-classic FRP to be Arrow-based FRP and other more different FRP systems. I might be wrong but it seems consistent with how Conal uses the "classic" word on his blog.
Thank you for the link to the paper. I had never seen it before but I'm going to take a look at it. I will respond again later after having read it.
Hello @yelouafi. Thank you for your tweet. I've realized that the ideas you mention above are similar to the ones discussed in the blog post. This is a reply to your post above, your blog post and an update on Hareactive.
I think you did a really great job at explaining the meaning of data-driven. The ideas from the paper was easier to understand in your blog post. Especially the unpacking of what Conal Elliott meant that implementation dependencies revert logical dependencies helped me. In the paper this important insight was just stated without any explanation.
One thing I didn't realize on my first read of the blog post is that there is no dependency graph in the implementation. That is remarkable. As far as I can tell what is typically done with propagation through a dependency graph is simply handled by function composition in data-driven.js
. But, will that not lead to unnecessary computations in some situations? For instance if a have long dependency chain like const a = smap(f, smap(g, smap(h, source))); const b = samp(i, a)
and then user react
on both a
and b
wont the application of f
, g
and h
be computed twice when source
triggers a notification? I.e. there is no sharing? If so, then the approach is a bit like a more elegant implementation of cold observables
as found in Rx or Most. They don't maintain a dependency graph either and also don't have sharing.
My biggest question after reading the blog post and the paper is: how do you think the approach compares to FRP? Sources seems to have similarities with behaviors but without the support for continuity? I can see the benefit in that the implementation is elegant and maps well to traditional GUI systems. Also the way glitches are avoided is neat. It seems to me like that from a users point of view the end result is somewhat closer to Rx than to classic FRP?
In Hareactive we are currently using an approach to FRP which is heavily inspired by frpnow and the paper describing it Practical Principled FRP. The semantics are very similar to those in Conal Elliotts push and pull paper but with two major benefits. First they eliminate the space and time leaks that Conal Elliott never really addressed (as far as I know). Second the paper presents a clean way of combining FRP with side-effects in a powerful way. So without sacrificing the power of classic FRP it solves it's main problems. Both of these features are achieved by introducing a monad called Now
. The now monad represents a specific moment in time. This makes it possible to use stateful combinatiors in a pure way (i.e. without being dependent on the entire history of a behavior). It also provides an opportunity to executed IO
-computations. We are using an IO-monad with generator based do-notation that looks a lot like redux-saga and mostly the same benefits.
We are currently building a framework Funnel on top of Hareactive that I think is very promising. It actually has a few similarities to the GUI system that Conal Elliott presents in the draft paper. This post is already quite long but in case you're interested I can describe the framework and some of the main benefits of our approach.
@paldepind Thanks for your feedback!
But, will that not lead to unnecessary computations in some situations?
Yes you're right. I can think of 2 possible solutions:
scombine
the Getter will always return the latest computed result (without checking the inputs as in the first solution) unless it's marked dirty by a change Notification. It'll perform slightly better than (1) but will shift us toward a more awkward dependency graph implementation: We'll have again those cascaded change notifiers and scombine
/smap
have to handle lazy subscriptions and glitches manually.If so, then the approach is a bit like a more elegant implementation of cold observables as found in Rx or Most.
I think it depends on the particular instance of the Source. It seems to me like Source is a lower level concept which can support both cold and hot observables. For example in the post the Behavior implementation (which is very hacky) is like a hot observable because it subscribes to the input events at the creation.
Also, as mentioned in the paper, Source
could be generalized to any pair (Monoid, Applicative)
which opens possibility to extend it beyond IO. In the paper the author used type composition ((,) Notifier ◦ Extractor
) to automatically derive the definitions. More explicitly it'll be
type DataDriven n g = (n, g)
type Getter = IO
type Notifier = IO () -> IO ()
type Source = DataDriven Notifier Getter
instance (Monoid n, Applicative g) => Applicative (n, g) where
pure = (mempty, pure)
(n1, g1) <*> (n2, g2) = (n1 `mappend` n2, g1 <*> g2)
My biggest question after reading the blog post and the paper is: how do you think the approach compares to FRP? Sources seems to have similarities with behaviors but without the support for continuity?
Hmm. Yes (and maybe no). At first glance Sources can't support continuous behaviors because we can't notify change of a value that varies continuously over time (like sin(time)
). But, wouldn't be possible to embed continuous behaviors inside Sources like Conal did in his push-pull paper where Behaviors are represented as phases (the outer/reactive part) of simple behaviors (the inner/non reactive/continuous part) ?
maybe something like (I'm hackily borrowing definitions from the push-pull paper)
Cont a = Time -> a -- continuous
Behavior a = Source (Cont a) -- discretes phases of nested continuous behaviors
--and
Cont a = K a -- a constant value
| Fun T -> a -- a continuously changing value
Or perhaps is this already the model you adopted. I'll have to check the FRPNow paper (maybe IO
and Now
play a similar role by introducing the start time parameter ? (via injecting the Behavior into the Monadic world of IO/Now). In Data-driven the signature of Getter is IO a
so with Behaviors we'll have IO (Cont a)
Btw. I noticed that you've added a pure semantic model; that's great. Did you consider adding the Monoid instance to Futures?
Hi @paldepind. I opened this issue as a feedback to your mail. Sorry for the delay.
I'm going here to share some conclusions on my earlier experiments and my readings (I'd be happy to take part on this but unfortunately I cant find more time to work on another project actually. But I'll be glad to discuss any issue with you on this repo. I could also contribute later If I find the time)
I think it's important to define a denotational model before starting the implementation ( As Conal mentioned there are 2 fundamental requirements to define an FRP system: Precise denotation and Continuous time.). It's not required to be a formal model with denotational semantics. One can do it simply with a pure language (Haskell) or even with a pure subset of JS to TypeScript. For example take a look at reactive banana model.
IMO, it'd be better to take the semantic model described in this paper (the model is described in the first 4 pages) instead of the Classic model. It uses standard type classes (Functors, Applicatives, ...). It's not necessary to follow the implementation defined in the rest of the paper.
As for the implementation, In my experiments I was mainly focused on reactive behaviors. Traditionally (in non pure languages like JS, Java or Scala) they are implemented on top of Event Emitters & dependency graphs but as you mentioned coming up with a memory-leak free implementation is quite challenging without native weak maps. in my cfrp lib (which TBH is really far from the classic FRP) I distinguished 2 types of behaviors
Computed behaviors can be evaluated lazily, since they entirely depend on their sources.
Root behaviors, OTOH, are eager by nature, they must be 'updated' on each event occurrence. We can't evaluate them lazily unless we keep the entire event history since the last evaluation (which may cause serious space/time leaks, this was the issue with my pull based implementation unReact). The issue is to prevent memory leaks, especially with dynamic behaviors: A behavior will typically subscribe to its source events, but how it should unsubcribe from them?
There is another interesting alternative to traditional dependency graphs and it uses pure FP concepts to build a glitch free dependency model. The solution was presented by Conal Elliott in this paper. Basically it defined a
Source
of value as a couple of Extractor (to extract a value) and a Notifier (to notify changes)Then define computation on
Source
values using Functors & Applicatives. I found this solution more elegant and also simpler than traditional dependency graphsi made a rough JS port (abstracting over IOs). The solution was part of Conal's attempt to build a push/pull implementation of FRP. But was eschewed in favor of an IO-free implementation (sort of, it used blocking threads to implement events on top of Futures). AFAIK this was the last Conal's implementation.