Search This Blog

Var, View, Lens, ListModel in UI.Next

Var, View, Lens, ListModel in UI.Next

Last week I needed to make a two way binding for a record with nested lists.
More precisely, I needed to observe all changes on this record.
This changes included normal members but also lists and I needed to observe changes like adding and removing items.

It took me a week to come out with a solution where I had to iterate multiple times to get to it.
I started with something which was far from ideal then had a conversation on WebSharper forum with Loïc and István and came out with other better solutions.

The process was as beneficial as the solution. So today I will take another approach for this blog post and instead of presenting the final solution directly, I will walk you through all the steps I took to finally come up with the solution.
And as usual, the code is available on GitHub.

By doing this, we can observe every member of the ReactiveBook.
To be able to react to any change, we need to construct a view of this record.
We do that by combining all the views of the member and make one single view for the ReactiveBook.

And like that when we change anything in rvBook, it will be reflected in the doc.

What is wrong with that?

Although it works, the error here is that I transformed a record to a totally mutable record and
passing around Var is not the recommended approach. Also creating a duplicate record feels wrong.

What I wanted from the beginning was to be able to create a Var.Create Book and just use that directly.
I didn’t want to have to bother with a ReactiveBook.

So I requested for some help and Loïc pointed to me that there was a set functions exactly for my needs called Lenses.

This is exactly the kind of situation you would use lensing for. The type IRef<'T> is an abstract class that is implemented by Var<'T>, but also returned by the Lens method which creates a bidirectional binding into another IRef<'T>

The right way - lensing into members

What are lenses?

We create a txt reactive variable and bind it to a doc.
We can set the txt by using Var.Set.

Var.Set txt "new text!"

By doing that, the changes are directly propagated to the doc.
If we want to react to changes in records, we can do the same:

typeMyRecord = { Content: string }let r =
Var.Create { Content = "" }

Because records are immutable, if you want to react to changes in the Content member, you need to recreate the whole record.

Var.Set r { Content = "Hello world" }

But if you remember, our Book can have multiple Pages and each one can have Comments.
Imagine what we would need to do if we wanted to change the content of a Comment.
Lucky us, we have Lens.

Lenses in WebSharper allow us to target a particular member and extract a IRef<_> out of it.
IRef<_> is the interface implemented by Var and we can use it with a set of function to create inputs like Doc.Input.

The signature of Lens on Var is:

IRef<'a>.Lens :: ('a -> 'b) -> ('a -> 'b -> 'a) -> IRef<'b>

The first function 'a -> 'b is used to select the member on which we want to lens.
And the second function 'a -> 'b -> 'a is used to update the current record of type 'a with the value set of type 'b.
The Lens returns a reactive variable of 'b which is the type of the member we lens into.

Since a IRef<_> is returned, we can lens another level and this will also return another IRef<_> and we can continue indefinitely like that.

So if we wanted to have a reactive variable on Comment.Content, from the Book I can lens into a particular Page then lens into a particular Comment and get out a IRef<string>.

Change our model

Now we can throw away the ReactiveBook and build some Lenses helpers using the Lens on Book!

And we can also throw away all the methods to create a view. Since we only deal with bookwe now have successfuly reduced the number Vars to only one and also remove the “reactive” copy of Book.
We started with a copy of the original record with all the members being Var and we now end up with only one Var.
We eliminated a record full of Vars!

The optimised way - optimising with ListModel

We now have a bidirectional binding with our Book type. But we are dealing with list and when anything is changed, we recreate the whole Book.

István pointed to me that to optimise that I could make use of ListModel.

You could define a ListModel of books then lens all the way down to the content of a comment. So using an immutable model your code might look like the following: http://try.websharper.com/snippet/qwe2/00007D. But there is an issue here: since our model is immutable, we have to copy and update the whole thing even if we just change one comment. So, while this clean and pretty, if you have a huge amounts of books and pages and comments this will get pretty slow. What i would do in that case is to define pages and comments to be ListModel<int, Page> and ListModel<int, Comment>

What are ListModel?

ListModel are used when we deal with reactive list. Instead of using Var<string list>, we can use ListModel<string, string>.

To create a ListModel, we use ListModel.Create which has a type:

ListModel.Create :: 'a -> 'key -> seq<'a> -> ListModel<'key, 'a>

The first type represents the key and the second represents the model.
The key is used to target a particular instance in the list.

ListModels are cool because they offer a set of helpful functions like Add, Remove, RemoveBy and most importantly when observing them, we can use MapSeqCachedBy or Doc.BindSeqCached which are optimised to do some clever caching for the elements which have not changed yet.
Also, ListModel has a special lens LensInto which allows us to get our a IRef<_> from a member of an element of the list.

Change our model

With ListModel we endup with a even simpler model.
Let’s change the list to ListModel.

With that we eliminated the extra Lens functions that we needed for our list types because we can directly use the Lens and LensInto functions exposed by ListModel. On top of that we can use Doc.BindSeqCached when rendering the list and get better performance.

Wonderful! We now have a model that can be observed on any members including the lists.

Conclusion

I hope this post showed the importance of getting people to review your code. When I started programming, I used to be stressed over people reviewing my code. But after I passed that mental barrier, I rapidly understood that reviews from trusted entities were extremely beneficial to write better software but also for me to improve.

We started with an idea and a bad implementation. After few rounds of conversation with the guys working on WebSharper, we ended up with a very nice solution and we ended up with understanding much better some functionalities of WebSharper.

We now know that we should restrict the number of Var that we use. Then when dealing with list we can use ListModel. Finally we know that we can use Lenses to observe members of records. All this thoughts would not have came up if I would have stopped at the first solution. Hope you enjoyed this post, if you have any comments, leave it here or hit me on Twitter @Kimserey_Lam. Thanks for reading!

Comments

We came up with pretty much the same approach, namely combining lenses with reactive variables, a few months ago and have written a few tiny JavaScript libraries to make that possible. Using those libs we have written a non-trivial SPA, a custom CMS, that is already in production.

The combination of lenses and reactive variables, we are calling those "atoms", really works absolutely beautifully! It is just amazing how simple everything becomes and that you can just plug-and-play components.

I'm currently in the process of documenting the approach better. You can find the documentation and libraries from the calmm-js GitHub project.

Post a Comment

Popular posts from this blog

Absolute layout and relative layout Xamarin formsIn Xamarin Forms, layouts are used to position elements in the page.
There are multiple layouts available like stack layout, grid layout, table layout, absolute layout and relative layout.
Stack layout is straight to grasp the concept, we stack elements one by one horizontally or vertically.
Grid layout is also straight forward, we define rows and columns and we place the elements by indicating the row index and column index.
On the other hand, absolute layout and relative layout isn’t that straight forward because in Xamarin it is NOT the same as positions in CSS.So today, we will see how and when we can use absolute layout and/or relative layout in our advantage.
This post is composed by two parts: 1. Absolute layout
2. Relative layout
1. Absolute layoutThe first thing to understand is that absolute layout has nothing to do with CSS absolute position.
In Xamarin Forms. absolute layout allows us to position elements in the page by pre…

Build an accordion view in Xamarin.FormsFew weeks ago I posted about absolute and relative layouts.
Layouts are called Layouts because they contain children which are placed in a particular way.
Xamarin.Forms has a lot of layouts and views to structure pages like grid, table view or list view.Today I would like to show you how we can use some of these basic views to build an Accordion view.Here’s a preview of the Accordion view:Full source code available on GitHub - https://github.com/Kimserey/AccordionView/blob/master/Accordion/AccordionView.csThis post will be composed of four steps:Create a BindablePropertyDefine the accordion expandable sectionDefine the accordion viewUsage sample1. Create a BindablePropertyAs we saw in one of my previous post,
Xamarin.Forms works around data bindings.
View properties are bound to viewmodel properties.Default views like Label, Button, ListView or TableView come with the necessary bindable properties like BackgroundColor, TextColor, ItemsSource, It…

Use Font Awesome from your Xamarin.Forms projectIcons are important in mobile applications where space is limited.
We use icon everywhere to convey action intent like + would be to add an item or a bin would be to delete one.
There are two ways to add icons to our mobile app:with imageswith fontsToday we will see the second option - how we can add Font awesome to our Xamarin.Android project and how we can use it from Xamarin.Forms.
This post will be composed by three parts: 1. Why Font Awesome
2. Add Font Awesome to the Droid project
3. Use with Xamarin.Forms
1. Why Font AwesomeIf you are familiar with Web development, you must have encountered Font awesome http://fontawesome.io/.
It is an icon font containing lots of icon for every type of usage.
The advantaged of using a font is that we can leverage the text options, like text color or text size, to easily update the style of the icon to match our app.Let’s see how we can bring Font Awesome to the Droid project.2. Add Font awesome…