Event and event handling in F#

Publishing events

Let’s look at the subject of event handling by starting with some code

type Application() =
    let state = new Event<_>()
    
    member this.State = state.Publish
    member this.Run() =
        state.Trigger(this, "Starting")
        state.Trigger(this, "Running")
        state.Trigger(this, "Finished")

So we’ve declared an Application object which has a private state value, of type Event<_>. Basically this is declaring a value as an Event, the <_> leaves it to the compiler to infer the usage, ofcourse you can explicitly state the type if you prefer.

An Event is an implementation of IEvent, which is itself derived from an IDelegateEvent and IObservable.

The IDelegateEvent has a special use when you mark properties with the attribute [<CLIEvent>]. The properties are now compatible with other languages which use CLI compatible events.

The IObservable allows an event to be composable using Reactive Extensions (Rx) or using F#’s subset of Rx – this means we can filter, merge, split and more on events.

Going back to the code sample, we declare a member property using this.State = state.Publish which basically binds to the Publish property which itself allows us to use the Add method to add event handlers. Had we wished to make this property CLI compliant we would write

[<CLIEvent>]
member this.State = state.Publish

The next piece of code in our example is a Run function and this simply triggers events in a way analogous to state changing during the function.

Let’s now look at some code for handling these “state” change events…

Subscribing to events

Assuming we’ve created an instance of the application object, as follows

let app = new Application()

we can now subscribe to events on the object by adding an event handler

app.State.Add(fun (sender, obj) -> printfn "%s" obj)

in this example we use the Add function to subscribe to the State event and in this example we supply an anonymous function which is called each time a trigger is fired on the Application object’s State event.

Now the Add function doesn’t have an equivalent remove, but as an alternative we could use the IDelegateEvent’s AddHandler and RemoveHandler as per the following

let handler = new Handler<_>(fun s o -> printfn "%s" (snd o))
app.State.AddHandler(handler)

// ...

app.State.RemoveHandler(handler)

We can also use the more functional style Event module methods to both add and filter (for example) to only handle certain messages, as per

app.State 
   |> Event.filter(fun (sender, obj) -> obj = "Running")
   |> Event.add(fun (sender, obj) -> printfn "%s" obj)

In this example we’re filtering the events to only handle the case when the message is “Running” and the add function simply subscribes to the resultant events and outputs the message.

Observable

We can also use the IObservable subscription based model for events using

app.State 
   |> Observable.map(fun (sender, obj) -> obj)
   |> Observable.filter(fun obj -> obj = "Running")
   |> Observable.add(fun obj -> printfn "%s" obj)

In this sample code we map the events to extract the message string (using the map function), then we filter it and finally we add our event handler.

And here’s an example using the subscribe function

app.State 
   |> Observable.map(fun (sender, obj) -> obj)
   |> Observable.filter(fun obj -> obj = "Running")
   |> Observable.subscribe(fun obj -> printfn "%s" obj)
   |> ignore