Tuesday 11 August 2009

Reacting to the Reactive Framework: Part 7

The demo application for today allows the user to experiment with a couple of the mechanisms that I tried for part 6. As you may recall, I wanted an IObservable<int[]> that would return the three selections made from three groups of radio buttons, and only begin returning values once a selection had been made from all three. The demo app allows you to switch between using SelectMany, ForkJoin and CombineLatest and observe how the behavior changes:

The first approach I tried was SelectMany - I’ll use the query comprehension syntax as it is much more readable than calling the SelectMany extension method directly:

from s1 in choiceControl1.OptionSelections
from s2 in choiceControl2.OptionSelections
from s3 in choiceControl3.OptionSelections
select new[] { s1, s2, s3 }

This syntax might be readable, but its also a little misleading. It looks very uniform, like it wouldn’t matter which order you wrote those three ‘from’ statements in. But if you are familiar with LINQ statements like this one, you will know that the ordering IS important. To observe this, activate the SelectMany option in the app above, and then make your selections in reverse order i.e. select an option from group 3, then group 2, then group 1. Notice how you don’t get a selection readout? What’s actually happening here is that the selections made in groups 2 and 3 are ignored until a selection is made in group 1. Then the selections made in group 3 are ignored, until a selection is made in group 2. Then finally selections from group 3 will trigger the observable and raise a result. I could go into more detail on what’s happening here, but is there much point? Its obvious that this implementation is not at all close to the desired result. Lets move on.

When I came across the signature for ForkJoin, I thought I had found what I was looking for:

public static IObservable<TSource[]> ForkJoin<TSource>(params IObservable<TSource>[] sources); 

It converts many IObservable<T>’s into one IObservable<T[]>, which is exactly what I want. The bad news is that it only works once. Go ahead and try ForkJoin in the sample app – the status text will update once a selection has been made from all three groups, but it will not update again as the selection continues to change. It did occur to me that perhaps I could use ForkJoin and somehow re-subscribe each time it fires, but I was reluctant to go down that path because it doesn’t feel like a functional (as in functional programming) solution.

The demo app for today uses CombineLatest in the same way as in my previous post:

choiceControl1.OptionSelections
  .CombineLatest(choiceControl2.OptionSelections, (i, j) => new[] { i, j })
  .CombineLatest(choiceControl3.OptionSelections, (array, k) => new[] { array[0], array[1], k }))

This works fine, though its not pretty. I’ll keep my eye out for a better solution, but before I finish up today I want to look at the code behind today’s demo app:

<StackPanel Margin="20">
    <TextBlock Text="Combining Example" HorizontalAlignment="Center" Margin="10" />
    <StackPanel Orientation="Vertical" HorizontalAlignment="Left">
        <TextBlock Text="Select a means of combination:" />
        <RadioButton Name="rbSelectMany" Content="Use SelectMany" />
        <RadioButton Name="rbForkJoin" Content="Use ForkJoin" />
        <RadioButton Name="rbCombineLatest" Content="Use CombineLatest" />                               
        <TextBlock Text="Select an option from each group below, and then experiment with changing your selections." TextWrapping="Wrap" Margin="0,10,0,0"  />
    </StackPanel>            
    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" >
        <SilverlightApp:ChoiceControl Name="choiceControl1" Heading="Group 1"/>
        <SilverlightApp:ChoiceControl Name="choiceControl2" Heading="Group 2"/>
        <SilverlightApp:ChoiceControl Name="choiceControl3" Heading="Group 3"/>
    </StackPanel>
    <TextBlock Name="statusText" Text="Status Text" TextAlignment="Center"></TextBlock>
</StackPanel>
public MainPage()
{
    InitializeComponent();

    IObservable<IObservable<int[]>> observerSelections = Observable.Merge(
        Observable.FromEvent<RoutedEventArgs>(rbSelectMany, "Checked")
            .Select(_ => from s1 in choiceControl1.OptionSelections
                         from s2 in choiceControl2.OptionSelections
                         from s3 in choiceControl3.OptionSelections
                         select new[] { s1, s2, s3 }),
        Observable.FromEvent<RoutedEventArgs>(rbForkJoin, "Checked")
            .Select(_ => Observable.ForkJoin(choiceControl1.OptionSelections, choiceControl2.OptionSelections, choiceControl3.OptionSelections)),
        Observable.FromEvent<RoutedEventArgs>(rbCombineLatest, "Checked")
            .Select(_ => choiceControl1.OptionSelections
                            .CombineLatest(choiceControl2.OptionSelections, (i, j) => new[] { i, j })
                            .CombineLatest(choiceControl3.OptionSelections, (array, k) => new[] { array[0], array[1], k }))
        );

    IDisposable subscription = null;
    observerSelections.Subscribe(observer =>
                                 {
                                     if (subscription != null)
                                         subscription.Dispose();

                                     choiceControl1.ClearAll();
                                     choiceControl2.ClearAll();
                                     choiceControl3.ClearAll();
                                     statusText.Text = string.Empty;

                                     subscription = observer.Subscribe(UpdateSelectedOptions);                                                                               
                                 });
}

private void UpdateSelectedOptions(int[] values)
{
    statusText.Text = string.Format("Option {0}, Option {1}, Option {2}",
                                    values[0], values[1], values[2]);
}

If you recall in the previous post, I was using the Observable.Merge method to glue three separate events together into one observable, and today I’m using the same technique on the radio buttons that let you swap between the different implementations. The difference in this case is that instead of simply returning an integer, I’m returning an IObservable<int[]>, so the result is an IObservable<IObservable<int[]>>. I then subscribe to this so that each time the selected implementation changes, the existing subscription is disposed, the selections are cleared and a new subscription is instated.

What do you think? Am I being too clever for my own good here? Is an IObservable<IObservable<T>> taking it too far? Let me know in the comments!

The code for this post has been tagged in my github repository.

Sunday 9 August 2009

Reacting to the Reactive Framework: Part 6

Before I dive into today’s code, there are two things worth mentioning:

  1. If you haven’t already, make haste to Jafar Husain’s blog. Jafar is posting truly interesting examples of using the Reactive Framework - none of the ‘hello world’ fare I’m peddling here.
  2. I mentioned in my previous post that I was having trouble using System.Reactive.dll with non-silverlight projects. Jb Evain has the goods on how you might get around this if you were so inclined. For now I’ll stick with Silverlight as its well suited for including interactive demonstrations in my blog posts.

Today I want to look at combining events using the Reactive Framework (Rx). Here is the demo app for today: (Once again I will remind readers using RSS readers to view this post in a proper browser window so you can see the embedded Silverlight app.)

The idea behind this example is that I am not interested in the selections until an option has been selected from all three groups. From then on, I’d like to be informed whenever the selection changes. This application consists of three instances of a user control, one for each group. Here is the relevant markup and code for the user control:

<StackPanel Margin="20">
    <TextBlock Text="{Binding Heading}" Margin="5" />
    <RadioButton Name="optionButton1" Content="Option 1" />
    <RadioButton Name="optionButton2" Content="Option 2" />
    <RadioButton Name="optionButton3" Content="Option 3" />
</StackPanel>
public ChoiceControl()
{
    InitializeComponent();

    OptionSelections = Observable.Merge(
        Observable.FromEvent<RoutedEventArgs>(optionButton1, "Checked").Select(_ => 1),
        Observable.FromEvent<RoutedEventArgs>(optionButton2, "Checked").Select(_ => 2),
        Observable.FromEvent<RoutedEventArgs>(optionButton3, "Checked").Select(_ => 3)
        );
}

public IObservable<int> OptionSelections { get; private set; }

As you can see, I am creating IObservables from the Checked events of the radio buttons. I’m using the Select method to make observables that simply return the index, and I’m merging those together into one IObservable<int>. So if the user checked option 1, then option 2, then option 3, the OptionSelections would call OnNext on its subscribed observers with values 1, 2 and 3 respectively.

As I mentioned before, this app has 3 instances of this user control:

<StackPanel x:Name="LayoutRoot" Margin="50" Background="Azure" Width="400">
    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" >
        <SilverlightApp:ChoiceControl Name="choiceControl1" Heading="Choice 1"/>
        <SilverlightApp:ChoiceControl Name="choiceControl2" Heading="Choice 2"/>
        <SilverlightApp:ChoiceControl Name="choiceControl3" Heading="Choice 3"/>
    </StackPanel>
    <TextBlock Name="statusText" TextAlignment="Center"></TextBlock>
</StackPanel>

Now somehow I want to subscribe to the OptionSelections from each control, in such a way that I don’t see any results raised until an option from all three groups have been selected. After a few failed attempts, I stumbled upon this solution:

public MainPage()
{
    InitializeComponent();

    string formatString = "Option {0}, Option {1}, Option {2}";

    var selections = choiceControl1.OptionSelections
        .CombineLatest(choiceControl2.OptionSelections, (i, j) => new[] { i, j })
        .CombineLatest(choiceControl3.OptionSelections, (array, k) => new[] { array[0], array[1], k });

    selections.Subscribe(values => statusText.Text = string.Format(formatString, values[0], values[1], values[2]));            

}

I’m using the CombineLatest extension method, but it will only combine two IObservables so I have to use it twice. The first call returns an IObservable<int[]>, where the array contains two elements. I then combine that with the option selections from choiceControl3, again returning an IObservable<int[]>, this time with all 3 selections. The final step is simply to subscribe to the resulting observable and display the message.

This solution of combining once into an array of two elements and then combining again into an array of three doesn’t seem particularly elegant, but so far I haven’t managed to find anything better. Perhaps I could write a version of CombineLatest that takes more than two observables and hides this ugliness? Its probably not necessary – there’s a good chance that the perfect method already exists, and I’ve just missed it.

In my next post, I’d like to demonstrate some of the failed attempts I made before I found CombineLatest. This demo will give me a chance to create my first IObservable<IObservable<T>>, which I’ve been itching to do :)

Today’s code has been tagged in my github repository.