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

[Discussion] nested withers for deep immutable object updates (lenses?) #16459

Closed
orthoxerox opened this issue Jan 12, 2017 · 3 comments
Closed

Comments

@orthoxerox
Copy link
Contributor

orthoxerox commented Jan 12, 2017

Let's say we have the following immutable records (I'm using the latest discussed record and wither syntax here, [ReadOnly] is a 🍝 attribute to show that the records are actually immutable):

[ReadOnly] public class Order(Customer customer, ImmutableMap<Product, OrderLine> Lines);
[ReadOnly] public class OrderLine(Product product, Quantity quantity);
[ReadOnly] public class Quantity(uint value);

e.g.:

var expr = new Order(
    customer,
    {
        [boots] = new OrderLine(boots, 100),
        [gloves] = new OrderLine(gloves, 200)
    });

If we need to update the innermost element, even with some kind of a wither this looks like this:

public static Order UpdateQuantity(Order order, Product product, Quantity newQuantity)
    => order with {
        Lines = order.Lines.SetItem(
            product, 
            order.Lines[product] with {
                Quantity = new Quantity
            }
        )
    };
    
public static Order SwitchProduct(Order order, Product oldProduct, Product newProduct)
    => order with {
        Lines = order.Lines.SetItem(
            newProduct, 
            (order.Lines?[newProduct] ?? new OrderLine(newProduct, 0)) with {
                Quantity = 
                    order.Lines?[newProduct].Quantity ?? 0 + 
                    order.Lines[oldProduct].Quantity
            }
        ),
        Lines = order.Lines.Remove(oldProduct)
    };

The withers should be nestable, like this:

public static Order UpdateQuantity(Order order, Product product, Quantity newQuantity)
    => order with { Lines[product].Quantity = newQuantity };

public static Order SwitchProduct(Order order, Product oldProduct, Product newProduct)
    => order with {
        Lines[newProduct] = 
            order.Lines?[newProduct] ?? new OrderLine(newProduct, 0),
        Lines[newProduct].Quantity = 
            order.Lines[newProduct].Quantity + 
            order.Lines[oldProduct].Quantity,
        Lines = order.Lines.Remove(oldProduct)
    };

The hardest part here is the indexer. Somehow most immutable collections should be able to recognize they are being used in a wither. Perhaps immutable property setters are needed for this to work transparently.

@alrz
Copy link
Member

alrz commented Jan 12, 2017

This has been brought up before and I agree that it should be possible, though I'd prefer the the already proposed syntax in the spec draft,

var p = person with
{
  Name.FirstName = "NewFirstName"
};

// equivalent to

var p = person with
{
  Name with { FirstName = "NewFirstName" }
};

This probably addresses the open issue regarding wither syntax more strongly,

Open issue: Does this syntactic sugar actually pay for itself?

Since in the basic form it is just the same method invocation with different tokens as stated,

A with_expression of the form

 e1 with { identifier = e2, ... }

is treated as an invocation of the form

e1.With(identifier2: e2, ...) 

Same analogy could be applied to property patterns as well,

if (person is { Name.FirstName: "Foo" })

// equivalent to

if (person is { Name: { FirstName: "Foo" } })

Above examples omitted the static types as they are known at the compile-time (see #11744).

@orthoxerox
Copy link
Contributor Author

@alrz I've updated the syntax to match the currently proposed one, even though it has the dreadful }), which I cannot force myself to write together despite never placing a ) on a line by itself otherwise.

Good idea about property patterns, I think this is much easier to implement since it's a straightforward transformation.

@orthoxerox
Copy link
Contributor Author

Discussed in dotnet/csharplang#77

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