HeinrichApfelmus / reactive-banana

Library for functional reactive programming in Haskell.
https://wiki.haskell.org/Reactive-banana
522 stars 71 forks source link

instance Monad Behavior #101

Open ocharles opened 9 years ago

ocharles commented 9 years ago

After spending time working with frpnow, having an instance of Monad for Behavior opens up a lot of new doors, to the point where it's hard to work without it. With both frpnow and reflex having support for this, I'm hoping that we can find a solution in reactive-banana, too.

HeinrichApfelmus commented 9 years ago

Semantically, a Monad instance for Behavior should be no problem, but the trouble is that it's difficult to do efficiently in a push-based implementation. (A pull-based implementation probably won't see a significant performance decrease, most likely because it's doing a lot of needless work anyway.)

The problem appears when looking at the changes of a Behavior. Every time you use the monadic bind, (>>=), the result will have to merge the changes from the first argument with the changes of the Behavior calculated from the second argument. However, monads usually come with the implicit assumption that the monadic bind is cheap and can be used excessively. I think this is not the case for Behavior here, that's why I had decided against attempting to implement it.

Also, looking at the type of switchB, you will see that it is actually a specialized version of the monadic bind function, namely if you restrict the latter to Behaviors that were generated from a pair (b, Event b) with stepper.

By the way, as soon as you drop the requirement of having a changes function for the purpose of connection to the outside world (and only for that), then a Monad instance is easy. In fact, what you get is precisely the Moment monad! Chances are that many constructions that seem to require a monad instance for Behavior can be achieved with Moment.


That said, I'm happy to explore what benefits a Monad instance for Behavior would entail. Could you give examples where you feel that it's hard to do without?

ocharles commented 9 years ago

You know, you're right that I might just be wanting the Moment monad. The last time I was using reactive-banana I was building a GHCJS client library. When I moved over to play with frpnow, I thought that due to the fact that I had a Monad instance for Behavior I could use something like WriterT HTML Behavior a as a DSL for building HTML trees (and get a lucid-like API out of it). It turns out you need more than just Behavior - because you need to build event handlers too. In the end, I came to WriterT (Behavior HTML) Now a, and that is certainly translatable to reactive-banana (I'm trying it in excitement now!).

However, there is one place where I'm stacking Behavior under Writer in that project - building the CSS styles for an element. Building CSS styles is pure, so it can happy work entirely within WriterT CSS Behavior. I can probably work around this though with Free Behavior, and then interpreting that free monad in the context of Moment - we'll see.

That said, I still think we should try really hard to find this instance, and there is one benefit that is hard to ignore: the syntactic benefit.

Syntactically, working with do notation is considerably more readable when you have multiple behaviors. Maybe applicative do will solve this, but it's not out yet. This is also why I was exploring an "idiom brackets" extension for GHC, which I ultimately stopped working on.

HeinrichApfelmus commented 9 years ago

That said, I still think we should try really hard to find this instance, and there is one benefit that is hard to ignore: the syntactic benefit.

Well, I fear it will come at a price, though. For example, in hindsight, one of the first uses of Applicative Functors was in the context of parser combinators. Swierstra et al had to take a step back from the monadic interface in order to devise combinators that can parse LL(1) grammars efficiently [pdf]. They write:

5.2 Problems with Monad-based Formulations Unfortunately the techniques which we have used do not easily extend to the monad-based combinator parsers[9]. Due to the monadic formulation the dynamic and the static values are tupled together, and thus cause the evaluation of the parser construction over and over again when parsing.

While it may be possible to implement a Monad instance, doing so may be prohibitively expensive.

By the way, the problem of time leaks in FRP falls into a similar category: Yes, it's possible to implement both

stepper :: a -> Event a -> Behavior a
switchB :: Behavior a -> Event (Behavior a) -> Behavior

with these types, and it's very convenient syntactically, but it's impossible to do so efficiently. One has to give up one of the type signatures, for instance by using Moment for the result of stepper, or by adding a phantom type to switchB. Or by doing something else, what frpnow does, but which I haven't looked at closely yet.


Could you give a concrete example of the API that a WriterT CSS Behavior implementation could provide? I.e. what does it look like to the user? I take it that the idea is to be able to express time-varying CSS values, but I have to admit that I'm not familiar with francium.

ocharles commented 9 years ago

Will get to your other comments later today, but

Could you give a concrete example of the API that a WriterT CSS Behavior implementation could provide?

Here's an example - it's all very work-in-progress at the moment.

                  div_ (style_ (do CSS.position CSS.fixed
                                   CSS.height (100 CSS.@@ CSS.pct)
                                   CSS.width (100 CSS.@@ CSS.pct)
                                   CSS.zIndex 9
                                   CSS.top (0 CSS.@@ CSS.px)
                                   CSS.left (0 CSS.@@ CSS.px)
                                   "opacity" -: fmap (fromString . show) overlayOpacity
                                   "background-color" -: "rgba(0,0,0,0.541)"
                                   "transform" -: "translateZ(0px)"))
                       mempty)

(Here you can see why I'm proposing let import CSS on the Haskell cafe ;))

In this formulation, there is are Num and FromString instances for Behavior so I can use literals. But also note that overlayOpacity :: Behavior Double, and I map that into a string and assign that to the opacity CSS property.

Working in the Moment monad might be best here though, as that means I can observe the changes to each individual property and only update that (otherwise I have to do some sort of diff whenever there is a change). This is all very work in progress, and I don't want to imply that I'm asking for Monad Behavior just to address my API (I also completely understand that such an undertaking must be justified!)