Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide a lens library to enable deep update of immutable types #84

Open
JKronberger opened this issue Jul 29, 2016 · 4 comments
Open

Comments

@JKronberger
Copy link

Example I hope explains all. It could be done with reflection on the currently generated immutable update methods.

Company company = ...
var managerFirstNameUpdater = Company.Focus(p=>p.Manager.Name.FirstName);
Company c1 = managerFirstNameUpdater.Set(company,"Brad");
Assert(c1.Manager.Name.FirstName=="Brad");
Assert(managerFirstNameUpdater.Get(c1) =="Brad");
Assert(managerFirstNameUpdater.Get(company) !="Brad");

Writing focus for collections is a bit more tricky but I think can be done.

@AArnott
Copy link
Owner

AArnott commented Jul 29, 2016

It is certainly an interesting idea. I guess the Focus method would take an Expression tree in order to start working its magic.
I won't have time in the near term for this feature, but I might accept a PR.

@bradphelan
Copy link

bradphelan commented Aug 1, 2016

Yes that is correct. We have our own internal framework that does this and it works very nicely. Here is one of our test cases.

using System;
using FluentAssertions;
using Xunit;
using ReactiveUI;

namespace Weingartner.Lens.Spec
{
    class ImmutableX : Immutable
    {
        public int ImmutableInt { get; private set; }
    }
    class ImmutableZ : Immutable
    {
        public ImmutableX ImmutableX { get; private set; }

        public ImmutableZ()
        {
            ImmutableX = new ImmutableX();
        }
    }

    class INPCObject : ReactiveObject
    {
        ImmutableZ _Z;
        public ImmutableZ ImmutableZ
        {
            get { return _Z; }
            set { this.RaiseAndSetIfChanged(ref _Z, value); }
        }

        public INPCObject()
        {
            ImmutableZ = new ImmutableZ();
        }
    }

    /// <summary>
    /// Verify that the bridge from an INPC held
    /// immutable object to Lens works correctly
    /// </summary>
    public class PropertyLensSpec
    {
        [Fact]
        public void PropertyLensShouldWork()
        {
            var c = new INPCObject();
            var l = c.PropertyLens(v => v.ImmutableZ);

            int test = -1;
            l.Focus(p => p.ImmutableX.ImmutableInt).Subject.Subscribe(v => test = v);
            test.Should().Be(0);
            l.Current.ImmutableX.ImmutableInt.Should().Be(0);

            l.Focus(p => p.ImmutableX.ImmutableInt).Current = 5;
            l.Focus(p => p.ImmutableX.ImmutableInt).Current.Should().Be(5);
            l.Current.ImmutableX.ImmutableInt.Should().Be(5);
            c.ImmutableZ.ImmutableX.ImmutableInt.Should().Be(5);
            test.Should().Be(5);

            c.ImmutableZ = new ImmutableZ();
            l.Focus(p => p.ImmutableX.ImmutableInt).Current.Should().Be(0);
            l.Current.ImmutableX.ImmutableInt.Should().Be(0);
            c.ImmutableZ.ImmutableX.ImmutableInt.Should().Be(0);
            test.Should().Be(0);
            test.Should().Be(0);

            l.Focus(p => p.ImmutableX.ImmutableInt).Subject.OnNext(25);
            l.Focus(p => p.ImmutableX.ImmutableInt).Current.Should().Be(25);
            l.Current.ImmutableX.ImmutableInt.Should().Be(25);
            c.ImmutableZ.ImmutableX.ImmutableInt.Should().Be(25);
            test.Should().Be(25);

        }

        [Fact]
        public void PropertyLensShouldThrowAnExceptionIfInvokedWithAPathDepthGreaterThan1()
        {
            var c = new INPCObject();
            new Action(()=>c.PropertyLens(p => p.ImmutableZ.ImmutableX))
                .ShouldThrow<ArgumentException>();
        }

    }
}

We can handle immutable lists and dictionaries. Behind the scenes we use reflection to clone and update read only properties but it would work nice with the builder framework you have.

@bradphelan
Copy link

Due to problems contributing directly to this project I've created a dependent project.

https://github.com/bradphelan/ImmutableObjectGraphLens

This provides composable lenses over the immutable object graph. Currently it only supports property based selectors, no lists or dictionaries but that can be added.

For example the test

https://github.com/bradphelan/ImmutableObjectGraphLens/blob/master/src/ImmutableObjectGraphLensSpec/CompanySpec.cs

has the following test data

[GenerateImmutable]
public partial class Company
{
    readonly string name;
    readonly Person cto;

    static partial void CreateDefaultTemplate(ref Template template)
    {
        template.Cto = Person.Create("john smith");
        template.Name = "Microsoft";
    }

}
[GenerateImmutable]
public partial class  Person
{
    readonly string name;
}

and a test showing immutable update using lenses and selectors.

[Fact]
public void LensSpec()
{
var company = Company.Create();

var l =  ImmutableLens.CreateLens((Company c)=>c.Cto.Name);

company = l.Set(company, "brad");
l.Get(company).Should().Be("brad");
company.Cto.Name.Should().Be("brad");

}

More interesting is the ability to attach the immutable object type to a mutable field on an INPC supporting object. We can now create lenses to subproperties of the immutable object and have the mutable property of the root update.

public class Root : ReactiveObject
{
Company _Company = Company.Create();
public Company Company 
{
    get { return _Company; }
    set { this.RaiseAndSetIfChanged(ref _Company, value); }
}
}

[Fact]
public void MutableLensesShouldWork()
{
var root = new Root();
var lens = new PropertyLens<Root,Company>(root,c=>c.Company);

lens.Current.Cto.Name.Should().Be("john smith");
var lens2 = lens.Focus(c => c.Cto.Name);
string data = "";
string data2 = "";

lens2.WhenAnyValue(p => p.Current).Subscribe(current => data = current);

lens.Observe(p=>p.Name, p => p.Cto.Name, (companyName, ctoName)=>new {companyName, ctoName})
    .Subscribe(current => data2 = current.ctoName);

lens2.Current = "Brad";
data.Should().Be("Brad");
data2.Should().Be("Brad");
lens2.Current.Should().Be("Brad");
lens.Current.Cto.Name.Should().Be("Brad");
root.Company.Cto.Name.Should().Be("Brad");

}

Let me know your ideas on this.

@bradphelan
Copy link

bradphelan commented Aug 17, 2016

The technical core of the library is this reflection heavy code that walks a property list recursively calling "With" on each node. Theoretically we could code generate a lens builder that will not require reflection. Any ideas?

https://github.com/bradphelan/ImmutableObjectGraphLens/blob/master/src/ImmutableObjectGraphLens/ImmutableLens.cs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants