Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Immutable State - Propagating Changes to the GUI Efficiently

In a previous question I asked how to idiomatically implement an observer pattern for an F# application. My application now uses a MailboxProcessor as reccomended and I've created some helper functions to create sub-MailboxProcessor's etc. However, I'm at a mental block when it comes to specific case scenarios w.r.t. GUI binding.

Lets say I have a model as such:

type Document = {
    Contents : seq<DocumentObject>
}

And the GUI (WPF, XAML) requires binding like so:

interface IMainWindowViewModel
{
    IEnumerable<Control> ContentViews { get; }
}

Each ViewModel for each Control will require a DocumentObject (its underlying model) and a way of knowing if it has changed. I supply this as a sub-MailboxProcessor<DocumentObject> so that changes may be propagated correctly, I'm moderately confident this pattern works. Essentially, it maps the service outputs and wraps modification requests (outer interface example below):

let subSvc = generateSubSvc svc (fun doc -> doc.Contents[0]) (fun f -> fun oldDoc -> { oldDoc with Contents[0] = f Contents[0] })
let viewModel = new SomeDocObjViewModel(docObjSvc)
new DocObjView(viewModel)

Now, imagine a modification command now deletes a DocumentObject from MyDocument. The top-level MailboxProcessor now echoes the change to IMainWindowViewModel using it's IEvent<MyDocument>. And here's where my problems begin.

My IMainWindowViewModel doesn't really know which DocumentObject has been deleted. Only that there's a new Document and it has to deal with it. There may be ways of it figuring out but it never really knows directly. This can force me down the path of having to either re-create all the Control's for all DocumentObject's to be safe (inefficient). There are additional problems (such as dangling subSvc's) which I also haven't mentioned here for brevity.

Normally, these kind of dynamic changes would be dealt with something like an ObservableCollection<DocumentObject> which is then mapped into an ObservableCollection<Control>. This comes with all the caveats of shared mutable state and is a little 'hackish'; however, it does do the job.

Ideally, I'd like a 'pure' model, free from the trappings of PropertyChanged and ObservableCollections, what kind of patterns in F# would satisfy this need? Where is it appropriate to draw the line between idiomatic and realistic?

like image 517
Adam Kewley Avatar asked Nov 11 '13 09:11

Adam Kewley


1 Answers

Have you considered using the Reactive Extensions (and Reactive UI further down the road) for the purpose of modelling mutable state (read: your model properties over time) in a functional way?

I don't see anything wrong technically to use an ObservableCollection in your model. After all, you need to track collection changes. You could do it on your own, but it looks like you can save yourself a lot of trouble reinventing the observable collection, unless you have a very specific reason to avoid the ObservableCollection class.

Also, using MailboxProcessor seems a bit overkill, since you could just use a Subject (from Rx) to publish and expose it as an IObservable to subscribe to 'messages':

type TheModel() =
    let charactersCountSubject = new Subject()
    let downloadDocument (* ... *) = async {
        let! text = // ...
        charactersCountSubject.OnNext(text.Length)
    }        

    member val CharactersCount = charactersCountSubject.AsObservable() with get

type TheViewModel(model : TheModel) =
    // ...
    member val IsTooManyCharacters = model.CharactersCount.Select((>) 42)

Of course since we're talking about WPF, the view-model should implement INPC. There are different approaches, but whichever one you take, the ReactiveUI has a lot of convenient tools.

For example the CreateDerivedCollection extension method that solves one of the problems you've mentioned:

documents.CreateDerivedCollection(fun x -> (* ... map Document to Control ... *))

This will take your documents observable collection, and make another observable collection out of it (actually a ReactiveCollection) that will have documents mapped to controls.

like image 72
MisterMetaphor Avatar answered Sep 30 '22 04:09

MisterMetaphor