Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

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

Proposal: Beyond property initialization to code block initialization #1243

Closed
softwaremills opened this issue Jan 9, 2018 · 5 comments
Closed

Comments

@softwaremills
Copy link

I have found this to be a really useful function:

public static T With<T>(this T control, Action<T> setup)
{
    setup(control);
    return control;
}

I generally use it when I want to do some initialization without assigning a variable, and that initialization doesn't come in the form of setting a property:

var views = new[]
{
    new View(context).With(v => v.SetBackgroundColor(color1)),
    new View(context).With(v => v.SetBackgroundColor(color2)),
    new ViewGroup(context).With(vg =>
    {
        vg.AddView(new View(context).With(v => v.SetBackgroundColor(color1)));
        vg.AddView(new View(context).With(v => v.SetBackgroundColor(color2)));
    },
};

That does work, but it annoys me that it's setting up a bunch of closure objects and functions behind the scenes for something that should be pretty simple syntactic sugar. Of course, not being a language designer, I have no idea what the proper syntax should be for this sort of thing. I imagine something like this:

var views = new[]
{
    new View(context) with v { v.SetBackgroundColor(color1); },
    new View(context) with v { v.SetBackgroundColor(color2); },
    new ViewGroup(context) with vg
    {
        vg.AddView(new View(context) with v { v.SetBackgroundColor(color1); });
        vg.AddView(new View(context) with v { v.SetBackgroundColor(color2); });
    },
};

I mean, I use my little helper With() in places, but I still cringe. And I find this pattern so useful all over the place, especially where you're building some complex data structure and it's not sufficient to use property initialization or collection initialization.

@softwaremills softwaremills changed the title Beyond property initialization to code block initialization Proposal: Beyond property initialization to code block initialization Jan 9, 2018
@iam3yal
Copy link
Contributor

iam3yal commented Jan 10, 2018

@softwaremills C# has the concept of object initializers, maybe this concept can be expanded.

So it would be something like this:

var views = new[]
{
    new View(context) { SetBackgroundColor(color1) },
    new View(context) { SetBackgroundColor(color2) },
    new ViewGroup(context) {
        AddView(new View(context) { SetBackgroundColor(color1) }),
        AddView(new View(context) { SetBackgroundColor(color2) })
    },
};

@softwaremills
Copy link
Author

softwaremills commented Jan 10, 2018

@eyalsk That might get most of the way there. I like having the named reference to be able to have the full capability of blocks, like being able to, say, pass the ViewGroup to the created subviews, but your syntax is indeed closer to existing C#. In the With calls in my existing codebase, 16 out of 27 cases would be covered by that syntax. Some cases wouldn't be covered because of ifs or foreaches in the block. Some wouldn't be covered because the With call doesn't actually have to be a new; it works just fine with any expression.

For instance, here's an actual snippet from my codebase:

new UpdateShiftsData {
	DeletedBids = bids.Map(b => b.BidId),
	EditedShifts = new[] {
		new ShiftData {
			ShiftId = shift.ShiftId,
			WorkerId = bid.WorkerId,
			UpForBid = false,
			BidDeadlineMins = null,
			ModifiedDate = now,
		}.With(sd => {
			if (bid.SubShiftStart.HasValue) sd.Start = bid.SubShiftStart;
			if (bid.SubShiftFinish.HasValue) sd.Finish = bid.SubShiftFinish;
		}),
	},
}.With(usd => {
	if (recalculateBreaks) {
		usd.DeletedBreaks = breaks.Map(b => b.BreakId);
		usd.AddedBreaks = applyBidBreaks.Map(b => new BreakData(b));
	}
}).ToJObject()

@ufcpp
Copy link

ufcpp commented Jan 10, 2018

@softwaremills
Copy link
Author

@ufcpp That is a good point; any proposed changes would need to be compatible with the syntax in records and, in particular, the with-expressions you mention. Good call.

This is quite different than records, of course, which are meant to enable good practices in immutable structs. This proposal is meant to refer to taking any expression, providing a block that operates on that expression, with the whole thing evaluating to the original expression. The syntax would ideally be general-purpose syntax that would be very useful for building complex literals. Such things happen a lot in the creation of data structures to be serialized (as in my code snippet above) or, possibly, applied to user interfaces (say, for instance, the creation of React-like virtual stand-in elements).

@dotnet dotnet deleted a comment Apr 19, 2018
@dotnet dotnet deleted a comment from yaakov-h Apr 19, 2018
@dotnet dotnet deleted a comment Apr 19, 2018
@dotnet dotnet deleted a comment from DavidArno Apr 19, 2018
@dotnet dotnet deleted a comment from yaakov-h Apr 19, 2018
@dotnet dotnet deleted a comment Apr 19, 2018
@dotnet dotnet deleted a comment from terrajobst Apr 19, 2018
@dotnet dotnet deleted a comment Apr 19, 2018
@dotnet dotnet deleted a comment from terrajobst Apr 19, 2018
@dotnet dotnet deleted a comment from TAGC Apr 19, 2018
@dotnet dotnet deleted a comment from jnm2 Apr 19, 2018
@MadsTorgersen MadsTorgersen reopened this Sep 8, 2020
@tomschrot
Copy link

Kotlin supports a similar construct, and has the automatic "it" variable in the callers' context for such closures.

I use such a extension for years, but naming it apply, config, utilize ....

The mechanism is also very useful as constructor and could replace the object initializer with the advantage to access the methods also.

`
var y = new XY (my =>
{
my.value = 10;
...
});

class XY
{
public XY () {}
public XY (Action treat) => treat (this);

public int value {get; set;} = 0;

}
`

@jnm2 jnm2 converted this issue into discussion #5808 Feb 21, 2022

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Projects
None yet
Development

No branches or pull requests

7 participants
@softwaremills @tomschrot @mattwar @iam3yal @ufcpp @MadsTorgersen and others