Open ocharles opened 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?
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.
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
.
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!)
After spending time working with
frpnow
, having an instance ofMonad
forBehavior
opens up a lot of new doors, to the point where it's hard to work without it. With bothfrpnow
andreflex
having support for this, I'm hoping that we can find a solution inreactive-banana
, too.