In the past week, I had the pleasure of speaking on Reactive Programming in F# with Brian McNamara at a conference out in Seattle. The point of this talk was to cover the what and why of using F# in reactive programming on both the client and the server and showed quite a few examples. One of the samples Brian alluded to, the “Time Flies Like An Arrow” example, which is an example of having a stream of text, each character delayed behind the previous, follow the mouse around the screen. Brian’s version used a combination of both the asynchronous workflows which are a standard part of F#, as well as first class events/observables. For my version, I’m going to strictly use F# first class events and the integration with the Reactive Extensions for .NET to show you how it can be done.
Implementing Using F#
Let’s take a look at what would be required for us to make the Time Flies Like an Arrow example work in F# using first class events and the Reactive Extensions for .NET on top of WPF. I’ll write it in a pseudo-code to give you an idea of how it should be done.
text = "F# Reacts to First Class Events!" for character in text textbox = TextBox with text = character Add textbox to canvas MouseMove - Get Position relative to canvas - Delay by character position * 100 - Observe on the dispatcher thread - Set the textbox top to the y position of the mouse - Set the textbox left to x position of the mouse plus character position * 10
Luckily, using F#, our code shouldn’t be much more complicated than that. With this pseudo-code in mind, let’s get started. First, we’ll need an additional two combinators that the F# Observable module does not include, which is the ability to delay and observe on the dispatcher thread. These combinators are simple pass-throughs to the Reactive Extensions which allow us to have a nice pipelining for creating our events.
module Observable = let delay (span : TimeSpan) (observable : IObservable<'T>) = observable.Delay(span) let observeOnDispatcher (observable : IObservable<'T>) = observable.ObserveOnDispatcher()
Next, we’ll need to be able to get the position of our mouse in relation to a given UIElement, and this case, it will be the main canvas.
let getPosition (element : #UIElement) (args : MouseEventArgs) = let point = args.GetPosition(element) (point.X, point.Y)
Now that we have some of the helpers defined, we can define our WPF Window. In this code, I’ll create a canvas and add it as the content to our Window. Then I’ll implement the pseudo-code from above into F# to iterate through the characters, create a textbox for each and then set the mouse move to move our text box when there is a reaction.
type TimeFliesWindow() as this = inherit Window() do this.Title <- "Time files like an arrow" let canvas = Canvas(Width=800.0, Height=400.0, Background = Brushes.White) do this.Content <- canvas do "F# can react to first class events!" |> Seq.iteri(fun i c -> let s = TextBlock(Width=20.0, Height=30.0, FontSize=20.0, Text=string c, Foreground=Brushes.Black, Background=Brushes.White) canvas.Children.Add(s) |> ignore this.MouseMove |> Observable.map(getPosition canvas) |> Observable.delay (TimeSpan.FromMilliseconds(float i * 100.0)) |> Observable.observeOnDispatcher |> Observable.subscribe(fun (x, y) -> Canvas.SetTop(s, y) Canvas.SetLeft(s, x + float ( i * 10))) |> ignore)
And there you have it, a rather simple example of using F# First Class Events which shows off the composable nature of events and what interesting things we can do with them.
Some point in the near future, I’ll come back to explain asynchronous workflows on the client to give further examples on where F# fits. F# is a great language for writing non-blocking I/O computations. Using the rich F# First Class events, which are also IObservable<T> instances, we can use both what F# has to offer as well as the integration with the Reactive Extensions for .NET to create even richer experiences.