Sampling a behaviour from outside network
Asked Answered
G

3

9

Since sodium has been deprecated by the author I'm trying to port my code to reactive-banana. However, there seem to be some incongruencies between the two that I'm having a hard time overcomming.

For example, in sodium it was easy to retrieve the current value of a behaviour:

retrieve :: Behaviour a -> IO a
retrieve b = sync $ sample b

I don't see how to do this in reactive-banana

(The reason I want this is because I'm trying to export the behaviour as a dbus property. Properties can be queried from other dbus clients)

Edit: Replaced the word "poll" as it was misleading

Gelsemium answered 12/1, 2016 at 16:23 Comment(11)
On a conceptual level, sampling a Behavior only makes sense in the context of a Moment, i.e. at a particular moment in time, which IO does not provide. This is not just a theoretical issue, but is important for the internal consistency of the implementation, so I'm hesitating to add a function like this. Could you elaborate on the specific context in which you want to use this (dbus)? Chances are that it can be expressed in a different way.Conformity
Im setting a callback function (getCurrentState:: IO Response) when creating the property that is called when a request is received. Said callback function should somehow retrieve the current value of the Behaviour (presumably using the same notion of "current" or "now" as triggering an event by calling the handler function created by newAddHandler does).Gelsemium
It turns out I can re-implement the same behaviour with the tools reactive-banana provides: gist. I'm using unsafePerformIO in this example to come as close as possible to sodium's semantics, but this is by no means necessary (I would just just have to pass the IO action around). So the question becomes: Is there a reason not to build the network with repeated "execute"s rather than directly with compile?Gelsemium
@Gelsemium Could you be more precise and add code that shows the interaction between dbus and your code? I see a good chance that reading the Behavior can be packaged together with an Event.Conformity
@Gelsemium For the second question: Could you make an issue on Github for that? I do see the need for running MomentIO after the network has been built. Note that if several networks are allowed, then it is problematic to return a Behavior from sync, because you could try to use it in another network, which will fail horribly.Conformity
@HeinrichApfelmus trying to see the bigger picture: how did Sodium differ that sample just worked there? Can I assume it was because it was breaking some laws...? Or was it a (safe) hack? Another question: could we simply generate an event that happens every, say, 0.01s, <@ that into the behavior, and get back roughly what Sodium offers? That would be less accurate but depending on needs, might work. (on another note: my gut feeling though is still to track what really is the underlying Event that directly or indirectly causes the vaue of the Behavior to change, and use that.)Manard
@HeinrichApfelmus yet another idea: would it be theoretically possible to provide a sample that did (probably dependent-types based) analysis to see if there is indeed an underlying Event from which the Behavior is computed, and automatically <@ with that Event? but cause a compile error if no such Event exists? But then again, outside "influence" is always ultimately an Event, so I think sample could always just work and thus deptypes would not even be needed...?Manard
Acutally, a Behavior can obviously also lazily read from an external resource (although, under the hood, absolutely everything is interrupt-based, so at least conceptually it's all event-based), so I guess deptypes or equiv. would still be needed.Manard
@erik-allik Reactive-banana does already have sample (called valueB), it's sync that's missing. And I managed to simulate it with the primitives available in reactive-banana, so it can't be breaking any laws that reactive-banana isn't already breaking.Gelsemium
Could you please share your solution in the form of an answer? I would love to learn from and upvote it. Your original question has 9 upvotes so it kind of even is your moral duty to resolve what you started :)Manard
Although I still think my own idea viable, i.e. to extract any underlying Event from a Behavior under observation — because valueB is nice but it does not stay within the pure world of events and behaviors, but a deeper solution could. Looking forward to a reply by @HeinrichApfelmusManard
G
0

The answer seems to be "it's sort of possible".

sample corresponds to valueB, but there is no direct equivalent to sync.

However, it can be re-implemented with the help of execute:

module Sync where

import Control.Monad.Trans
import Data.IORef
import Reactive.Banana
import Reactive.Banana.Frameworks

data Network = Network { eventNetwork :: EventNetwork
                       , run :: MomentIO () -> IO ()
                       }

newNet :: IO Network
newNet = do
    -- Create a new Event to handle MomentIO actions to be executed
    (ah, call) <- newAddHandler
    network <- compile $ do
        globalExecuteEV <- fromAddHandler ah
        -- Set it up so it executes MomentIO actions passed to it
        _ <- execute globalExecuteEV
        return ()
    actuate network
    return $ Network { eventNetwork = network
                     , run = call -- IO Action to fire the event
                     }

-- To run a MomentIO action within the context of the network, pass it to the
-- event.
sync :: Network -> MomentIO a -> IO a
sync Network{run = call} f = do
    -- To retrieve the result of the action we set up an IORef
    ref <- newIORef (error "Network hasn't written result to ref")
    -- (`call' passes the do-block to the event)
    call $ do
        res <- f
        -- Put the result into the IORef
        liftIO $ writeIORef ref res
    -- and read it back once the event has finished firing
    readIORef ref

-- Example
main :: IO ()
main = do
    net <- newNet -- Create an empty network
    (bhv1, set1) <- sync net $ newBehavior (0 :: Integer)
    (bhv2, set2) <- sync net $ newBehavior (0 :: Integer)
    set1 3
    set2 7
    let sumB = (liftA2 (+) bhv1 bhv2)
    print =<< sync net (valueB sumB)
    set1 5
    print =<< sync net (valueB sumB)
    return ()
Gelsemium answered 27/1, 2016 at 19:17 Comment(0)
K
1

If you have a Behaviour modelling the value of your property, and you have an Event modelling the incoming requests for the property's value, then you can just use (<@) :: Behavior b -> Event a -> Event b1 to get a new event occurring at the times of your incoming requests with the value the property has at that time). Then you can transform that into the actual IO actions you need to take to reply to the request and use reactimate as usual.


1 https://hackage.haskell.org/package/reactive-banana-1.1.0.0/docs/Reactive-Banana-Combinators.html#v:-60--64-

Kraus answered 14/1, 2016 at 2:4 Comment(1)
There is no action to reply to the request, rather, the return value of the callback set for the dbus property is sent in reply. What I could do is create a new IORef and pass is into the Event along with the request, use reactimate to put the sampled value into the IORef, and then read the IORef in the request handling function once the Event-call returns. However, this requires interacting code in two places (the event network and the callback funcion) which makes the code harder to maintain, compared to the simple solution that sodium affords.Gelsemium
M
0

For conceptual/architectural reasons, Reactive Banana has functions from Event to Behavior, but not vice versa, and it makes sense too, given th nature and meaning of FRP. I'm quite sure you can write a polling function, but instead you should consider changing the underlying code to expose events instead.

Is there a reason you can't change your Behavior into an Event? If not, that would be a good way to go about resolving your issue. (It might in theory even reveal a design shortcoming you have been overlooking so far.)

Manard answered 12/1, 2016 at 23:17 Comment(4)
I'm not sure how this applies to my question. I'm fairly certain that it should be a Behaviour, since it models a time-varying value. My problem is that I have a request coming in from the network that I would like to answer with the current value of this Behaviour. Of course, I could model the arrival of this request as an Event (by triggering a handler), but this will only get the request into the (frp-) network, it can't retrieve the result to send back.Gelsemium
My choice of the word "poll" may have been misleading. I do not want to check when the value has changed, rather I'd like to retrieve the value of a Behaviour at certain times, e.g. to answer a network request (think HTTP)Gelsemium
Oh, I see, my bad then :)Manard
Actually retracted my deletion — I still think this is the way to go. This only does not work if there is no underlying Event that is "triggering" changes to the Behavior, which might or might not be the case. But then again, you could also consider changing your design in a way that you are relying on an incoming event to emit outgoing events. Anything else is either a hack (breaking the laws) or an approximation (time-based sampling)... but I might be missing something.Manard
G
0

The answer seems to be "it's sort of possible".

sample corresponds to valueB, but there is no direct equivalent to sync.

However, it can be re-implemented with the help of execute:

module Sync where

import Control.Monad.Trans
import Data.IORef
import Reactive.Banana
import Reactive.Banana.Frameworks

data Network = Network { eventNetwork :: EventNetwork
                       , run :: MomentIO () -> IO ()
                       }

newNet :: IO Network
newNet = do
    -- Create a new Event to handle MomentIO actions to be executed
    (ah, call) <- newAddHandler
    network <- compile $ do
        globalExecuteEV <- fromAddHandler ah
        -- Set it up so it executes MomentIO actions passed to it
        _ <- execute globalExecuteEV
        return ()
    actuate network
    return $ Network { eventNetwork = network
                     , run = call -- IO Action to fire the event
                     }

-- To run a MomentIO action within the context of the network, pass it to the
-- event.
sync :: Network -> MomentIO a -> IO a
sync Network{run = call} f = do
    -- To retrieve the result of the action we set up an IORef
    ref <- newIORef (error "Network hasn't written result to ref")
    -- (`call' passes the do-block to the event)
    call $ do
        res <- f
        -- Put the result into the IORef
        liftIO $ writeIORef ref res
    -- and read it back once the event has finished firing
    readIORef ref

-- Example
main :: IO ()
main = do
    net <- newNet -- Create an empty network
    (bhv1, set1) <- sync net $ newBehavior (0 :: Integer)
    (bhv2, set2) <- sync net $ newBehavior (0 :: Integer)
    set1 3
    set2 7
    let sumB = (liftA2 (+) bhv1 bhv2)
    print =<< sync net (valueB sumB)
    set1 5
    print =<< sync net (valueB sumB)
    return ()
Gelsemium answered 27/1, 2016 at 19:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.