F# First Class Events – Composing Events Until Others
After a comment on my last post in regards to First Class Events in F#, I thought I’d revisit them briefly before going back to the Reactive Framework series. In particular, this comment was in regards to implementing the until combinator using F# First Class Events. As part of my hacking during this series, I came across a rather identical solution to the one that was posted, so I thought it’d be worth exploring.
Implementing Until
The idea behind the until event combinator is to that we will return values from our event until any notification from the other event occurs. In order to accomplish this, we must subscribe to the other event and if it has fired, then we reset a flag to not fire. We then have to subscribe to the main event and only trigger when the flag is set to true. Let’s look through how this code might look.
module Event = let until (other: IEvent<_,_>) (source: IEvent<_,_>) = let event = new Event<_>() let fire = ref true other .Add(fun _ -> fire := false) source.Add(fun args -> if !fire then event.Trigger args) event.Publish
In order for us to modify the fire flag, we must use a ref cell. The reason being is that local mutables are always stack allocated which is why you cannot capture them inside a closure. One comment that was made about this solution was that we added two subscriptions for creating this single event. That indeed can be a problem as Tess Ferrandez points out about event handlers that made the memory balloon where you hook up to static event handlers that long outlive pages. That’s not necessarily something we’ll address here, and it’s rare that we’ll run into this situation.
Now that we have this until defined, what can we do with it? In my previous post, we used the WebClient to track the progress changed of a download until the download has completed. We’ll use that same logic here using our F# first class events.
open System open System.Net type IDelegateEvent<'Del when 'Del :> Delegate > with member this.Subscribe(d) = this.AddHandler(d) { new IDisposable with member disp.Dispose() = this.RemoveHandler(d) } let wc = new WebClient(Credentials = new NetworkCredential("foo","bar")) let progSub = wc.DownloadProgressChanged |> Event.until wc.DownloadStringCompleted |> fun event -> event.Subscribe(fun _ args -> printfn "%d%% Complete" args.ProgressPercentage) let downloadSub = wc.DownloadStringCompleted.Subscribe (fun _ args -> printfn "%s" args.Result)
Here, once again I’m using the extension method I created that allows us to dispose of our event handlers easily through an IDisposable interface. This follows the design of the Reactive Framework, and quite frankly, makes a lot of sense. What we’re able to do is create our DownloadProgressChanged subscription which listens until the DownloadStringCompleted event and then we output our percentage complete as it goes along. The download subscription is much like before in that we subscribe to the event and can dispose at any time later in our program.
Adding Some Monadic Magic
Let’s add another wrinkle here to our solution. What if we want to create our mouse drag event like we had from the previous post using the Reactive Framework? Let’s review the code that was required in Rx to make this happen.
var mouseDrag = from md in this.GetMouseDown() from mm in this.GetMouseMove().Until(this.GetMouseUp()) select mm;
I may have hinted in the previous post, that LINQ to Objects is an implementation of the list monad and the Reactive Framework is an implementation of the continuation monad. As Erik points out in one of his recent Channel 9 episodes, indeed many of the interesting things we’re finding in programming are coming in monadic form. We’ll cover exactly what that means in a later post, but what if we could apply the same kind of logic from the above and apply it to F# first class events as well?
In order to make this happen, we need to implement a bind/collect combinator. This bind function follows the basic pattern of:
val bind : M<'a> -> ('a -> M<'b>) -> M<'b>
Where the M is some monadic type. In the case of LINQ to Objects, this is the SelectMany method and the M type is an IEnumerable<T>. If we were to construct the collect combinator from scratch for an IEnumerable<T>, it might look like the following:
let collect f = Seq.map f >> Seq.concat
And our approach to doing this with events should be no different. In this case, we also need to implement the concat combinator as well as that does not exist in the base libraries but pretty simple to create. First, with the concat, we need to take in a IEvent of an IEvent and concatenate them together in a way that we get only an IEvent as a return. In order to do so, we need to add a handle to our source, which in turn listens to the inner events and triggers our new event that we later publish. Secondly, we’ll need to implement the collect combinator much like we had above, but this time for events. Below is what the code might look like.
module Event = let concat (source: IEvent<IEvent<_,_>>) = let event = new Event<_>() source.Add (Event.listen event.Trigger) event.Publish let collect f = Event.map f >> concat
Now that we have a basis for binding together our events, let’s create a monadic builder so that we could write event expressions much as we would for sequence expressions. In this case, we only need the bind and not the return for this instance. A simple return wouldn’t make sense to yield a single value through an event.
type EventBuilder() = member this.Bind(m, f) = Event.collect f m let event = new EventBuilder()
Now that we have this defined, F# gives us some syntactic sugar much like the do notation in Haskell. Let’s now add an extension method to the WinForms Control class.
type System.Windows.Forms.Control with member this.MouseDrag = event { let! _ = this.MouseDown return! this.MouseMove |> Event.until this.MouseUp }
And there you have it! We now have a mouse drag event exposed on our control class which tracks only when the mouse button is down and our mouse is moving. To get an idea of what it’s doing behind the scenes, I’ll also include the non-syntactic sugar version.
type System.Windows.Forms.Control with member this.MouseDrag = event.Bind(this.MouseDown, fun _ -> this.MouseMove |> Event.until this.MouseUp)
And now we can subscribe to this much like any other event and have it track our mouse.
Conclusion
Creating composable events using F# first class events is a pretty compelling story. One of the driving ideas behind the Reactive Framework in terms of composable events does in fact come from F#. Come .NET 4.0, it will be interesting on how Rx and F# First Class Events can co-exist and in fact compliment each other. Now back to the Reactive Framework series.