reactive-banana icon indicating copy to clipboard operation
reactive-banana copied to clipboard

Combinator for applying a function in an event to a value in a behavior and making a new Event

Open Kritzefitz opened this issue 9 years ago • 8 comments

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

Kritzefitz avatar Apr 15 '15 14:04 Kritzefitz

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.

HeinrichApfelmus avatar Apr 15 '15 17:04 HeinrichApfelmus

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.

Kritzefitz avatar Apr 15 '15 20:04 Kritzefitz

I currently think that

flip ($) <$> myValueBehavior <@> myFunctionEvent

is the best way to go about it. But thanks again for the suggestion!

HeinrichApfelmus avatar Sep 14 '15 13:09 HeinrichApfelmus

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.

ocharles avatar Sep 14 '15 14:09 ocharles

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.

HeinrichApfelmus avatar Sep 14 '15 18:09 HeinrichApfelmus

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 avatar Dec 30 '15 18:12 archaephyrryx

@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 avatar Jan 04 '16 19:01 HeinrichApfelmus

@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

archaephyrryx avatar Jan 08 '16 23:01 archaephyrryx