Event handling in Netwire compared to conventional FRP frameworks
Asked Answered
O

2

9

Most Haskell FRP frameworks like AFRP, Yampa and Reactive-banana make a difference between continuous time-varying functions and discrete ones. Usually they call them behaviors and events.

One exception is Netwire, which uses an inhibition monoid to model events. What are pros and cons of such an approach?

In particular, I'm interested in application of FRP to robot controlling. For example, this paper http://haskell.cs.yale.edu/?post_type=publication&p=182 show a way to encode a task and HSM abstractions in FRP using events. Can this be directly translated to Netwire?

Odious answered 1/2, 2013 at 20:24 Comment(0)
O
2

After some trials I've implemented the behavior I needed. Basically, You write a custom inhibitor type which catches the concept of events you need. In my case it was

data Inhibitor = Done | Timeout | Interrupt deriving Show

Done means normal finishing and the rest constructors signal some kind of an error.

After it, you write any custom combinators you need. In my case I needed a way to stop computations and signal a error further:

timeout deadline w | deadline <= 0 = inhibit Timeout
                   | otherwise = mkGen $ \dt a -> do
                       res <- stepWire w dt a
                       case res of
                         (Right o, w') -> return (Right o, timeout (deadline - dt) w')
                         (Left e, _)  -> return (Left e, inhibit e)

This is a variant of switchBy which allows you to change the wire once. Note, it passes the inhibition signal of a new wire:

switchOn new w0 =
    mkGen $ \dt x' ->
        let select w' = do
                (mx, w) <- stepWire w' dt x'
                case mx of
                  Left ex -> stepWire (new ex) dt x'
                  Right x -> return (Right x, switchOn new w)
        in select w0

And this is a variant of (-->) which catches the idea of interrupting the task chain.

infixr 1 ~>

w1 ~> w2 = switchOn ( \e -> case e of
                         Done -> w2
                         _ -> inhibit e
                    ) w1
Odious answered 6/2, 2013 at 5:37 Comment(0)
P
10

The advantage of events as potentially inhibited signals is that it allows you to encode most even complicated reactive formulas very concisely. Imagine a switch that displays "yes" when pressed and "no" otherwise:

"yes" . switchPressed <|> "no"

The idea is that switchPressed acts like the identity wire if its corresponding event occurs and inhibits otherwise. That's where <|> comes in. If the first wire inhibits, it tries the second. Here is a hypothetical robot arm controlled by two buttons (left and right):

robotArm = integral_ 0 . direction
direction =
    ((-1) . leftPressed  <|> 0) +
    (1    . rightPressed <|> 0)

While the robot arm is hypothetical, this code is not. It's really the way you would write this in Netwire.

Provide answered 1/2, 2013 at 22:10 Comment(4)
(-1) and (+1) have different types; but I am not sure what type you intend for them to have, so I am loath to edit your code.Penult
Does it mean that all the events must have the same type? Namely, e in Wire e m a b? Speaking about yours example, What if I want to react both on key press by moving the arm in one axis and on stylus event, which gives me two coordinates?Odious
Indeed, write 1 instead of (+1). It was just to make the code more beautiful. =) The type e is the inhibition monoid. It takes the same role as the e in Either e a. Events don't have a specific type. The concept of events is bound to inhibition. An event wire usually acts like the identity wire when an event occurs and inhibits otherwise.Provide
@DmitryVyal: I must have missed your question. For event wires view (.) as "and" and (<|>) as (inclusive) "or". For example the wire w . e1 . e2 acts like w, if both e1 and e2 happen. The wire w . (e1 <|> e2) acts like w, if at least one of e1 and e2 happens.Provide
O
2

After some trials I've implemented the behavior I needed. Basically, You write a custom inhibitor type which catches the concept of events you need. In my case it was

data Inhibitor = Done | Timeout | Interrupt deriving Show

Done means normal finishing and the rest constructors signal some kind of an error.

After it, you write any custom combinators you need. In my case I needed a way to stop computations and signal a error further:

timeout deadline w | deadline <= 0 = inhibit Timeout
                   | otherwise = mkGen $ \dt a -> do
                       res <- stepWire w dt a
                       case res of
                         (Right o, w') -> return (Right o, timeout (deadline - dt) w')
                         (Left e, _)  -> return (Left e, inhibit e)

This is a variant of switchBy which allows you to change the wire once. Note, it passes the inhibition signal of a new wire:

switchOn new w0 =
    mkGen $ \dt x' ->
        let select w' = do
                (mx, w) <- stepWire w' dt x'
                case mx of
                  Left ex -> stepWire (new ex) dt x'
                  Right x -> return (Right x, switchOn new w)
        in select w0

And this is a variant of (-->) which catches the idea of interrupting the task chain.

infixr 1 ~>

w1 ~> w2 = switchOn ( \e -> case e of
                         Done -> w2
                         _ -> inhibit e
                    ) w1
Odious answered 6/2, 2013 at 5:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.