TP

Reactive programming (II.) - Introducing Reactive LINQ

In this article I'm going to introduce my project Reactive LINQ. This is largely inspired by the ideas that come from functional reactive programming in the Haskell language and from functionality that's available for working in events in F#. I introduced these ideas in my previous article in this mini-series, so if you're interested in learning more about these interesting technologies, you should definitely read the previous article about First class events in F#.

In this article, we're going to look how to apply the same ideas to the C# language and how to use LINQ queries for processing events. This article is just another my article that shows how to implement some idea from functional programming in C#. I believe this will once again show that the new features in the C# 3.0 aren't just about querying databases, but are more widely useful even in situations that are not directly related to data-processing.

The idea: Event is a stream

The key idea that is important for the project is that we can look at .NET events in a different way. We can say that events are actually streams that carry some values, even though these values are not available all the time. They appear in the stream once in a while when the event occurs. Let's take for example a MouseDown event. This can be viewed as a stream that contains information about mouse clicks - the button together with X and Y location. A new value in the stream appears every time user clicks on the control. Now that we have a stream of values, you can see that this is quite similar to for example a collection of values.

Processing events with queries

Form with a maze bitmap

I'll show you a simple example first and then we're going to look how things work behind the scene. We'll write a very simple game - the form will show you a maze (just like the one you can see on the screenshot) and your goal will be to move the mouse cursor from the starting point (on the left) to the target point (on the right). You have to follow the white path and if you move the cursor on the grass, the game ends.

Our implementation will be very simple - it will load the bitmap into the memory and every time you move the mouse, it will check whether the mouse is still on the path (I guess you could cheat if you moved the mouse quickly, but that's not the point now!)

In the usual C# programming style, you'd register an event handler for the MouseMove event and implemented a method that reads the color from the bitmap and tests whether the color is white. If not, it would show a message and set some flag to end the game. Then it would check whether we're close to the target point and if yes, it would display some congratulations box. We'll implement the same thing using two Reactive LINQ queries.

Following the path

The first query will test whether the cursor is still on the path (by checking the color below the cursor). This means that we'll take the event stream coming from the MouseMove event. A value will appear in the stream whenever the cursor position changes. In the first part, we're interested only in the cases where the cursor moved out of the path, so we'll use the where clause to filter values that are on the path. Let's take a look at the first part of the code:

// Move cursor to the start location and load the bitmap
Cursor.Position = this.PointToScreen(new Point(150, 200));
Bitmap bmp = (Bitmap)this.BackgroundImage;

// Create stream for the 'MouseMove' event and
// filter events when the mouse cursor is on the path
var eOut =
  from me in this.AttachEvent<MouseEventArgs>("MouseMove")
  let clr = bmp.GetPixel(me.X, me.Y)
  where clr.R != 255 || clr.G != 255 || clr.B != 255
  select "Oops!\nYou stepped on the grass!";

We start by loading the bitmap and by moving the cursor to the starting position in the maze. The second statement is much more interesting. It is a LINQ query that processes values called me. The type of me is MouseEventArgs, which suggests that the value is created every time the user moves the mouse. The source of these values is created using AttachEvent method. In an ideal world, we could just write this.MouseMove, but in C# you can't use event as an object (for example as a method argument), so the AttachEvent method contains some reflection code to create a value that represents the event. I'll talk about these values in a second.

Once we have MouseEventArgs value available, we can use its properties to check whether the cursor is still following the path. We first use the LINQ let clause to store the color in a variable clr and then use where to test whether the color is other than white. Once the user moves the mouse out of the path, this becomes true and the select clause is triggered. It simply returns a message that we want to show to the user.

Reactive LINQ under the hood

Under the hood, this is translated to an expression that calls Where and Select LINQ operators and looks roughly like this (the let clause makes it a bit difficult, but we don't have to care about that, because we're interested only in the structure):

var eOut = 
  this.AttachEvent<MouseEventArgs>("MouseMove")
    .Where(args => /* some lambda expression */ )
    .Select(args => /* some lambda expression */ );          

This means, that we create some representation of event using AttachEvent and the Reactive LINQ library provides standard LINQ query operators for this representation. Both Where and Select take an event and a function as arguments and return a new event. The type that I use for representing events is an interface type called IEvent<T>. This interface has methods for adding and removing event handlers, but you'll typically never use them directly, because you can work with it using operators like Where and others.

The AttachEvent method is added as an extension method to Windows Forms controls and takes a name of the event as an argument. You also have to specify the specific EventArgs type used with this event. The result in our previous example is IEvent<MouseEventArgs>. This represents an event that gives us MouseEventArgs value when it is triggered. Then we use two LINQ operators that do the following thing:

This means that the overall result of the LINQ query from the previous example is IEvent<string>. This event is triggered when the user mouse the cursor out of the path and it will contain a string value (a message) that we want to show to the user. We'll do this in the next section.

Reacting to events

We'll first implement a handler that will show a message box when the user fails and later we'll finish the second part of the game, which is testing whether the cursor is close to the target point. Adding handler to the event value eOut is quite easy:

eOut.Listen(str => MessageBox.Show(str));

This uses another operator (extension method) available for the IEvent<T> type, which is called Listen. Similarly as Event.listen in F# (see the previous part of this series) this can be used at the end of the event query. It will be called when the event is raised and it can react to the event in any way. In our example, we use a lambda expression that takes the string (that is the message coming from the event) and displays it using MessageBox.Show.

Combining multiple events

Message after finishing the maze

The code for testing whether the mouse is close to the target point will be very similar to the query we've seen earlier. Only the condition that tests whether the event should be triggered (written using where) will differ. Again, we'll write this as an event that gives us a string value when it occurs, so it makes a good sense to reuse the part that shows the message to the user.

Testing the target point

Let's start with the query. Similarly as in the first case, it'll query values comming from the MouseMove event. Next, it calculates the distance between the target point, which is (450, 250) and the current mouse location (me.X, me.Y). We'll test whether this distance is small enough and if yes, we'll trigger the event with a message that the game was successfully completed:

// Filter events when mouse is far from the target
// if the cursor is close, we return a message 
var eFinish =
  from me in this.AttachEvent<MouseEventArgs>("MouseMove")
  let dist = Math.Pow(450 - me.X, 2) + 
             Math.Pow(250 - me.Y, 2)
  where dist < 250
  select "Congratulations!\nYou made it to the end!";

As I mentioned earlier, the query is essentially the same as the first one, with the exception that it has a different condition and returns a different message. An important thing to note is that the eFinish value has the same type as the eOut value - both of them are IEvent<string>.

Merging events

So far, we have two events - one that gives us a message when the user fails and the second one that gives us a message when the user succeeds. The reaction to both of the events will be exactly the same. We'll display the message and then stop further processing of the events, because we don't want to show the messages again (we ignored this issue in the first version).

Because we want to use a single handler for both of the events, we'd like to combine them into a single event in some way. This can be done using Merge method from the static class Reactive. This class contains various other methods for working with events and we'll talk about some of them in later parts of this series. Once we combine the events, we can again use Listen to write the code that reacts to the event. Note that if you're writing the code, you'll need to remove the previous line where we implemented a reaction to the eOut event, because now we'll react to both of the events at once:

// Wait for either of the events
// then stop all processing and show the message
var eMerged = Reactive.Merge(eOut, eFinish);
eMerged.First().Listen(str => MessageBox.Show(str));

We start by merging the events into an event called eMerged. This event has again a type IEvent<string>. It will be triggered whenever either of the two merged events is triggered. This means that the eMerged event will be triggered when the user moves outside of the path or when the cursor is close to the target point (possibly several times). However, we're only interested in the first occurrence. Once either of these two occurs, we just want to show the message box and we're not interested in any further events. This can be written using another LINQ operator called First. The event returned by First will be triggered only once, so we can react to this event using Listen.

Summary

In this article, we've looked at the basic examples of my Reactive LINQ project. This project is largely inspired by functional reactive programming in Haskell [1] and also by first-class events in the F# language [2]. I wrote about the F# implementation in the previous series, so if you skipped that, you may want to read it too.

We looked at the basic query operators that Reactive LINQ provides for events. In particular, we've looked at Where and Select that can be accessed using the nice LINQ query syntax and at combinators like First (for selecting the first event), Merge (for merging a couple of events) and Listen (for reacting to an event). In the next series, we're going to look at some more very useful operators for working with events and in particular at switching operators that allow us to change the way events are handled dynamically.

Downloads and implementation notes

The current implementation is really just a prototype to demonstrate the idea. This means that it probably still contains many issues. One of the possible issues is with removing event handlers when they are not needed anymore. For example, after the First operator is triggered for the first time, it needs to unregister itself from the source event, because it doesn't need to listen to them anymore. Currently, this is implemented using reference-counting like technique. This means that events count how many listeners they have and they unregister when there is no listener. This is of course problematic, because you may create loops. In the future, I'd like to make the implementation more stable and I'm planning to take a look at the Weak Event Pattern [3], which is used in WPF. Since this is a prototype, feel free to do anything with it and let me know if you have any suggestions or ideas how it could be improved!

Series links

References

Published: Wednesday, 19 November 2008, 7:57 PM
Author: Tomas Petricek
Typos: Send me a pull request!
Tags: functional, c#