This time we want to show a sequence of primes:
- 2
- 3
- 5
- 7
- 11
- …
module MathLib let IsPrime n = if n < 2L then false else seq{2L..n - 1L} |> Seq.exists(fun x -> n % x = 0L) |> not
Next we create an object to represent an element in the sequence:
type Element(sequenceNumber:int, value:int64) = member x.SequenceNumber = sequenceNumber member x.Value = valueNow it is possible to create a function that will determine the next element in the sequence of primes: let rec nextPrime (element:Element) =
if IsPrime (element.Value + 1L) then
Element(element.SequenceNumber + 1, element.Value + 1L)
else
Element(element.SequenceNumber, element.Value + 1L) |> nextPrime
Next we create a sequence of all primes (details at page 320 of Real-World Functional Programming by Tomas Petricek and with Jon Skeet, buy this book!!):
let rec primeSequence = seq { yield Element(1, 2L) for e in primeSequence do yield e |> nextPrime }
and test the model by adding a test to the Script.fsx file.
#load "Lib.fs" MathLib.primeSequence |> Seq.take 5 |> Seq.iter( fun r -> (printfn "%d. %d" r.SequenceNumber r.Value))
When we run the code in F# Interactive we get the expected result:
Compile the project and add a reference from the C# project to the F# project.
Next step: the Model. We contact the UI designer and he sends us the following XAML:
<GridView ItemsSource="{Binding}" > <GridView.ItemTemplate> <DataTemplate> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <TextBlock Grid.Column="0" Text="{Binding SequenceNumber}"/> <TextBlock Grid.Column="1" Text="->"/> <TextBlock Grid.Column="2" Text="{Binding Value}"/> </Grid> </DataTemplate> </GridView.ItemTemplate> </GridView>
We past it in the Grid of the MainPage. Gridview is one of the Windows Store App controls that can display collections.
Next we update the MainPage code behind:
using System.Collections.ObjectModel;
and
public sealed partial class MainPage : Page { private ObservableCollection<MathLib.Element> Collection; public MainPage() { this.InitializeComponent(); Collection = new ObservableCollection<MathLib.Element>() { new MathLib.Element(1, 2), new MathLib.Element(2, 3), new MathLib.Element(3, 5), new MathLib.Element(4, 7), new MathLib.Element(5, 11) }; this.DataContext = Collection; }
We have created an ObservableCollection class called Collection and added some elements so we can see the result of the binding.
F5:
It works as expected but it does not look good.
So we contact our designer again. This is the code he sends us:
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}"> <TextBlock Text="Primes" HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="42" Margin="80,40,0,0" /> <GridView x:Name="ItemListView" ItemsSource="{Binding}" Margin="80, 100, 80, 80"> <GridView.ItemsPanel> <ItemsPanelTemplate> <WrapGrid /> </ItemsPanelTemplate> </GridView.ItemsPanel> <GridView.ItemTemplate> <DataTemplate> <Grid Width="200" Height="60" Background="Green"> <Grid.ColumnDefinitions> <ColumnDefinition Width="60"/> <ColumnDefinition Width="20"/> <ColumnDefinition Width="120"/> </Grid.ColumnDefinitions> <TextBlock Grid.Column="0" Text="{Binding SequenceNumber}" HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="15" Foreground="Black" Margin="5,5,5,5" /> <TextBlock Grid.Column="2" Text="{Binding Value}" HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="24" Foreground="White" Margin="5,5,5,5"/> </Grid> </DataTemplate> </GridView.ItemTemplate> </GridView> </Grid>
and the result looks like this (replace the XAML and press F5):
We accept this design.
Remark: there are better ways to deal with design data in XAML: [WPF / MVVM] How to get data in “design time” ?
Now it is time to glue the Model and the View together, we will add the View Model code.
First we change the initialization of the Collection:
Collection = new ObservableCollection<MathLib.Element>();
and add a new F# file to the F# project called ViewModel.
We add the following code:
module ViewModel open System.Collections.ObjectModel let addElements(collection:ObservableCollection<MathLib.Element>) = collection.Clear() MathLib.primeSequence |> Seq.iter collection.Add
Build the solution and add the following code to event handler OnNavigatedTo of the MainPage:
protected override void OnNavigatedTo(NavigationEventArgs e) { ViewModel.addElements(this.Collection); }
F5 and the application never shows the Main Page:
When we reduce the number of elements to 100
let addElements(collection:ObservableCollection<MathLib.Element>) = collection.Clear() MathLib.primeSequence|> Seq.take 100 |> Seq.iter collection.Add
we get the expected result.
This solution will not work when we want to display a very large number of primes.
So we go the async/await way again.
protected async override void OnNavigatedTo(NavigationEventArgs e) { await ViewModel.addElementsTask(this.Collection); }
and change the ViewModel
module ViewModel open System.Collections.ObjectModelopen System.Threading.Tasks let addElements(collection:ObservableCollection<MathLib.Element>) = MathLib.primeSequence|> Seq.iter collection.Add let addElementsTask(collection:ObservableCollection<MathLib.Element>) = collection.Clear()
Task.Factory.StartNew(fun _ -> addElements collection);
This time we get error messages:
The C# project needs a reference to the F# library. We will not go that way, we solve the issue by creating a Task of int and add a dummy return value.
module ViewModel open System.Collections.ObjectModelopen System.Threading.Tasks let addElements(collection:ObservableCollection<MathLib.Element>) = MathLib.primeSequence |> Seq.iter collection.Add let addElementsTask(collection:ObservableCollection<MathLib.Element>) = collection.Clear() Task<int>.Factory.StartNew(fun _ -> addElements(collection) 0)
F5 again. This time we get an context exception.
So we add the context to the functions.
protected async override void OnNavigatedTo(NavigationEventArgs e) { await ViewModel.addElementsTask(this.Collection, System.Threading.SynchronizationContext.Current); }
and
module ViewModel
open System.Collections.ObjectModelopen System.Threadingopen System.Threading.Tasks
let addElements(collection:ObservableCollection<MathLib.Element>, context:SynchronizationContext) =
MathLib.primeSequence |> Seq.iter(fun x -> context.Post((fun _ -> collection.Add(x)) ,null))
let addElementsTask(collection:ObservableCollection<MathLib.Element>, context:SynchronizationContext) =
collection.Clear()
Task<int>.Factory.StartNew(fun _ ->
addElements(collection,context)
0)
and we get the desired result:
We end with two small remarks:
- In this post we used the ObservableCollection, this is a .NET solution. The WinRT way is to use IObservableVector<T>.
- We can replace addElementsTask by
let addElementsTaskMutable (collection:ObservableCollection<MathLib.Element>, synchronizationContext:SynchronizationContext) = collection.Clear() Task<int>.Factory.StartNew( fun _ -> let mutable e = MathLib.Element(0, 1L) while true do e <- MathLib.nextPrime e let x = e //make the result immutable synchronizationContext.Post((fun _ -> collection.Add(x)) ,null) 0 )This seems to improve the performance of the application quit a lot.
1 comment:
I've finished the series now and want to say thanks, again. The WinRT and F# issues you've addressed here will be a huge help to me, and I'm so glad that I worked through all of your tutorial before starting on my app.
I'm looking forward to doing this work. F# just rocks in .Net!
Post a Comment