Reactive Programming, Posting # 9
Reactive Extensions (Rx) has a number of operators that we’ve looked at already, including Select, Throttle, Subscribe and so forth. While SelectMany is not the most important Rx operator, it is surely the most powerful.
SelectMany’s principal job is to allow you to select against an IObservable<T>, run a func<u> and get back an IObservable<u>
IObservable<U> SelectMany(IObservable<T>, func<U>)
Read that in your head “SelectMany takes an IObservable of T and a func (funk) of U and returns an IObservable of U.” |
Today we’ll take a look at the case of having a function that returns an IObservable and you wish to then pass the results to a function that returns an IObservable. That is, the case where you wan to chain calls. If you were to use Select, you would end up with an IObservable<IObservable<T>> which is not what you want. SelectMany flattens that for you.
To see this at work, create a new WPF application (though the same can be done with Silverlight and with the Phone). In this application we will pass a phrase to BingTranslator for translation into Japanese, and then we’ll send it back to BingTranslator for translation back into English.
To do this, you’ll need to get a Bing API key.
Begin by creating a very simple UI consisting of a TextBox (to take the phrase we want to translate) and a TextBlock (to display the results) and a button (to set off the action):
<Window x:Class="Translation2.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="36*" /> <RowDefinition Height="242*" /> <RowDefinition Height="33*" /> </Grid.RowDefinitions> <TextBox Name="Input" Text="This is a test" Margin="0,0,0,124" Grid.RowSpan="2" /> <TextBox TextWrapping="Wrap" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="10,17,10,10" Name="Output" Grid.Row="1" /> <Button Name="Go" Content="Go" Grid.Row="2" HorizontalAlignment="Center" VerticalAlignment="Center" /> </Grid> </Window>
In the code-behind, create a constant for your API key,
const string APP_ID = "6xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxC";
Create an event handler and in that event handler you’ll invoke a private helper method Translate that will take your input text, your input language and your output language,
public MainWindow( ) { InitializeComponent( ); Go.Click += new RoutedEventHandler( Go_Click ); } private void Go_Click( object sender, RoutedEventArgs e ) { Translate( this.Input.Text, "en", "ja" )
Create a reference to the Bing Translation service at http://api.microsofttranslator.com/V2/Soap.svc
Enter the URL [1], click the Go button [2], enter a name for the service [3] and be sure to click on the advanced button [4] so that you can check Generate asynchronous operations [5]
Use the object browser to examine the myriad methods for translation. What we want in this case is to instantiate a Language Service,
private IObservable<string> Translate( string text, string from, string to ) { var lsc = new LanguageServiceClient( ) as LanguageService;
We can then use the language service to call the asynchronous BeginTranslate and EndTranslate, or, better, we can use Rx’s FromAsyncPattern to simplify the asynchronous call. To do so, we examine the non-asynchronous version to see how many parameters it has (six strings) and to find its return value (a string).
At the moment, the phone version does not allow you to create a func<6 params> – it is limited to 4, which is why we are doing this in WPF. |
var myFunc = Observable.FromAsyncPattern< string, string, string, string, string, string, string>( lsc.BeginTranslate, lsc.EndTranslate );
We can invoke that function by pasing in the APP_ID we created above, the text we want to translate, the language we want to translate from and the language we want to translate to. The final two parameters can be null,
return myFunc( APP_ID, text, from, to, null, null ); }
We invoke Translate by passing in the text and the two letter acronym used to identify the original and translated languages
Translate( this.Input.Text, "en", "ja" )
As you can see from the return type of our Translate method, it returns an IObservable<String>. I can’t use select to then send the results back to Translate or I’ll end up with an IObservable of IObservable. This is where SelectMany does its magic,
.SelectMany( x => Translate( x, "ja", "en" ) )
The result is an IObservable<String> which I can then subscribe to. Here is the complete source code,
using System; using System.Linq; using System.Windows; using Translation2.BingTranslator; namespace Translation2 { public partial class MainWindow : Window { const string APP_ID = "6DC862858476B72127BC381A1EAADABBC281C88C"; public MainWindow( ) { InitializeComponent( ); Go.Click += new RoutedEventHandler( Go_Click ); } private void Go_Click( object sender, RoutedEventArgs e ) { Translate( this.Input.Text, "en", "ja" ) .SelectMany( x => Translate( x, "ja", "en" ) ) .ObserveOnDispatcher( ) .Subscribe( x => Output.Text = x ); } private IObservable<string> Translate( string text, string from, string to ) { var lsc = new LanguageServiceClient( ) as LanguageService; var myFunc = Observable.FromAsyncPattern< string, string, string, string, string, string, string>( lsc.BeginTranslate, lsc.EndTranslate ); return myFunc( APP_ID, text, from, to, null, null ); } } }
In the next example, we’ll build on this and create an application that translates a single phrase into multiple languages.
5 Responses to Select Many: Reactive Extensions’ Mother Of All Operators [Chaining]