F# First Class Events – Creating Events

In the previous post on first class composable events in F#, we talked mostly about the underlying types and the basic composition that you can achieve through the Event module.  By using the basic combinators of map, filter, partition, etc, we are able to create some rather rich scenarios for first class events.  We’ve already shown what we can do with existing events off of such things as Windows Forms applications, but how about we create our own?

Creating Custom Events

Creating events in F# is unlike experiences in other .NET languages as you do not declare events as members.  Typically in C#, we would declare an event such as the following:

public delegate Assembly ResolveEventHandler(
    object sender, 
    ResolveEventArgs args);

public class AppDomain { 
    public event ResolveEventHandler TypeResolve;

Instead, in F#, we use the Event.create function to create a new event.  Let’s take a look at the signature:

Event.create : 
  // No arguments
  unit -> 
  // Handler function and event tuple
  ('a -> unit) * #Event<'a>
  // Event function

What this tells us is that we create both an invoker function and an event when we call Event.create.  You’ll notice that our events don’t have to follow the standard object sender and EventArgs signature, and instead can be anything we want them to.  If the type inference cannot infer how we’re using our event, we may need to specify what our arguments are.  Let’s create a simple one that posts numbers and reacts to them.

> let fire, event = Event.create()
- event.Add(printfn "Fired %d")
- fire(25);;
Fired 25

val fire : (int -> unit)
val event : IEvent<int>

The above code first calls Event.create which gives us our invoker (fire) and our event in which we can then add a handler to in order to print our value.  Finally, we call the fire invoker to send a message of 25 and as you see below it prints out “Fired 25”.  You can see we used no type declarations at all in the above code as they weren’t necessary when our type inference did the work for us.

Let’s look through another example of creating an event which is fired when a combination of two events occur, a mouse move and a mouse down, but only until the mouse up event.  Instead of using WinForms, let’s use WPF instead.  First, let’s get some of the overhead out of the way:

open System.Windows
open System.Windows.Controls
open System.Windows.Input
open System.Windows.Media

let getPosition (elem : #IInputElement) (args : #MouseEventArgs) =
  let point = args.MouseDevice.GetPosition(elem)
  point.X, point.Y

This allows us easily to get our absolute position of our mouse based upon our given element.  This will be useful for obtaining our coordinates later.  By using the #, I’m specifying that our given parameters must inherit from the given class or interface.  As F# is strongly typed, even moreso than C#, it makes our lives easier by not requiring casting.  let’s move onto the creating this mouse tracker:

let createMouseTracker (e : #UIElement) =
  let fire,event = Event.create()
  let lastPosition = ref None
    |> Event.map (getPosition e)
    |> Event.listen (fun position -> lastPosition := Some position);
  e.MouseUp.Add(fun args -> lastPosition := None);
    |> Event.map (getPosition e)
    |> Event.listen (fun position ->
         match !lastPosition with
         | Some last -> fire(last,position); lastPosition := Some position
         | None -> ())

Once again, I create our fire and our event in the beginning.  In order to capture state such as last position, I need a reference cell in order to do this.  F# does not allow for mutable values to escape scope, so instead, the ref cell does the trick.  We first do a map on the mouse down to get the position, then update the position ref cell.  On the mouse up event, we simply reset the ref cell to empty.  The mouse move event, once again we get the position, then we listen based upon last position.  If there is a last, we fire our event with our previous and current value.  And finally, we return our new event.

That’s great, but what about publishing our events inside a class?

Publishing Events to the World

With our previous examples, we’re mostly dealing with local functions, events and values?  But, what if we want to encapsulate these events in classes and publish them?  Let’s consider the following scenario of a simple state machine that holds integers.  This has two events, one for updating and one for clearing.  We’ll implement this by using a simple MailboxProcessor class implementation, but first, let’s get some of the infrastructure out of the way.

// Shorthand operator
let (<--) (m:'a MailboxProcessor) msg = m.Post(msg)

// Messages for our state machine
type private StateMessage = 
  | Update of int 
  | Get of AsyncReplyChannel<int option> 
  | Clear 

We’ll define three messages for our state machine to handle, the Update which gets an integer, our Get which takes a reply channel for the data, and finally, our Clear message.  Now that we have this defined, let’s get to our main implementation:

type StateMachine() =
  // Our two events
  let clearFire, clearEvent = Event.create()
  let updateFire, updateEvent = Event.create()

  let actor = 
    MailboxProcessor.Start(fun inbox ->
      let rec loop state =
        async { let! msg = inbox.Receive()
                match msg with
                | Clear ->
                    return! loop None
                | Get replyChannel -> 
                    return! loop state
                | Update newState ->
                    match state with
                    | Some oldState ->
                        updateFire(oldState, newState)
                    | None -> ()
                    return! loop <| Some(newState) }
      loop None)

  // Publishing the events
  member this.Updating = updateEvent
  member this.Clearing = clearEvent

  // Expose behavior
  member this.Clear() = actor <-- Clear
  member this.Update(x) = actor <-- Update(x)
  member this.Get() = actor.PostAndReply(fun replyChannel -> Get(replyChannel))

This is a bit of code, so bear with me here.  First, we declare our two events as I described above, one to handle our clearing event, and one for our updating event.  Next, we create our mailbox processor which handles our messages.  In case of our clear, we simply invoke with no arguments and loop again.  In the case of our update on the other hand, we only will fire if there is previous state and will return both the old and the new. 

In order to publish events, we simply expose our created events much like we would for any immutable property.  This allows the outside consumer to then latch on to these events.  And finally in our code, we send our private messages to our actor in order to clear, update and get our value.  Now that everything is defined, let’s go for a test run to see how this works:

> let state = new StateMachine()
- state.Clearing.Add(fun () -> printfn "Clearing")
- state.Updating.Add(fun (old,new') -> printfn "Old(%d)\tNew(%d)" old new');;

val state : StateMachine

We created two handlers, one to print clearing for a clearing event and for the update, we print out the old and new value.  Let’s run this through a loop to see what sort of goodies we can print out:

> for i = 0 to 10 do
-   if i % 3 = 0 then state.Clear()
-   state.Update(i);;
val it : unit = ()
Old(0)  New(1)
Old(1)  New(2)
Old(3)  New(4)
Old(4)  New(5)
Old(6)  New(7)
Old(7)  New(8)
Old(9)  New(10)

As you can see, we get our expected behavior from this that every third action from 0 to 10 should have a clearing event which is triggered when we clear, and then update in between with our old and new value.  It’s an interesting, albeit naive example that shows the power of events in F# in classes.


I hope by now you start to see some of the possibilities with first class events with both consuming and publishing.  Unlike other .NET languages, instead of exposing our events as members, we call our Event.create function, which gives us a bit more interesting power.  We’re not quite finished here as we have a lot more to cover with both first-class events in F#, as well as the Reactive Framework.

No Comments