In my quest for recursive sub-model lists, I ended up rewriting most of Elmish.WPF from scratch, cleaning up some cruft and making names and types clearer (IMHO). I find the new implementation easier to grasp (says the authorā¦), and I think you'll find there's quite a bit of attention to detail. In any case, Iād like to explain why and would like an open discussion on how we should proceed (use all/most of the changes vs. rewriting the features that are actually new into the existing implementation).
The new implementation is here. I havenāt made a PR because the diff would be pointless, but let me know if you want one anyway.
My implementation solves/supersedes #5, #38, #41, #42, #43, #44, and #45.
Disclaimer: The views expressed here are subjective. It is not my intention to step on any toes. But I donāt want to waste anyoneās time sugarcoating or beating around the bush, so Iāll call āem like I see āem.
Simply put, I had trouble understanding the existing implementation. Not because thereās anything fundamentally wrong with it ā itās great, and mine is fundamentally similar. But all the type aliases confounded me, and itās clear that the ViewModelBase, ViewBinding, ViewBindings, Variable, and PropertyAccessor types werenāt made with sub-model usage in mind, or lazy evaluation (which I also wanted).
So I did the only thing that made sense to me in order to understand the architecture: Using the existing implementation as a guide, I rewrote Elmish.WPF mostly from scratch, only complexifying as needed and in the direction that made most sense to me considering the final features I had in mind.
My rewrite turned out very similar, but types and functions relating to bindings and the view model is refactored quite a bit compared to the existing implementation.
BindingSpecData is generated by the Binding functions and contains everything the view model needs to set up a binding. Binding contains all data used by active bindings in the view model (including mutable types / ref cells where relevant).ViewModelBase (now called ViewModel) has been rewritten from scratch (but works in fundamentally the same way).Internal namespace.Note that I donāt care about binary breaking changes here, only source breaking changes. These changes can of course be tweaked/reverted.
twoWayValidation is called twoWayIfValid (because thatās what it is, and it clearly separates it from the new twoWayValidate)oneWayMap is called oneWayLazy (its implementation has changed, and the use case has expanded, but is similar)cmd and cmdIf have been renamed paramCmd and paramCmdIf (the old names exist but have new signatures / use-cases, see below)model has been renamed subModel because itās more clearProgram.runDebugWindow has been removed in favour of Program.runWithConfig (see below)Binding moduleBinding.oneWayLazy replaces oneWayMap and is more efficientBinding.oneWaySeq for avoiding re-rendering whole lists etc. each time an item changesBinding.twoWayValidate is just like twoWay but with a separate validate function. The benefits of this is explained in the documentation and the Validation sample.Binding.cmd and Binding.cmdIf depend only on the model, which allows us to trigger CanExecuteChanged only when changed instead of for each updateBinding.cmdIfValid allows better re-use and type safety, see the documentation for detailsBinding.paramCmd and Binding.paramCmdIf take an extra parameter to control whether to use CommandManager.RequerySuggested. In many cases (when not binding CommandParameter to another UI control) itās not needed, and it triggers all. the. time., so this could be a good optimization.Binding.subModelSeq, perhaps the central new feature.Binding.subModelOpt, allowing a sub-model wrapped in option. This makes it easier to make illegal states unrepresentable in the model (though it's still limited by the fact that Elmish.WPF uses bindings instead of dynamically generated views). None models are surfaced as null to the XAML bindings.Program.runWithConfig function that allows extensible configuration (e.g. log to trace or console or both, in the future perhaps verbosity level and who knows what else).Application instance if it exists)'model -> obj is much more clear than Getter<'model>, and there is five of these (and more for new functions), so I constantly had to look them up.try/with around the GetSetValidate setter. I don't see why it should be a good idea to just drop all exceptions.Cmd.ofAsyncCallback ā I donāt know the use-case for this. Wouldnāt it be better to just implement it with try ... with inside the async, and couldnāt users easily do that themselves?Program.runWindowWith ā I donāt see why we need a separate function just to pass an initial parameter to the init function, because you can just do that with partial application (or wrapping in a lambda) before calling Program.runWindow. IMHO thatās easier for consumers of the library, because itās one less thing to learn. But I might have missed a valid use-case that requires this function.Performance and DesignDataContext samplesAs said, I encourage you to take a careful look at the implementation.
I like the rewrite (well duh), and think itās clearer for newcomers wanting to contribute to Elmish.WPF as well as making future development slightly easier due to the architecture being a bit more clear (the binding union types and ViewModel implementation). But I donāt want it hanging around on GitHub alongside Elmish.WPF doing exactly the same thing, potentially splitting the community.
How do we move forward from here? I see a couple of paths:
Any input is most welcome.
By the way, I'm also considering adding Binding.oneWaySeq that's just like oneWay, but for sequences, and is backed by an ObservableCollection (which is updated just like subModelSeq), so that the entire list is not re-rendered/re-bound on each update (as AFAIK it would be with oneWay). Should be a simple addition.
Great work! Would love to see this incorporated to the repo, the breaking changes seem very reasonable and the added functionality is just great.
That looks great, I like your take on validations updating the model too. I will test it furtherly, expect some feedback.
Binding.oneWaySeq is now implemented. Check out the sample, the performance increase is drastic over Binding.oneWay.
I have now implemented Binding.subModelOpt, which allows a sub-model wrapped in option. This makes it easier to make illegal states unrepresentable in the model (though it's still limited by the fact that Elmish.WPF uses bindings instead of dynamically generated views). None models are surfaced as null to the XAML bindings. I'll make a sample sooner or later.
Sorry for the delay, none of us are available at the moment to take a good care of it. How would you like to take on the stewardship of this repo? I'll send you an invite into the elmish org where hopefully you can get right exposure and find collaborators to move it forward.
Part of me would like that, but my time too is limited, so I can't promise that I'll be able to make regular contributions. Let me think on that for a bit.
There's no "till death do us apart" clause, so you don't have anything to loose ;) Come join us!
Great, sure! š
Awesome! If you want to chat interactively we can be found either in fable gitter or in fsharp.org slack.
Closed by #48
A big, tasty beer for @cmeeren and a big thank you, will test in a production project very soon, so happy to switch to a fully immutable model from a Frankenstein immutable and VM collection one.
Thanks! Looking forward to that switch myself in a pet project of mine.
Note that there are some issues with the deployment; it seems only the symbol package got published (it contains everything the non-symbol package contains, so I think it'll work fine, but I'll investigate what went wrong.)
Most helpful comment
Awesome! If you want to chat interactively we can be found either in fable gitter or in fsharp.org slack.