Elmish.wpf: Implement the strategy design pattern for VmBinding

Created on 13 Oct 2019  路  9Comments  路  Source: elmish/Elmish.WPF

Writing the code in #149 was more difficult than it would have been if we had stronger types in ViewModel<'model, 'msg>. Nearly all the types in all the bindings are boxed to obj before they get used by library authors in ViewModel<'model, 'msg>. Replacing one thing of (compile-time) type obj for another satisfies the compiler but would (typically) throw an exception at runtime.

I think the way forward to to have a view model type for every binding type. For example, the view model for SubModelSeqBinding would be SubModelSeqViewModel<'model, 'msg, 'bindingModel, 'bindingMsg, 'id>. These view models still need to expose a common set of behaviors, and there is common code that still needs somewhere to live. Having SubModelSeqViewModel<'model, 'msg, 'bindingModel, 'bindingMsg, 'id> extend ViewModel<'model, 'msg> would help to minimize the change while also exposing the common set of methods and giving a home some common behavior.

There are many ways to group things. I often see things grouped either syntactically or semantically. Currently, I would describe the view model code as being grouped syntactically. For example, all of the different cases of updateValue are located next to each other in the source code (within a pattern match on the binding type). I am suggesting that we group the code semantically. For example, SubModelSeqViewModel<'model, 'msg, 'bindingModel, 'bindingMsg, 'id> would implement updateValue and other methods like TryGetMember and TrySetMember by just having the code that is currently contained in the SubModelSeq cases of the respective pattern matches.

Speaking more technically, the branching currently expressed through pattern matching would be pushed "down" and performed by the runtime when it goes through the virtual method table while trying to determine which implementation to execute among the types in the resulting inheritance hierarchy. I first encountered this idea in the Google Clean Code talk titled Inheritance, Polymorphism, & Testing.

This change would be invisible to users. The main disadvantage I see is that pattern match cases that currently share an implemention would either have to repeat/duplicate the implementation or extract the behavior to some function that they all call. However, I don't think this matters much because there are very few cases that share an implementation, and when they do, the implementations are very short.

Most helpful comment

@cmeeren, that's a good argument of course, and I'm not advocating anything. Just pointing to other work in this area that perhaps contains pieces of useful information for you.

All 9 comments

Repeating my comment from issue 13 : I suspect Reed Copsey's Christmas Trees in WPF, 2017 Update can be relevant in this regard. A "view model" is used, and magic strings are replaced with quotations.

Edit: And I think I've mentioned elsewhere that the F# MVC framework by Dmitry A. Morozov does something similar.

Thanks! I will definitely review that.

Can you provide a link to Morozov's work?

@bender2k14 This sounds like a good idea. Feel free to work on it!

@BentTranberg: I'm sceptical of anything involving quotations due to performance concerns. Also I think this can work well in the form @bender2k14 describes. But of course, I may have overlooked something.

Links for F# MVC framework by Dmitry A. Morozov

http://fsprojects.github.io/FSharp.Desktop.UI/
https://github.com/dmitry-a-morozov/fsharp-wpf-mvc-series
https://github.com/dmitry-a-morozov/fsharp-wpf-mvc-series/wiki

I think that last link describes step by step how he developed the thing.

@cmeeren, that's a good argument of course, and I'm not advocating anything. Just pointing to other work in this area that perhaps contains pieces of useful information for you.

@bender2k14 After thinking a bit more about this: Would it really be a good idea? Wouldn't this mean we have one VM instance per binding, potentially causing worse performance (higher memory consumption and GC pressure)?

Also, would it work at all? Bindings from WPF are to properties on a single object (the DataContext). How can you bind to properties of different types, if they are on separate objects?

Huh, great questions. I now believe that I was thinking about (and thus explaining) this incorrectly.

The good news is that I am still confident about the end result, which will be identical in behavior, nearly identical in performance (including memory), and have significantly better typing.

Another way to describe what I want to do is to implement the strategy design pattern.

So I don't need to change ViewModel<'model, 'msg> or have anything extend it.

Instead, I want to replace...
https://github.com/elmish/Elmish.WPF/blob/789cbc304719f34f931a451dafa1b58141af833e/src/Elmish.WPF/ViewModel.fs#L89-L100

...with an interface and 11 implementations (one for each case in the current DU), where the interface has methods like updateValue, TryGetMember and TrySetMember.

For example, the last line of...
https://github.com/elmish/Elmish.WPF/blob/789cbc304719f34f931a451dafa1b58141af833e/src/Elmish.WPF/ViewModel.fs#L579-L582

...would become something like...

|> Seq.filter (fun (Kvp (name, binding)) -> binding.UpdateValue(name, newModel))

...and the implementation of this interface for the OneWay case would look something like

type OneWayBinding<'model, 'a>(get: 'model -> 'a) =
  interface VmBinding with 
    member _.UpdateValue(_, newModel) = 
      get currentModel <> get newModel
    // other methods omitted

This example shows that the (strong) type 'a is used instead of obj (after boxing) like we do now. This might be enough to propagate any additional type constrations back to the user (as I mentioned here), but not sure about that though...I can't see the whole thing yet in my mind.

Thanks! That makes a lot more sense. 馃憤

I'll be very glad to get rid of considerable amounts of obj in the internals of Elmish.WPF!

I think I see how this can be done now.

The main idea is to define all the binding-specific behavior (currently in ViewModel<'model, 'msg>) in terms is a the various BindingData<...> before their type parameters (other than 'model and 'msg) are boxed. Then the record in each case of theVmBinding<...> will hold onto the appropriate record instance of BindingData<...>.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

KingKnecht picture KingKnecht  路  6Comments

BentTranberg picture BentTranberg  路  4Comments

TysonMN picture TysonMN  路  7Comments

TysonMN picture TysonMN  路  13Comments

cmeeren picture cmeeren  路  12Comments