reactive-banana
reactive-banana copied to clipboard
Combinator for applying a function in an event to a value in a behavior and making a new Event
I would like to propose an operator similiar to <@>
. Instead of applying the function from the behavior to the value in the event, the function from the event is applied to the value of the behavior. The operator would thus have the following type:
foo :: Event t (a -> b) -> Behavior t a -> Event t b
Currently this can be accomplished by doing something like this:
myFunctionEvent :: Event t (a -> B)
myFunctionEvent = <...>
myValueBehavior :: Behavior t a
myValueBehavior = <...>
myAppliedEvent :: Event t b
myAppliedEvent = fmap (flip ($)) myValueBehavior <@> myFunctionEvent
The fmap (flip ($)) myValueBehavior
may seem a bit cryptic (that's why I'm proposing this new operator). Its purpose is to create a new behavior that contains the function ($ myValue)
, which will the be applied to the function in the event, ending up as myFunction $ myValue
.
With the proposed operator myAppliedEvent
could be shortened to this:
myAppliedEvent = myFunctionEvent `foo` myValueBehavior
To be honest, I'm not too keen on creating a new operator for situations that are not very common. Do you have any code examples where this operator is useful?
In your particular example, you can use <$>
to save a few parentheses.
flip ($) <$> myValueBehavior <@> myFunctionEvent
I think this is reasonably clear. If anything, it may be more useful to give a better name to flip ($)
, because this combination appears in other contexts as well.
The version you provided does indeed look alright. I think it would be ok to go with it. But because you asked for a use case, here a simplified version of my use case:
I have a behavior in the following form:
someBehavior :: Behavior t a
someBehavior = accumB [] appliedEvent -- I will get to the definition of appliedEvent later
Now I want to modify my behavior through the following event:
someEvent :: Event t (b -> a -> a)
someEvent = <...> -- Some input, probably something fromAddHandler
That b
is a value from the data graph that influences how the value in someBehavior
is changed. So the current way to define appliedEvent
would be this (assuming that someOtherBehavior
provides the required b
):
appliedEvent :: Event t (a -> a)
appliedEvent = flip ($) <$> someOtherBehavior <@> someEvent
I don't know how common this use case is. So it's up to you to decide whether to make a new operator for this.
I currently think that
flip ($) <$> myValueBehavior <@> myFunctionEvent
is the best way to go about it. But thanks again for the suggestion!
I can see how it could be problematic if you had a function where one argument from an Event
was "in the middle". For example, consider some contrived function...
foo :: A -> B -> C -> D
If you have
a :: Behavior A
b :: Event B
c :: Behavior C
it is quite hard to form Event D
:
ev = (\a' c' b' -> foo a' b' c') <$> a <*> c <@> b
which is pretty weird!
My preference would be that <@>
is actually a type-classed operator, but I'd have to give this a bit more thought.
That said, it's all hypothetical - I've been nervous about this for a while, but it hasn't actually come up in practice.
Usually, I tend to rewrite the definition of foo
, or just use the seemingly complicated form. The Animation.hs
example has some expressions where I'm not 100% happy either, but I think it's ok once you get used to the "dollar, star, at" pattern.
The trouble with polymorphic <@>
is that it may lead to ambiguities — though I'm not sure if that happens here. It's probably not worth the cost, but I'll leave the issue open in case someone finds a solution.
This might not be entirely practical with the specific implementations of Event and Behavior, but I have a mockup of a <@>
operator and a complementary <#>
operator that work with dummy Behavior and Event types (which are just value wrappers in my example). The exact instance implementation of <@>
and <#>
should not be much harder than the current <@>
implementation as far as I can guess, but this may not be practical for whatever reason.
{-# LANGUAGE TypeFamilies #-}
import Control.Applicative ((<$>))
class Functor k => FRDual k where
type Dual k :: * -> *
(<@>) :: (Dual k) (a -> b) -> k a -> k b
(<#>) :: k (a -> b) -> (Dual k) a -> k b
data Behavior x = B { b :: x }
data Event x = E { e :: x }
instance Functor Behavior where
fmap f (B x) = B (f x)
instance Functor Event where
fmap f (E x) = E (f x)
instance FRDual Behavior where
type Dual Behavior = Event
(E f) <@> bx = f <$> bx
bf <#> (E x) = ($x) <$> bf
instance FRDual Event where
type Dual Event = Behavior
(B f) <@> ex = f <$> ex
ef <#> (B x) = ($x) <$> ef
@archaephyrryx So, essentially, you are proposing to give a name to this operator.
(<#>) :: Event (a -> b) -> Behavior a -> Event b
ef <#> bx = flip ($) <$> bx <*> ef
I'm not sure if this particular naming scheme is a good idea. The operator (#)
is used in the diagrams library as a reverse function application, so if we take that as a "standard" (which we don't have to), then I would expect the type
(<#>) :: Behavior a -> Behavior (a -> b) -> Behavior b
instead. In particular, I would expect that the function argument comes second.
I don't think that the idea with overloading (<@>)
works. It's not possible to write combinators with types
bar :: Event (a -> b) -> Behavior a -> Behavior b
bar2 :: Behavior (a -> b) -> Event a -> Behavior b
that do the right thing (= are total).
@HeinrichApfelmus
I agree with you that <#>
is a rather unfortunate name, though I chose it just for the purposes of the mock-up. I also figured that for Behavior, an FRDual instance might not be possible (or, even if it technically could be done, it wouldn't necessarily conform to the underlying models). In any case, this was more of a proof of concept than a true solution, but I think I might have a revision that addresses some of the issues:
{-# LANGUAGE TypeFamilies #-}
import Control.Applicative ((<$>))
infixl 4 <@>
class Functor k => FRDual k where
type Dual k :: * -> *
(<@>) :: k (a -> b) -> (Dual k) a -> Event b
data Behavior x = B { b :: x }
data Event x = E { e :: x }
instance Functor Behavior where
fmap f (B x) = B (f x)
instance Functor Event where
fmap f (E x) = E (f x)
instance FRDual Behavior where
type Dual Behavior = Event
(B f) <@> ex = f <$> ex
instance FRDual Event where
type Dual Event = Behavior
ef <@> bx = flip ($) <$> bx <@> ef