2

I want to map an IO action over a Behavior but there is no such function in (threepenny-gui) Is there some way of building it with the exposed API. Is it semantically sound?

I've implemented this inside threepenny, in my use case works fine.

-- Just bind the IO action to the Latch
unsafeMapIOB :: (a → IO b) → Behavior a → Behavior b
unsafeMapIOB f (B l e) = B (Prim.bindL (Prim.Latch ∘ f) l) e

-- Wrap the IO bind
bindL :: (a -> Latch b) ->  Latch a -> Latch b
bindL f l = Latch { readL = (readL ∘ f) =<< readL l}

-- Map the IO action over both Behavior and Event
unsafeMapIOT :: (a -> IO b) -> Tidings a -> Tidings b
unsafeMapIOT f x = tidings (unsafeMapIOB f $ facts x) (unsafeMapIO f $ rumors x)

I've never used unsafeMapIOB alone just unsafeMapIOT . What i believe is happening is that unsafeMapIOB is executed just once then unsafeMapIO is triggered with the event.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Massudaw
  • 31
  • 5
  • I don't know that library so I won't attempt a full-fledged answer, but to me this seems to boil down whether there exists a function of type `IO a -> Behavior a` (by which I mean not just if such a function is explicitly provided, but also whether the functions provided allow you to write one). Certainly there is no `IO a -> a` function that would allow you to write this. – Luis Casillas Aug 14 '14 at 16:06
  • 6
    I'd say that semantically it's not very meaningful to use `Behavior` in this way, as it's a time-varying function, so there aren't clear time instants when the `IO` function should be invoked. Instead, `Event` seems to be more appropriate. There is function [`unsafeMapIO`](http://hackage.haskell.org/package/threepenny-gui-0.4.2.0/docs/Reactive-Threepenny.html#v:unsafeMapIO), but it seems it's not something you should use without thorough consideration. – Petr Aug 14 '14 at 18:57

1 Answers1

2

With ideal FRP semanitcs, a behavior is a function over time: Time → a—it can be changing continuously over time. This means that calling an IO action on every change is not semantically sound because there is no notion of a discrete change in the semantics.

This also makes sense in practice: depending on when the value of a behavior changes is often too implementation-dependent. Consider, for example, the mouse position: how often the value changes is based on how often it's polled, which is very system dependent. Even if the actual behavior is not continuous, the discrete changes are still a detail that we do not want to leak into the your program. (In an ideal world, perhaps that behavior would be continuous too—maybe the system would do some sort of interpolation or smoothing to compensate for the underlying polling in hardware.)

To avoid depending on these arbitrary implementation details, you have to be explicit about when to sample your behavior to fire your IO action. You can do this by taking the time component of another event stream, creating a new event stream with events at the same time but values based on the current value of the behavior. In Reactive Banana, you can do this with the <@ operator. Specialized to our types, we get:

(<@) :: Behavior a -> Event b -> Event a

In essence, we map the value of the behavior over an event stream, giving us a new event stream. Then we can just use a standard function to fire off an IO action on every event.

The final question, then, is where you get your event stream from, which will depend on your specific program. If the behavior changes because of some specific user action, you can get a stream for that action. You could also just create a timer event and poll at a given time interval. Or you can even just do both, using union! A nice illustration of how FRP is pretty composable.

Tikhon Jelvis
  • 67,485
  • 18
  • 177
  • 214
  • 2
    The first problem is indeed that `Behavior` is to be thought of as continuous, and doesn't come with a notion of "updates". The second problem is that even a function of type `Event a -> (a -> IO b) -> Event b` is problematic, because the order of `IO` actions would be undefined if we have different events that are built using this function. – Heinrich Apfelmus Aug 15 '14 at 08:19
  • @HeinrichApfelmus: Hmm, good point. I mentally parsed the question as asking for something more like `Event a -> (a -> IO b) -> IO ()`, which, to my understanding, should be fine. – Tikhon Jelvis Aug 15 '14 at 18:50
  • Yup, this should be fine. It's the type of `reactimate`. – Heinrich Apfelmus Aug 16 '14 at 09:45