views:

175

answers:

4

In F# I know how to wait asynchronously for one event using Async.AwaitEvent:

let test = async {
  let! move = Async.AwaitEvent(form.MouseMove)
  ...handle move... }

Suppose I want to wait for either the MouseMove or the KeyDown event. I'd like to have something like this:

let! moveOrKeyDown = Async.AwaitEvent(form.MouseMove, form.KeyDown)

This function doesn't exist but is there another way to do this?

+1  A: 

You can use a combination of Event.map and Event.merge:

let eventOccurs e = e |> Event.map ignore
let mouseOrKey = Event.merge (eventOccurs frm.MouseMove) (eventOccurs frm.KeyDown)

Then you can use Async.AwaitEvent with this new event. If MouseMove and KeyDown had the same type, you could skip the Event.map step and just directly merge them.

EDIT

But at Tomas points out, you should use the Observable combinators in preference to the Event ones.

kvb
This works but now I loose the ability to do anything with the event properties because the event is of type `unit`. Maybe I need to take an entirely different approach but I'm not sure what.
Ronald Wildenberg
you can map event args on Choice (or some another type) instead of ignoring it. I've edited my answer to include sample for this version
desco
+10  A: 
let ignoreEvent e = Event.map ignore e

let merged = Event.merge (ignoreEvent f.KeyDown) (ignoreEvent f.MouseMove)
Async.AwaitEvent merged

EDIT: another version that preserves original types

let merged = Event.merge (f.KeyDown |> Event.map Choice1Of2) (f.MouseMove |> Event.map Choice2Of2)
Async.AwaitEvent merged

EDIT 2: according to comments of Tomas Petricek

let e1 = f.KeyDown |> Observable.map Choice1Of2
let e2 = f.MouseMove |> Observable.map Choice2Of2
let! evt = Observable.merge e1 e2 |> Async.AwaitObservable

AwaitObservable primitive can be taken from here ('Reactive demos in Silverlight' by Tomas Petricek).

desco
Cool, learned a little more F#. Thanks.
Ronald Wildenberg
Could you consider changing the code to use `Observable` instead of `Event`? (Using `Event.xyz` in this scenario can cause leaks - see my answer for more info...)
Tomas Petricek
You may want to note that `Async.AwaitObservable` is not built into F# and that it's an extension found in "Real-World Functional Programming."
gradbot
+4  A: 

In the interest of understanding what's going on I looked up the source code to Event.map, Event.merge and Choice.

type Choice<'T1,'T2> = 
    | Choice1Of2 of 'T1 
    | Choice2Of2 of 'T2

[<CompiledName("Map")>]
let map f (w: IEvent<'Delegate,'T>) =
    let ev = new Event<_>() 
    w.Add(fun x -> ev.Trigger(f x));
    ev.Publish

[<CompiledName("Merge")>]
let merge (w1: IEvent<'Del1,'T>) (w2: IEvent<'Del2,'T>) =
    let ev = new Event<_>() 
    w1.Add(fun x -> ev.Trigger(x));
    w2.Add(fun x -> ev.Trigger(x));
    ev.Publish

This means our solution is creating 3 new events.

async {
    let merged = Event.merge 
                     (f.KeyDown |> Event.map Choice1Of2) 
                     (f.MouseMove |> Event.map Choice2Of2)
    let! move = Async.AwaitEvent merged
}

We could reduce this to one event by making a tightly coupled version of this library code.

type EventChoice<'T1, 'T2> = 
    | EventChoice1Of2 of 'T1
    | EventChoice2Of2 of 'T2
    with 
    static member CreateChoice (w1: IEvent<_,'T1>) (w2: IEvent<_,'T2>) =
        let ev = new Event<_>()
        w1.Add(fun x -> ev.Trigger(EventChoice1Of2 x))
        w2.Add(fun x -> ev.Trigger(EventChoice2Of2 x))
        ev.Publish

And here is our new code.

async {
    let merged = EventChoice.CreateChoice form.MouseMove form.KeyDown
    let! move = Async.AwaitEvent merged
}
gradbot
+6  A: 

I used an implementation of a method that you use in your sample in the talk about reactive programming that I had in London (there is a download link at the bottom of the page). If you're interested in this topic, you may find the talk useful as well :-).

The version I'm using takes IObservable instead of IEvent (so the name of the method is AwaitObservable). There are some serious memory leaks when using Event.merge (and other combinators from the Event module) together with AwaitEvent, so you should use Observable.merge etc. and AwaitObservable instead.

The problem is described in more detail here (see Section 3 for a clear example). Briefly - when you use Event.merge, it attaches a handler to the source event (e.g. MouseDown), but it does not remove the handler after you finish waiting using AwaitEvent, so the event is never removed - if you keep waiting in a loop coded using asynchronous workflow, you keep adding new handlers (that do not do anything when run).

A simple correct solution (based on what desco posted) would look like this:

let rec loop () = async {
  let e1 = f.KeyDown |> Observable.map Choice1Of2
  let e2 = f.MouseMove |> Observable.map Choice2Of2
  let! evt = Observable.merge e1 e2 |> Async.AwaitObservable
  // ...
  return! loop() } // Continue looping

BTW: You may also want to look at this article (based on chapter 16 from my book).

Tomas Petricek
@Tomas - Interesting. It was actually while reading ch.16 of your book that this question came up. I thought there had to be a way that F# Core somehow provided support for this scenario and it appears it does. Reading through section 3 of the article, I understand what the problem is. What I'm not sure about is whether observables use the backward-referencing technique described in section 6 to prevent memory leaks? Or do they use some other technique?
Ronald Wildenberg
@Ronald: Observables use a different technique then the one described in the referenced paper. Briefly (I don't have much time right now) - when you start listening to an observable (e.g. created using `map`), it returns a token that can be used to unregister from the original event source.
Tomas Petricek