C# Design Notes - catch up edition, Feb 29, 2016 (deconstruction and immutable object creation) #9330

Closed
MadsTorgersen opened this Issue Mar 1, 2016 · 170 comments

Comments

@MadsTorgersen
Contributor

MadsTorgersen commented Mar 1, 2016

C# Language Design Notes Feb 29, 2016

Catch up edition (deconstruction and immutable object creation)

Over the past couple of months various design activities took place that weren't documented in design notes. The following is a summary of the state of design regarding positional deconstruction, with-expressions and object initializers for immutable types.

Philosophy

We agree on the following design tenets:

Positional deconstruction, with-expressions and object initializers are separable features, enabled by the presence of certain API patterns on types that can be expressed manually, as well as generated by other language features such as records.

API Patterns

API patterns for a language feature facilitate two things:

  • Provide actual APIs to call at runtime when the language feature is used
  • Inform the compiler at compile time about how to generate code for the feature

It turns out the biggest design challenges are around the second part. Specifically, all these API patterns turn out to need to bridge between positional and name-based expressions of the members of types. How each API pattern does that is a central question of its design.

Assume the following running example:

public class Person
{
  public string FirstName { get; }
  public string LastName { get; }

  public Person(string firstName, string lastName)
  {
    FirstName = firstName;
    LastName = lastName;
  }
}

In the following we'll consider extending and changing this type to expose various API patterns as we examine the individual language features.

Here's an example of using the three language features:

var p = new Person { FirstName = "Mickey", LastName = "Mouse" }; // object initializer
if (p is Person("Mickey", *)) // positional deconstruction
{
  return p with { FirstName = "Minney" }; // with-expression
}

Semantically this corresponds to something like this:

var p = new Person("Mickey", "Mouse"); // constructor call
if (p.FirstName == "Mickey") // property access
{
  return new Person("Minney", p.LastName); // constructor call
}

Notice how the new features that use property names correspond to API calls using positional parameters, whereas the feature that uses positions corresponds to member access by name!

Object initializers for immutable objects

(See e.g. #229)

This feature allows an object initializer for which assignable properties are not found, to fall back to a constructor call taking the properties' new values as arguments.

new Person { FirstName = "Mickey", LastName = "Mouse" }

becomes

new Person("Mickey", "Mouse")

The question then is: how does the compiler decide to pass the given FirstName as the first argument? Somehow it needs clues from the Person type as to which properties correspond to which constructor parameters. These clues cannot just be the constructor body: we need this to work across assemblies, so the clues must be evident from metadata.

Here are some options:

1: The type or constructor explicitly includes metadata for this purpose, e.g. in the form of attributes.
2: The names of the constructor parameters must match exactly the names of the corresponding properties.

The former is unattractive because it requires the type's author to write those attributes. It requires the type to be explicitly edited for the purpose.

The latter is better in that it doesn't require extra API elements. However, API design guidelines stipulate that public properties start with uppercase, and parameters start with lower case. This pattern would break that, and for the same reason is highly unlikely to apply to any existing types.

This leads us to:

3: The names of the constructor parameters must match the names of the corresponding properties, modulo case!

This would allow a large number of existing types to just work (including the example above), but at the cost of introducing case insensitivity to this part of the C# language.

With-expressions

(see e.g. #5172)

With-expressions are similar to object initializers, except that they provide a source object from which to copy all the properties that aren't specified. Thus it seems reasonable to use a similar strategy for compilation; to call a constructor, this time filling in missing properties by accessing those on the source object.

Thus the same strategies as above would apply to establish the connection between properties and constructor parameters.

p with { FirstName = "Minney" }

becomes

new Person("Minney", p.LastName)

However, there's a hitch: if the runtime source object is actually of a derived type with more properties than are known from its static type, it would typically be expected that those are copied over too. In that case, the static type is also likely to be abstract (most base types are), so it wouldn't offer a callable constructor.

For this situation there needs to be a way that an abstract base class can offer "with-ability" that correctly copies over members of derived types. The best way we can think of is to offer a virtual With method, as follows:

public abstract class Person
{
  ...
  public abstract Person With(string firstName, string lastName);
}

In the presence of such a With method we would generate a with expression to call that instead of the constructor:

p.With("Minney", p.LastName)

We can decide whether to make with-expressions require a With method, or fall back to constructor calls in its absence.

If we require a With method, that makes for less interoperability with existing types. However, it gives us new opportunities for how to provide the position/name mapping metadata thorugh the declaration of that With method: For instance, we could introduce a new kind of default parameter that explicitly wires the parameter to a property:

  public abstract Person With(string firstName = this.FirstName, string lastName = this.LastName);

To explicitly facilitate interop with an existing type, a mandatory With method could be allowed to be provided as an extension method. It is unclear how that would work with the default parameter approach, though.

Positional deconstruction

(see e.g. #206)

This feature allows a positional syntax for extracting the property values from an object, for instance in the context of pattern matching, but potentially also elsewhere.

Ideally, a positional deconstruction would simply generate an access of each member whose value is obtained:

p is Person("Mickey", *)

becomes

p.FirstName == "Mickey"

Again, this requires the compiler's understanding of how positions correspond to property names. Again, the same strategies as for object initializers are possible. See e.g. #8415.

Additionally, just as in with-expressions, one might wish to override the default behavior, or provide it if names don't match. Again, an explicit method could be used:

public abstract class Person
{
  ...
  public void Person GetValues(out string firstName, out string lastName);
}

There are several options as to the shape of such a method. Instead of out-parameters, it might return a tuple. This has pros and cons: there could be only one tuple-returning GetValues method, because there would be no parameters to distinguish signatures. This may be a good or a bad thing.

Just as the With method, we can decide whether deconstruction should require a GetValues method, or should fall back to metadata or to name matching against the constructor's parameter names.

If the GetValues method is used, the compiler doesn't need to resolve between positions and properties: the deconstruction as well as the method are already positional. We'd generate the code as follows:

string __p1;
string __p2;
p.GetValues(out __p1, out __p2);
...
__p1 == "Mickey"

Somewhat less elegant for sure, and possibly less efficient, since the LastName is obtained for no reason. However, this is compiler generated code that no one has to look at, and it can probably be optimized, so this may not be a big issue.

Summary

For each of these three features we are grappling with the position-to-property match. Our options:

  1. Require specific metadata
  2. Match property and parameter names, possibly in a case sensitive way
  3. For deconstruction and with-expressions, allow or require specific methods (GetValues and With respectively) to implement their behavior, and possibly have special syntax in With methods to provide the name-to-position matching.

We continue to work on this.

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Mar 1, 2016

👍 for design notes!

HaloFour commented Mar 1, 2016

👍 for design notes!

@MgSam

This comment has been minimized.

Show comment
Hide comment
@MgSam

MgSam Mar 1, 2016

I second HaloFour's sentiment.

I'll echo my thoughts about positional decomposition from the other thread- I think its a step in the wrong direction. It makes the code harder to read and understand without using Intellisense. With Autocomplete, the cost of having to type property names is tiny, so why avoid it?

Are record types still on the table for C# 7.0? I notice your Person example doesn't use them. Those are by far the more interesting and useful feature to me.

MgSam commented Mar 1, 2016

I second HaloFour's sentiment.

I'll echo my thoughts about positional decomposition from the other thread- I think its a step in the wrong direction. It makes the code harder to read and understand without using Intellisense. With Autocomplete, the cost of having to type property names is tiny, so why avoid it?

Are record types still on the table for C# 7.0? I notice your Person example doesn't use them. Those are by far the more interesting and useful feature to me.

@chrisaut

This comment has been minimized.

Show comment
Hide comment
@chrisaut

chrisaut Mar 1, 2016

I just don't understand what this gives us:

if (p is Person("Mickey", *)) // positional deconstruction

over specifying the property names, other than saving a few keystrokes, at the, IMO, huge cost of much less readability by introducing what just seems like compiler magic where everytime I have to look up the types ctor (or GetValues method) to understand what the code is doing.
Why can this not be something like

if (p is Person { FirstName == "Mickey"}) 

or even clearer IMO

if (p is Person where FirstName == "Mickey") 

The object initializer trying to find matching ctor parameter names seems a bit strange too. Ok, getter only properties cannot be written to outside a ctor, but as far as I understand that is not really enforced at the clr level (eg. reflection can write to readonly fields), so why not just solve the problem this way (emit code that just does it anyways)? If that's not possible, why can we not change the clr (yes I know its a high bar, but this is C#, the posterchild language for the CLR, it seems we need to find all these workarounds instead of changing/evolving things if needed).

I guess I'm just not too hot on magic transformations that depends on argument positions and naming conventions for core language features.

PS: Sorry if this sounds like a bit of a rant, I'm sure you guys are working hard, and I love C# and most of the things going on with it.

chrisaut commented Mar 1, 2016

I just don't understand what this gives us:

if (p is Person("Mickey", *)) // positional deconstruction

over specifying the property names, other than saving a few keystrokes, at the, IMO, huge cost of much less readability by introducing what just seems like compiler magic where everytime I have to look up the types ctor (or GetValues method) to understand what the code is doing.
Why can this not be something like

if (p is Person { FirstName == "Mickey"}) 

or even clearer IMO

if (p is Person where FirstName == "Mickey") 

The object initializer trying to find matching ctor parameter names seems a bit strange too. Ok, getter only properties cannot be written to outside a ctor, but as far as I understand that is not really enforced at the clr level (eg. reflection can write to readonly fields), so why not just solve the problem this way (emit code that just does it anyways)? If that's not possible, why can we not change the clr (yes I know its a high bar, but this is C#, the posterchild language for the CLR, it seems we need to find all these workarounds instead of changing/evolving things if needed).

I guess I'm just not too hot on magic transformations that depends on argument positions and naming conventions for core language features.

PS: Sorry if this sounds like a bit of a rant, I'm sure you guys are working hard, and I love C# and most of the things going on with it.

@ErikSchierboom

This comment has been minimized.

Show comment
Hide comment
@ErikSchierboom

ErikSchierboom Mar 1, 2016

First, let me say that I love all three suggested features. However, as @chrisaut, I also feel this needs some polishing. The positional deconstruction is a bit hard to read IMHO. I feel that this is due to the fact that positional deconstruction is of course, based on the positions of the parameters. However, that is not something that I keep in my head most of the time. Why not make things more explicit by having syntax that refers to the properties themselves?

The example provided by @chrisaut that uses the where keyword is my favorite:

if (p is Person where FirstName == "Mickey") 

I feel that this better represents what is happening, namely that you first check if p is a Person and then subsequently check if the FirstName is equal to "Mickey". If we look at the positional deconstruction example (if (p is Person("Mickey", *))), this is far less apparent to me.

Another benefit of the where syntax is that it enables all types of boolean expressions to be used. For example, we could do this to match persons which FirstName starts with and "M":

if (p is Person where FirstName.StartsWith("M"))

The positional destructuring doesn't naturally allow this as far as I can tell. It also doesn't easily allow and or or statements, which are also possible in the where syntax:

if (p is Person where FirstName.StartsWith("M") && FirstName.EndsWith("y"))

if (p is Person where FirstName == "Mickey" || FirstName == "Alice")

My final argument in favor of the where syntax (or something similar) is that to me it feels more C#-ish. While this is hard to define, I feel that the positional structuring is not explicit enough, whereas the where syntax kinda looks like the exception filtering meets LINQ.

First, let me say that I love all three suggested features. However, as @chrisaut, I also feel this needs some polishing. The positional deconstruction is a bit hard to read IMHO. I feel that this is due to the fact that positional deconstruction is of course, based on the positions of the parameters. However, that is not something that I keep in my head most of the time. Why not make things more explicit by having syntax that refers to the properties themselves?

The example provided by @chrisaut that uses the where keyword is my favorite:

if (p is Person where FirstName == "Mickey") 

I feel that this better represents what is happening, namely that you first check if p is a Person and then subsequently check if the FirstName is equal to "Mickey". If we look at the positional deconstruction example (if (p is Person("Mickey", *))), this is far less apparent to me.

Another benefit of the where syntax is that it enables all types of boolean expressions to be used. For example, we could do this to match persons which FirstName starts with and "M":

if (p is Person where FirstName.StartsWith("M"))

The positional destructuring doesn't naturally allow this as far as I can tell. It also doesn't easily allow and or or statements, which are also possible in the where syntax:

if (p is Person where FirstName.StartsWith("M") && FirstName.EndsWith("y"))

if (p is Person where FirstName == "Mickey" || FirstName == "Alice")

My final argument in favor of the where syntax (or something similar) is that to me it feels more C#-ish. While this is hard to define, I feel that the positional structuring is not explicit enough, whereas the where syntax kinda looks like the exception filtering meets LINQ.

@dadhi

This comment has been minimized.

Show comment
Hide comment
@dadhi

dadhi Mar 1, 2016

How is the proposed deconstruction is different from?

if ((p as Person)?.FirstName == "Mickey")

dadhi commented Mar 1, 2016

How is the proposed deconstruction is different from?

if ((p as Person)?.FirstName == "Mickey")
@maloo

This comment has been minimized.

Show comment
Hide comment
@maloo

maloo Mar 1, 2016

Please don't do positional deconstruction. C# has always been easy to read and reason about. Like defining ref/out. The "initializer syntax" is so much easier to understand and require no extra magic. Or at least show an example where positional deconstruction would be a better option.

maloo commented Mar 1, 2016

Please don't do positional deconstruction. C# has always been easy to read and reason about. Like defining ref/out. The "initializer syntax" is so much easier to understand and require no extra magic. Or at least show an example where positional deconstruction would be a better option.

@omariom

This comment has been minimized.

Show comment
Hide comment
@omariom

omariom Mar 1, 2016

p is Person("Mickey", *)

That will generate a lot of traffic to StackOverflow :)

omariom commented Mar 1, 2016

p is Person("Mickey", *)

That will generate a lot of traffic to StackOverflow :)

@omariom

This comment has been minimized.

Show comment
Hide comment
@omariom

omariom Mar 1, 2016

I like if (p is Person where FirstName == "Mickey") but it will confuse LINQ quieries.

What about this?

Person p;
object obj;

if (p is Person && p.FirstName == "Mickey")

if (obj is Student s && s.FirstName == "Mickey")

omariom commented Mar 1, 2016

I like if (p is Person where FirstName == "Mickey") but it will confuse LINQ quieries.

What about this?

Person p;
object obj;

if (p is Person && p.FirstName == "Mickey")

if (obj is Student s && s.FirstName == "Mickey")
@ErikSchierboom

This comment has been minimized.

Show comment
Hide comment

@omariom Not bad at all!

@chrisaut

This comment has been minimized.

Show comment
Hide comment
@chrisaut

chrisaut Mar 1, 2016

if (obj is Student s && s.FirstName == "Mickey")

@omariom I like this one the most, it's very C#ish and crystal clear what it does IMO.

BTW I don't think the where would confuse LINQ queries, where already is a contextual keyword (only a keyword if already inside an open Linq expression). I didn't even think about Linq when I proposed it, I thought of the where used in Generics (eg. Class<T> where T : struct)

chrisaut commented Mar 1, 2016

if (obj is Student s && s.FirstName == "Mickey")

@omariom I like this one the most, it's very C#ish and crystal clear what it does IMO.

BTW I don't think the where would confuse LINQ queries, where already is a contextual keyword (only a keyword if already inside an open Linq expression). I didn't even think about Linq when I proposed it, I thought of the where used in Generics (eg. Class<T> where T : struct)

@jcdickinson

This comment has been minimized.

Show comment
Hide comment
@jcdickinson

jcdickinson Mar 1, 2016

@omariom when do we get this && operator? It's seems very useful!

On a serious note your expression syntax example is a specialization of #254, albeit an interesting and useful one.

@omariom when do we get this && operator? It's seems very useful!

On a serious note your expression syntax example is a specialization of #254, albeit an interesting and useful one.

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Mar 1, 2016

@chrisaut

Such a syntax is already proposed (#206) and would work on any existing type:

if (obj is Person { FirstName is "Mickey" }) { ... }

In simpler cases you could combine the type pattern with other conditions:

if (obj is Person p && p.FirstName == "Mickey") { ... }

The positional construction, matching and deconstruction is a feature of records. The purpose of these additional proposals is to bring some of that functionality to existing types.

HaloFour commented Mar 1, 2016

@chrisaut

Such a syntax is already proposed (#206) and would work on any existing type:

if (obj is Person { FirstName is "Mickey" }) { ... }

In simpler cases you could combine the type pattern with other conditions:

if (obj is Person p && p.FirstName == "Mickey") { ... }

The positional construction, matching and deconstruction is a feature of records. The purpose of these additional proposals is to bring some of that functionality to existing types.

@nirvinm

This comment has been minimized.

Show comment
Hide comment
@nirvinm

nirvinm Mar 1, 2016

This seems to have more clarity.

if (obj is Person with FirstName == "Mickey") {
}

But this proposal don't bring much readability. new Ojb() is better than 'with' expression. Anyway please don't bring special 'With' or other methods like Python does. It is awful.

nirvinm commented Mar 1, 2016

This seems to have more clarity.

if (obj is Person with FirstName == "Mickey") {
}

But this proposal don't bring much readability. new Ojb() is better than 'with' expression. Anyway please don't bring special 'With' or other methods like Python does. It is awful.

@alrz

This comment has been minimized.

Show comment
Hide comment
@alrz

alrz Mar 1, 2016

Contributor

@HaloFour I think you're refering to let syntax, because there is no such guard for is operator and a logical AND would do.

Contributor

alrz commented Mar 1, 2016

@HaloFour I think you're refering to let syntax, because there is no such guard for is operator and a logical AND would do.

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Mar 1, 2016

@alrz Ah you're right, the proposal does specifically mention when as a part of switch, match and let. So yes, normal Boolean operators would apply instead. I'll update my comment.

HaloFour commented Mar 1, 2016

@alrz Ah you're right, the proposal does specifically mention when as a part of switch, match and let. So yes, normal Boolean operators would apply instead. I'll update my comment.

@alrz

This comment has been minimized.

Show comment
Hide comment
@alrz

alrz Mar 1, 2016

Contributor

I don't understand why people are freaking out of the syntax in its most basic form; do you guys ever heard of the word "pattern"? And suggestions involving where and with are all ambiguous. 😕

Contributor

alrz commented Mar 1, 2016

I don't understand why people are freaking out of the syntax in its most basic form; do you guys ever heard of the word "pattern"? And suggestions involving where and with are all ambiguous. 😕

@orthoxerox

This comment has been minimized.

Show comment
Hide comment
@orthoxerox

orthoxerox Mar 1, 2016

Contributor

I agree with the rest of the peanut gallery that silent automatic decomposition based on constructors is a bad idea. I'd rather use property patterns or implement an extension method to deconstruct existing classes. New classes could get a primary constructor with restricted semantics for those cases when records aren't complex enough.

Contributor

orthoxerox commented Mar 1, 2016

I agree with the rest of the peanut gallery that silent automatic decomposition based on constructors is a bad idea. I'd rather use property patterns or implement an extension method to deconstruct existing classes. New classes could get a primary constructor with restricted semantics for those cases when records aren't complex enough.

@dsaf

This comment has been minimized.

Show comment
Hide comment
@dsaf

dsaf Mar 1, 2016

The best way we can think of is to offer a virtual With method, as follows:

public abstract class Person
{
...
public abstract Person With(string firstName, string lastName);
...
public void Person GetValues(out string firstName, out string lastName);
...

Shouldn't these be new operator declarations instead? I don't know how to explain it, but it feels wrong tying language constructs to type members without a more explicit contract. Somehow the IEnumerable<T> - select and Task<T> - async and IDisposable - using relationships are more obvious...

dsaf commented Mar 1, 2016

The best way we can think of is to offer a virtual With method, as follows:

public abstract class Person
{
...
public abstract Person With(string firstName, string lastName);
...
public void Person GetValues(out string firstName, out string lastName);
...

Shouldn't these be new operator declarations instead? I don't know how to explain it, but it feels wrong tying language constructs to type members without a more explicit contract. Somehow the IEnumerable<T> - select and Task<T> - async and IDisposable - using relationships are more obvious...

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Mar 1, 2016

@dsaf

As operators or as extension methods they couldn't be virtual and it's largely necessary for them to be virtual so that derived types can correctly copy over the properties that aren't specified in the signature:

public class Person {
    public string FirstName { get; }
    public string LastName { get; }

    public Person(string firstName, string lastName) {
        this.FirstName = firstName;
        this.LastName = lastName;
    }

    public virtual Person With(string firstName, string lastName) {
        return new Person(firstName, lastName);
    }
}

public class Student : Person {
    public int Grade { get; }

    public Student(string firstName, string lastName, int grade) : base(firstName, lastName) {
        this.Grade = grade;
    }

    public override Student With(string firstName, string lastName) {
        return With(firstName, lastName, this.Grade);
    }

    public virtual Student With(string firstName, string lastName, int grade) {
        return new Student(firstName, lastName, grade);
    }
}

...

Person person = new Student("Foo", "Bar", 98);

Person person2 = person with { LastName = "Baz" };
Debug.Assert(person2 is Student { FirstName is "Foo", LastName is "Baz", Grade is 98 });

HaloFour commented Mar 1, 2016

@dsaf

As operators or as extension methods they couldn't be virtual and it's largely necessary for them to be virtual so that derived types can correctly copy over the properties that aren't specified in the signature:

public class Person {
    public string FirstName { get; }
    public string LastName { get; }

    public Person(string firstName, string lastName) {
        this.FirstName = firstName;
        this.LastName = lastName;
    }

    public virtual Person With(string firstName, string lastName) {
        return new Person(firstName, lastName);
    }
}

public class Student : Person {
    public int Grade { get; }

    public Student(string firstName, string lastName, int grade) : base(firstName, lastName) {
        this.Grade = grade;
    }

    public override Student With(string firstName, string lastName) {
        return With(firstName, lastName, this.Grade);
    }

    public virtual Student With(string firstName, string lastName, int grade) {
        return new Student(firstName, lastName, grade);
    }
}

...

Person person = new Student("Foo", "Bar", 98);

Person person2 = person with { LastName = "Baz" };
Debug.Assert(person2 is Student { FirstName is "Foo", LastName is "Baz", Grade is 98 });
@omariom

This comment has been minimized.

Show comment
Hide comment
@omariom

omariom Mar 1, 2016

@chrisaut

I don't think the where would confuse LINQ queries, where already is a contextual keyword (only a keyword if already inside an open Linq expression).

I meant this case:

from p in persons
where p is Student s 
where s.FirstName == "Mickey"
select p;

Even if it don't confuse the compiler it will confuse me )

omariom commented Mar 1, 2016

@chrisaut

I don't think the where would confuse LINQ queries, where already is a contextual keyword (only a keyword if already inside an open Linq expression).

I meant this case:

from p in persons
where p is Student s 
where s.FirstName == "Mickey"
select p;

Even if it don't confuse the compiler it will confuse me )

@KathleenDollard

This comment has been minimized.

Show comment
Hide comment
@KathleenDollard

KathleenDollard Mar 1, 2016

Could the team explain the perceived value of positional decomposition?

Initial knee-jerk response - For #$@#$ sake don't do that!

We can fine tune syntax, but just write that puppy out with our archetypal point example. Any two ints or bools, much less three or four. OMG!

The rest is coming along very nicely :)

Could the team explain the perceived value of positional decomposition?

Initial knee-jerk response - For #$@#$ sake don't do that!

We can fine tune syntax, but just write that puppy out with our archetypal point example. Any two ints or bools, much less three or four. OMG!

The rest is coming along very nicely :)

@dsaf

This comment has been minimized.

Show comment
Hide comment
@dsaf

dsaf Mar 1, 2016

@HaloFour Understood, thanks!

dsaf commented Mar 1, 2016

@HaloFour Understood, thanks!

@JiriZidek

This comment has been minimized.

Show comment
Hide comment
@JiriZidek

JiriZidek Mar 1, 2016

  1. Object initializer for immutables - maybe optional parameters in constructor (similarily as in Attribute declaration) is enough ?
    var p = new Person( LastName: "Taylor");
    In general I guess that implicit constructor for each public property can be generated using the very same parameter names, so it would be enough just to indicate in class declaration, that such constructor shoud exist - why not just name of class without parentheses ?
    public Person;
    compiles as
    public Person(string FirstName, string LastName) { this.FirstName=FirstName; this.LastName=LastName; }
    and when these params are optional, there is no big step to allow initializer to call such constructor.
  2. p is Person("Mickey",*) OR p is Person where FirstName == "Mickey" we know that p IS Person, so why not use just p?.FirstName=="Mickey" ? I do not se any benefit in deconstruction just for eqauality. Instead of this I would appretiate methods returning multiple values - like: var (n,x) = GetMinMax(int[] row).It solves some real pain...
  3. p with Person("Minnie",*) - I would suggest simplier syntax - maybe I just dislike word "with" from VB:
    var r = p { FirstName="Minnie" };
    That's my opinion.
    Jiri
  1. Object initializer for immutables - maybe optional parameters in constructor (similarily as in Attribute declaration) is enough ?
    var p = new Person( LastName: "Taylor");
    In general I guess that implicit constructor for each public property can be generated using the very same parameter names, so it would be enough just to indicate in class declaration, that such constructor shoud exist - why not just name of class without parentheses ?
    public Person;
    compiles as
    public Person(string FirstName, string LastName) { this.FirstName=FirstName; this.LastName=LastName; }
    and when these params are optional, there is no big step to allow initializer to call such constructor.
  2. p is Person("Mickey",*) OR p is Person where FirstName == "Mickey" we know that p IS Person, so why not use just p?.FirstName=="Mickey" ? I do not se any benefit in deconstruction just for eqauality. Instead of this I would appretiate methods returning multiple values - like: var (n,x) = GetMinMax(int[] row).It solves some real pain...
  3. p with Person("Minnie",*) - I would suggest simplier syntax - maybe I just dislike word "with" from VB:
    var r = p { FirstName="Minnie" };
    That's my opinion.
    Jiri
@gafter

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Mar 1, 2016

Member

This "summary" exposes a debate that we've been having in the LDM: whether or not we should use name-matching (across distinct APIs) to drive language features, and in particular to get positional behavior. This summary describes the situation with the assumption that we should. The other side is that we should not.

API patterns for a language feature facilitate two things:

  • Provide actual APIs to call at runtime when the language feature is used
  • Inform the compiler at compile time about how to generate code for the feature

That is not the traditional role of API patterns in C#. It has always been the former (API elements that the compiler invokes). I think it would not be advisable for us to have an "API pattern" where the compiler finds the API pattern and then does something other than invoke it. All of the difficulties of inferring the latter from the former can be avoided if we just don't do that.

Object initializers for immutable objects

Not sure why we're discussing this. It has been low on the list of things we're likely to consider for C# 7 for some time. Is it really that much of an advantage to allow people to type more characters for an alternative way to invoke a constructor, especially with all of the semantic issues that are thereby self-created?

Positional deconstruction
Ideally, a positional deconstruction would simply generate an access of each member whose value is obtained:

That is not ideal. Properties do not have "positions". The ideal is an API pattern that is invoked by the compiler.

Member

gafter commented Mar 1, 2016

This "summary" exposes a debate that we've been having in the LDM: whether or not we should use name-matching (across distinct APIs) to drive language features, and in particular to get positional behavior. This summary describes the situation with the assumption that we should. The other side is that we should not.

API patterns for a language feature facilitate two things:

  • Provide actual APIs to call at runtime when the language feature is used
  • Inform the compiler at compile time about how to generate code for the feature

That is not the traditional role of API patterns in C#. It has always been the former (API elements that the compiler invokes). I think it would not be advisable for us to have an "API pattern" where the compiler finds the API pattern and then does something other than invoke it. All of the difficulties of inferring the latter from the former can be avoided if we just don't do that.

Object initializers for immutable objects

Not sure why we're discussing this. It has been low on the list of things we're likely to consider for C# 7 for some time. Is it really that much of an advantage to allow people to type more characters for an alternative way to invoke a constructor, especially with all of the semantic issues that are thereby self-created?

Positional deconstruction
Ideally, a positional deconstruction would simply generate an access of each member whose value is obtained:

That is not ideal. Properties do not have "positions". The ideal is an API pattern that is invoked by the compiler.

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Mar 1, 2016

@gafter

I tend to agree. If the compiler is going to allow record shorthand for non-record types I'd prefer it be through the same conventions established for record types. If a typical C# record generates a With method and/or a GetValues method which are used for "withers" or deconstruction respectively then I could see those features being enabled for CLR types that expose those same methods (or through extension methods in scope). Otherwise I don't think it's worth it.

HaloFour commented Mar 1, 2016

@gafter

I tend to agree. If the compiler is going to allow record shorthand for non-record types I'd prefer it be through the same conventions established for record types. If a typical C# record generates a With method and/or a GetValues method which are used for "withers" or deconstruction respectively then I could see those features being enabled for CLR types that expose those same methods (or through extension methods in scope). Otherwise I don't think it's worth it.

@alrz

This comment has been minimized.

Show comment
Hide comment
@alrz

alrz Mar 1, 2016

Contributor

Couldn't agree more ⬆️ 👍

just don't do that.

As for "object initializers for immutable objects" I don't think that it will be any useful in presence of record types, considering that you still need to declare the constructor. Also, if the class requirements go beyond of a record, it woudn't make sense to provide the same syntax for deconstruction out of the box.

Contributor

alrz commented Mar 1, 2016

Couldn't agree more ⬆️ 👍

just don't do that.

As for "object initializers for immutable objects" I don't think that it will be any useful in presence of record types, considering that you still need to declare the constructor. Also, if the class requirements go beyond of a record, it woudn't make sense to provide the same syntax for deconstruction out of the box.

@isaacabraham

This comment has been minimized.

Show comment
Hide comment
@isaacabraham

isaacabraham Mar 1, 2016

Positional deconstruction for tuples, yes. For records / classes with named properties - no.

Positional deconstruction for tuples, yes. For records / classes with named properties - no.

@jakeswenson

This comment has been minimized.

Show comment
Hide comment
@jakeswenson

jakeswenson Mar 1, 2016

I love all of this.
Would you be able to use named arguments in positional deconstruction? (named deconstruction?)

p is Person(firstName: "Mickey", lastName: *)

I love all of this.
Would you be able to use named arguments in positional deconstruction? (named deconstruction?)

p is Person(firstName: "Mickey", lastName: *)
@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Mar 2, 2016

@jakeswenson

Why wouldn't property patterns be sufficient for that purpose?

p is Person { FirstName is "Mickey" }

HaloFour commented Mar 2, 2016

@jakeswenson

Why wouldn't property patterns be sufficient for that purpose?

p is Person { FirstName is "Mickey" }
@paulomorgado

This comment has been minimized.

Show comment
Hide comment
@paulomorgado

paulomorgado Mar 2, 2016

@HaloFour, operators would inded kill the chance of having virtual methods. But extension methdos wouldn't. Pretty much like LINQ - operators can translate to either instance methods or extension methods.

@MadsTorgersen, @gafter, What's the use case of this?

public abstract Person With(string firstName = this.FirstName, string lastName = this.LastName);

Isn't this opening a can of worms?

Would this (or something like this) be valid:

if (p is Person(lastName: "Mouse", firstName: "Mickey")) { ... }
if (p is Person(firstName: "Mickey", lastName: *)) { ... }
if (p is Person(firstName: "Mickey")) { ... }
if (p is Person(FirstName: "Mickey", LastName: *)) { ... }
if (p is Person(FirstName: "Mickey")) { ... }

@HaloFour, operators would inded kill the chance of having virtual methods. But extension methdos wouldn't. Pretty much like LINQ - operators can translate to either instance methods or extension methods.

@MadsTorgersen, @gafter, What's the use case of this?

public abstract Person With(string firstName = this.FirstName, string lastName = this.LastName);

Isn't this opening a can of worms?

Would this (or something like this) be valid:

if (p is Person(lastName: "Mouse", firstName: "Mickey")) { ... }
if (p is Person(firstName: "Mickey", lastName: *)) { ... }
if (p is Person(firstName: "Mickey")) { ... }
if (p is Person(FirstName: "Mickey", LastName: *)) { ... }
if (p is Person(FirstName: "Mickey")) { ... }
@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Mar 2, 2016

@paulomorgado

Extension methods have the exact same problem that operators do, the dispatch is determined at compile time and not at run time based on the actual type of the instance. In my example above the variable is Person, not Student, so any extension method it would resolve would be for this Person, not this Student.

HaloFour commented Mar 2, 2016

@paulomorgado

Extension methods have the exact same problem that operators do, the dispatch is determined at compile time and not at run time based on the actual type of the instance. In my example above the variable is Person, not Student, so any extension method it would resolve would be for this Person, not this Student.

@paulomorgado

This comment has been minimized.

Show comment
Hide comment
@paulomorgado

paulomorgado Mar 2, 2016

@HaloFour, you can put the extension methods in any extension class you want to and virtual methods would still be possible.

The exact same thing happens with LINQ. Not all enumerables are alike.

@HaloFour, you can put the extension methods in any extension class you want to and virtual methods would still be possible.

The exact same thing happens with LINQ. Not all enumerables are alike.

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Mar 2, 2016

@paulomorgado You could, but that only works based on the type of the variable since that's the only thing that the compiler knows. This is very different from virtual methods where the dispatch is determined at runtime based on the exact type:

public static class PersonExtensions {
    public static string Test(this Person person) {
        return "Person";
    }

    public static string Test(this Student person) {
        return "Student";
    }
}
...
Person person = new Student();
 // compiler resolves to extension method of Person
string result = person.Test();
Debug.Assert(result == "Person");

vs.

public class Person {
    public virtual string Test() {
        return "Person";
    }
}

public class Student : Person {
    public override string Test() {
        return "Student";
    }
}
...
Person person = new Student();
// compiler virtually calls Person.Test which is dispatched to Student.Test at runtime
string result = person.Test();
Debug.Assert(result == "Student");

If Person or an extension method for Person performs the "wither" the Grade property will most certainly be lost and the return type would be a Person, not a Student. That's why they would need to be virtual instance methods, and in the case of abstract types also abstract.

HaloFour commented Mar 2, 2016

@paulomorgado You could, but that only works based on the type of the variable since that's the only thing that the compiler knows. This is very different from virtual methods where the dispatch is determined at runtime based on the exact type:

public static class PersonExtensions {
    public static string Test(this Person person) {
        return "Person";
    }

    public static string Test(this Student person) {
        return "Student";
    }
}
...
Person person = new Student();
 // compiler resolves to extension method of Person
string result = person.Test();
Debug.Assert(result == "Person");

vs.

public class Person {
    public virtual string Test() {
        return "Person";
    }
}

public class Student : Person {
    public override string Test() {
        return "Student";
    }
}
...
Person person = new Student();
// compiler virtually calls Person.Test which is dispatched to Student.Test at runtime
string result = person.Test();
Debug.Assert(result == "Student");

If Person or an extension method for Person performs the "wither" the Grade property will most certainly be lost and the return type would be a Person, not a Student. That's why they would need to be virtual instance methods, and in the case of abstract types also abstract.

@mythz

This comment has been minimized.

Show comment
Hide comment
@mythz

mythz Mar 2, 2016

Positional deconstruction

I struggle to see how this feature justifies the complexity and new concepts it would add to C#. In just 1-line we're introducing is against a constructor, using a constructor without new, and the yet-seen-before wildcard in C#:

if (p is Person("Mickey", *)) // positional deconstruction

What are all these new concepts saving us from writing, this?

if ((p as Person)?.FirstName == "Mickey") {
}

Which is even less readable than above as you'll need to either carry the constructor definition in your head or routinely consult the class definition to find out what the condition is matching against.

Note: just because something is more terse doesn't make it more readable.

If we also wanted multiple conditions we add a static function and get readable, typed syntax today:

using static Test;
public class Test {
    public static bool match<T>(T o, Func<T, bool> predicate) => o != null && predicate(o);
}

if (match(p as Person, p => p.FirstName == "Mickey" && p.LastName == "Mouse")) {
}

With no added complexity cost to language or tooling. Tho would be nice if the compiler could optimize away the method call stack + lambda overhead for us :)

Object initializers for immutable objects

Whilst not as bad as positional deconstruction, I'm not seeing the benefits from immutable object initializers either:

var p = new Person { FirstName = "Mickey", LastName = "Mouse" }; // object initializer

From what I can tell it's just an alternative to:

var p = new Person(firstName:"Mickey", lastName:"Mouse");

var p = new Person("Mickey", "Mouse");

But worse as it relies on non-equivalent name matching, a reminder from Java with how bean properties map to Java getter/setter methods by naming convention - another ugly corner of the language.

The main friction I have with immutable objects are that they're harder to genericize using reflection, (e.g. serializers/mappers), which this feature isn't helping with.

With-expressions

Not in love with the syntax but fills a need that would require a bit of machinery without language support. My preference would probably be to go with a syntax operator variety.

p { FirstName = "Minney" }
p + { FirstName = "Minney" }
p .+ { FirstName = "Minney" }
p <- { FirstName = "Minney" }

Language Complexity

Whilst on topic of adding unnecessary language features using wildcards, I'll leave a reminder of the dangers of adding language features with the retrospect of adding wildcards to Java generics:

“I am completely and totally humbled. Laid low. I realize now that I am simply not smart at all. I made the mistake of thinking that I could understand generics. I simply cannot. I just can't. This is really depressing. It is the first time that I've ever not been able to understand something related to computers, in any domain, anywhere, period.”

a feature which led Joshua Bloch (one of the designers of Java platform) to conclude:

"We simply cannot afford another wildcards"

mythz commented Mar 2, 2016

Positional deconstruction

I struggle to see how this feature justifies the complexity and new concepts it would add to C#. In just 1-line we're introducing is against a constructor, using a constructor without new, and the yet-seen-before wildcard in C#:

if (p is Person("Mickey", *)) // positional deconstruction

What are all these new concepts saving us from writing, this?

if ((p as Person)?.FirstName == "Mickey") {
}

Which is even less readable than above as you'll need to either carry the constructor definition in your head or routinely consult the class definition to find out what the condition is matching against.

Note: just because something is more terse doesn't make it more readable.

If we also wanted multiple conditions we add a static function and get readable, typed syntax today:

using static Test;
public class Test {
    public static bool match<T>(T o, Func<T, bool> predicate) => o != null && predicate(o);
}

if (match(p as Person, p => p.FirstName == "Mickey" && p.LastName == "Mouse")) {
}

With no added complexity cost to language or tooling. Tho would be nice if the compiler could optimize away the method call stack + lambda overhead for us :)

Object initializers for immutable objects

Whilst not as bad as positional deconstruction, I'm not seeing the benefits from immutable object initializers either:

var p = new Person { FirstName = "Mickey", LastName = "Mouse" }; // object initializer

From what I can tell it's just an alternative to:

var p = new Person(firstName:"Mickey", lastName:"Mouse");

var p = new Person("Mickey", "Mouse");

But worse as it relies on non-equivalent name matching, a reminder from Java with how bean properties map to Java getter/setter methods by naming convention - another ugly corner of the language.

The main friction I have with immutable objects are that they're harder to genericize using reflection, (e.g. serializers/mappers), which this feature isn't helping with.

With-expressions

Not in love with the syntax but fills a need that would require a bit of machinery without language support. My preference would probably be to go with a syntax operator variety.

p { FirstName = "Minney" }
p + { FirstName = "Minney" }
p .+ { FirstName = "Minney" }
p <- { FirstName = "Minney" }

Language Complexity

Whilst on topic of adding unnecessary language features using wildcards, I'll leave a reminder of the dangers of adding language features with the retrospect of adding wildcards to Java generics:

“I am completely and totally humbled. Laid low. I realize now that I am simply not smart at all. I made the mistake of thinking that I could understand generics. I simply cannot. I just can't. This is really depressing. It is the first time that I've ever not been able to understand something related to computers, in any domain, anywhere, period.”

a feature which led Joshua Bloch (one of the designers of Java platform) to conclude:

"We simply cannot afford another wildcards"

@MadsTorgersen

This comment has been minimized.

Show comment
Hide comment
@MadsTorgersen

MadsTorgersen Mar 2, 2016

Contributor

@mythz To be clear, these are features that are in support of pattern matching, and of immutable objects that we expect to have more and more of with the help of the records feature. I realize I posted them a bit out of context here, and I should have made that more clear. Over the coming days there'll be notes on those other features, to help evaluate in context.

You're seeing the sausage get made. Your feedback and that of others helps us make the right decisions in the end - just as in C# 6.

Funny you bring up Wildcards. I bear my part of the responsibility for that very feature, though in my defense I was an academic at the time. ;-) They are a perfect example of optimizing for expressiveness instead of usability.

Contributor

MadsTorgersen commented Mar 2, 2016

@mythz To be clear, these are features that are in support of pattern matching, and of immutable objects that we expect to have more and more of with the help of the records feature. I realize I posted them a bit out of context here, and I should have made that more clear. Over the coming days there'll be notes on those other features, to help evaluate in context.

You're seeing the sausage get made. Your feedback and that of others helps us make the right decisions in the end - just as in C# 6.

Funny you bring up Wildcards. I bear my part of the responsibility for that very feature, though in my defense I was an academic at the time. ;-) They are a perfect example of optimizing for expressiveness instead of usability.

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Mar 2, 2016

@mythz

The wildcards proposed for pattern matching are nothing like the wildcards in Java, the latter involving generics and variance. And even Java's wildcards aren't that bad, they express the variance in a pretty clever way and because you can express them at the variable and parameter level you have much more freedom than you do in .NET. Java's real problem is that the generic type inference is backwards (compared to C#) which makes generic resolution in general a massive string of four-letter-words and the compiler error messages are ridiculously cryptic.

HaloFour commented Mar 2, 2016

@mythz

The wildcards proposed for pattern matching are nothing like the wildcards in Java, the latter involving generics and variance. And even Java's wildcards aren't that bad, they express the variance in a pretty clever way and because you can express them at the variable and parameter level you have much more freedom than you do in .NET. Java's real problem is that the generic type inference is backwards (compared to C#) which makes generic resolution in general a massive string of four-letter-words and the compiler error messages are ridiculously cryptic.

@isaacabraham

This comment has been minimized.

Show comment
Hide comment
@isaacabraham

isaacabraham Mar 4, 2016

@CoolDadTx I can say from personal experience that pattern matching and deconstruction in general are definitely very, very powerful feature and can save lots of boilerplate, especially around conditional branching statements (particularly when conditions stretch over multiple variables) - code is generally simpler to read and reason about. If whatever syntax can be thought up and settled upon fits nicely with existing C# syntax, I think it'll be well worth having in the language.

@CoolDadTx I can say from personal experience that pattern matching and deconstruction in general are definitely very, very powerful feature and can save lots of boilerplate, especially around conditional branching statements (particularly when conditions stretch over multiple variables) - code is generally simpler to read and reason about. If whatever syntax can be thought up and settled upon fits nicely with existing C# syntax, I think it'll be well worth having in the language.

@MadsTorgersen

This comment has been minimized.

Show comment
Hide comment
@MadsTorgersen

MadsTorgersen Mar 4, 2016

Contributor

Two points:

First of all, let me just say that my initial example above was extremely simplistic, because the point wasn't to argue the value of the features, but to examine the range of possible mechanisms beneath them. I think that @CyrusNajmabadi and others brought up much more compelling examples of where you'd want positional deconstruction.

Secondly, there's a viewpoint inherent in the original records proposal (#206) that I gave short shrift in the original post above, but that I want to highlight as well: That we're not really talking about positional deconstruction - as in grabbing the properties back out in some order - but positional matching, which is more powerful because you can define named "active patterns" that can match or fail based on complex logic, and pull out pertinent data in non-straightforward ways. In a sense, positional deconstruction is just the simplest imaginable form of that, where you always match against the type itself, and always pull out values only of direct properties of the object.

Whether we want to embrace that extra power or not is an open design question. But it's part of the story of what positional patterns may be able to do for you.

Contributor

MadsTorgersen commented Mar 4, 2016

Two points:

First of all, let me just say that my initial example above was extremely simplistic, because the point wasn't to argue the value of the features, but to examine the range of possible mechanisms beneath them. I think that @CyrusNajmabadi and others brought up much more compelling examples of where you'd want positional deconstruction.

Secondly, there's a viewpoint inherent in the original records proposal (#206) that I gave short shrift in the original post above, but that I want to highlight as well: That we're not really talking about positional deconstruction - as in grabbing the properties back out in some order - but positional matching, which is more powerful because you can define named "active patterns" that can match or fail based on complex logic, and pull out pertinent data in non-straightforward ways. In a sense, positional deconstruction is just the simplest imaginable form of that, where you always match against the type itself, and always pull out values only of direct properties of the object.

Whether we want to embrace that extra power or not is an open design question. But it's part of the story of what positional patterns may be able to do for you.

@isaacabraham

This comment has been minimized.

Show comment
Hide comment
@isaacabraham

isaacabraham Mar 4, 2016

@MadsTorgersen OK, if we're talking about something akin to F# active patterns that makes a lot more sense. I agree that they're definitely very powerful and help make a lot of complex matching logic sane.

@MadsTorgersen OK, if we're talking about something akin to F# active patterns that makes a lot more sense. I agree that they're definitely very powerful and help make a lot of complex matching logic sane.

@CyrusNajmabadi

This comment has been minimized.

Show comment
Hide comment
@CyrusNajmabadi

CyrusNajmabadi Mar 4, 2016

Contributor

@isaacabraham

So are we saying that the constructor defines the deconstruction shape? Interesting.

We are considering (but have not even proposed or spec'ed out how it would work) a system whereby constructors could be taken into account whne using positional pattern matching.

The general intuition I have is:

  1. The type can fully describe its deconstruction shape (though some mechanism we haven't specified yet; possibly a method; possibly an operator). This takes priority if it exists.
  2. Externally from the type you should be able to describe its deconstruction shape (through some mechanism we haven't specified yet; possibly an extension method).
  3. If neither of the above exist, we may try to infer the deconstruction shape from the constructor (through some mechanism we haven't specified yet).

This is the current state of things. I have been tasked with coming up with a proposal for '3', and i'm going to try to start on that next week. I have some ideas about what might work. But i'll have to experiment and run tests against large corpuses of C# code to see what sort of results we produce.

Thanks!

Contributor

CyrusNajmabadi commented Mar 4, 2016

@isaacabraham

So are we saying that the constructor defines the deconstruction shape? Interesting.

We are considering (but have not even proposed or spec'ed out how it would work) a system whereby constructors could be taken into account whne using positional pattern matching.

The general intuition I have is:

  1. The type can fully describe its deconstruction shape (though some mechanism we haven't specified yet; possibly a method; possibly an operator). This takes priority if it exists.
  2. Externally from the type you should be able to describe its deconstruction shape (through some mechanism we haven't specified yet; possibly an extension method).
  3. If neither of the above exist, we may try to infer the deconstruction shape from the constructor (through some mechanism we haven't specified yet).

This is the current state of things. I have been tasked with coming up with a proposal for '3', and i'm going to try to start on that next week. I have some ideas about what might work. But i'll have to experiment and run tests against large corpuses of C# code to see what sort of results we produce.

Thanks!

@CyrusNajmabadi

This comment has been minimized.

Show comment
Hide comment
@CyrusNajmabadi

CyrusNajmabadi Mar 4, 2016

Contributor

@isaacabraham

So - would the constructor be one written by the user, or auto-generated?

We have not investigated or spec'ed anything out yet. I have no idea :)

Another side point - you make an interesting point regarding giving users the freedom to choose what feature to use for e.g. pattern matching. So the developer can use either positional-style or explicit-named-fields style. There's another side to this coin though - the risk that there's not an idiomatic way to achieve this in C#.

All things being equal, i would be ok with that. But i'm ok with non-idiomatic when things are not equal. In my personal opinion there is enough value in brevity and symmetry to warrant positional deconstruction (plus it's practically required for tuples). As such, i think we'd want them. But this will not be a decision made in isolation. We will treat it as seriously as we do all language changes.

As it is, I suspect that the concept of pattern matching will be very, very new to many C# developers - might it be better to have a single, prescriptive way of achieving a given feature? I fear for new developers to the language seeing several ways to do the same thing.

That's definitely a reasonable concern. Though, one that i'm not sure i'm that concerned about here. We have, in the past, introduced very new things for C# devs with multiple ways to use them. For example, when we introduced linq we heard the same things about the method form .Where(...).Select(...) vs the query form where ... select .... But in practice it turned out fine and people mix and match the different facilities where it makes sense to them.

Contributor

CyrusNajmabadi commented Mar 4, 2016

@isaacabraham

So - would the constructor be one written by the user, or auto-generated?

We have not investigated or spec'ed anything out yet. I have no idea :)

Another side point - you make an interesting point regarding giving users the freedom to choose what feature to use for e.g. pattern matching. So the developer can use either positional-style or explicit-named-fields style. There's another side to this coin though - the risk that there's not an idiomatic way to achieve this in C#.

All things being equal, i would be ok with that. But i'm ok with non-idiomatic when things are not equal. In my personal opinion there is enough value in brevity and symmetry to warrant positional deconstruction (plus it's practically required for tuples). As such, i think we'd want them. But this will not be a decision made in isolation. We will treat it as seriously as we do all language changes.

As it is, I suspect that the concept of pattern matching will be very, very new to many C# developers - might it be better to have a single, prescriptive way of achieving a given feature? I fear for new developers to the language seeing several ways to do the same thing.

That's definitely a reasonable concern. Though, one that i'm not sure i'm that concerned about here. We have, in the past, introduced very new things for C# devs with multiple ways to use them. For example, when we introduced linq we heard the same things about the method form .Where(...).Select(...) vs the query form where ... select .... But in practice it turned out fine and people mix and match the different facilities where it makes sense to them.

@CyrusNajmabadi

This comment has been minimized.

Show comment
Hide comment
@CyrusNajmabadi

CyrusNajmabadi Mar 4, 2016

Contributor

@TonyValenti

What they want more than features is a consistent way to use those features.

Consistency is in the eye of the beholder :)

To me deconstruction, is the parallel of construction. As such, it's inconsistent that i can construct something some way, but then have to deconstruct it some other way :)

Contributor

CyrusNajmabadi commented Mar 4, 2016

@TonyValenti

What they want more than features is a consistent way to use those features.

Consistency is in the eye of the beholder :)

To me deconstruction, is the parallel of construction. As such, it's inconsistent that i can construct something some way, but then have to deconstruct it some other way :)

@isaacabraham

This comment has been minimized.

Show comment
Hide comment
@isaacabraham

isaacabraham Mar 4, 2016

@CyrusNajmabadi Thanks for the detailed answers. And yes, I know this is a very early stage of design - and it's great that you and the rest of the team are pro-actively getting feedback at this stage too. I don't expect you to have all the answers just yet - but questions like this from myself and others are probably useful to you all the same :-)

And of course LINQ is a great example of that point. Do you use a for loop? Lambda syntax? Query syntax? etc. - and yes, I've been there and done that on many real-world projects with large teams, having to come up with internal standards for what to use, and in what circumstances. It's a constant challenge as the language grows new features - some people have a tendency to adopt new feature everywhere simply because they're new; others shy away from there; others use them inconsistently etc. etc..

So yes, freedom of choice is all well and good - but sometimes, just sometimes, developers want to be told what to do (even if they don't admit it!).

@CyrusNajmabadi Thanks for the detailed answers. And yes, I know this is a very early stage of design - and it's great that you and the rest of the team are pro-actively getting feedback at this stage too. I don't expect you to have all the answers just yet - but questions like this from myself and others are probably useful to you all the same :-)

And of course LINQ is a great example of that point. Do you use a for loop? Lambda syntax? Query syntax? etc. - and yes, I've been there and done that on many real-world projects with large teams, having to come up with internal standards for what to use, and in what circumstances. It's a constant challenge as the language grows new features - some people have a tendency to adopt new feature everywhere simply because they're new; others shy away from there; others use them inconsistently etc. etc..

So yes, freedom of choice is all well and good - but sometimes, just sometimes, developers want to be told what to do (even if they don't admit it!).

@mythz

This comment has been minimized.

Show comment
Hide comment
@mythz

mythz Mar 4, 2016

To me deconstruction, is the parallel of construction. As such, it's inconsistent that i can construct something some way, but then have to deconstruct it some other way :)

Wouldn't that only apply to "dumb" constructors i.e. that only set properties that map 1:1 with constructor arguments? In which case you can get by with no constructor and setting the properties in an object intializer. How's it going to work when there's no relationship between constructor arguments and the types stored fields?

mythz commented Mar 4, 2016

To me deconstruction, is the parallel of construction. As such, it's inconsistent that i can construct something some way, but then have to deconstruct it some other way :)

Wouldn't that only apply to "dumb" constructors i.e. that only set properties that map 1:1 with constructor arguments? In which case you can get by with no constructor and setting the properties in an object intializer. How's it going to work when there's no relationship between constructor arguments and the types stored fields?

@alrz

This comment has been minimized.

Show comment
Hide comment
@alrz

alrz Mar 4, 2016

Contributor

it's inconsistent that i can construct something some way, but not deconstruct it another way

Using type invocation for records; that just makes sense,

// Person is a record
var person = Person("Foo", "Bar");
if(person is Person("Foo", "Bar"))

But for regular classes that you use new,

// Person is not a record
var person = new Person("Foo", "Bar");
if(person is new Person("Foo", "Bar")) // ????

This is what F# is doing, it requires new just for regular classes (§6.4.2):

The type ty must not be an array, record, union or tuple type

So it is reasonable to not be able to deconstruct them in the same way.

Contributor

alrz commented Mar 4, 2016

it's inconsistent that i can construct something some way, but not deconstruct it another way

Using type invocation for records; that just makes sense,

// Person is a record
var person = Person("Foo", "Bar");
if(person is Person("Foo", "Bar"))

But for regular classes that you use new,

// Person is not a record
var person = new Person("Foo", "Bar");
if(person is new Person("Foo", "Bar")) // ????

This is what F# is doing, it requires new just for regular classes (§6.4.2):

The type ty must not be an array, record, union or tuple type

So it is reasonable to not be able to deconstruct them in the same way.

@CyrusNajmabadi

This comment has been minimized.

Show comment
Hide comment
@CyrusNajmabadi

CyrusNajmabadi Mar 4, 2016

Contributor

In which case you can get by with no constructor and setting the properties in an object intializer.

I personally try to use immutable objects as much as possible. So, unfortunately, object initializes are off the table for me.

Contributor

CyrusNajmabadi commented Mar 4, 2016

In which case you can get by with no constructor and setting the properties in an object intializer.

I personally try to use immutable objects as much as possible. So, unfortunately, object initializes are off the table for me.

@CyrusNajmabadi

This comment has been minimized.

Show comment
Hide comment
@CyrusNajmabadi

CyrusNajmabadi Mar 4, 2016

Contributor

So it is reasonable to not be able to deconstruct them in the same way

In your eyes, sure. In mine no. I can positionally construct something. I would like to be able to positionally deconstruct something.

Anyways, i'm not sure we're making any progress here. The feedback has been heard. There is a lot of pushback against positional deconstruction/matching in certain circumstances. At this point, i'm not sure debating any further is adding any new information to the discussion. Perhaps we should table this?

Contributor

CyrusNajmabadi commented Mar 4, 2016

So it is reasonable to not be able to deconstruct them in the same way

In your eyes, sure. In mine no. I can positionally construct something. I would like to be able to positionally deconstruct something.

Anyways, i'm not sure we're making any progress here. The feedback has been heard. There is a lot of pushback against positional deconstruction/matching in certain circumstances. At this point, i'm not sure debating any further is adding any new information to the discussion. Perhaps we should table this?

@CyrusNajmabadi

This comment has been minimized.

Show comment
Hide comment
@CyrusNajmabadi

CyrusNajmabadi Mar 4, 2016

Contributor

So yes, freedom of choice is all well and good - but sometimes, just sometimes, developers want to be told what to do (even if they don't admit it!).

I'm really hesitant to go there. The language has thrived so far making judicious choices about allowing people multiple ways to do things. Linq is a great example. When i talk to many C# developers it's literally at the top of their list for things they love about C#. Turns out that flexibility and non-presciprtive attitude turned out to be a huge benefit, even if it means we don't have total consistency over the entirety of C# code out there. It turned out there to be great not having a 'one size fits all' attitude. As you yourself mentioned, you've see all sorts of different developers who have each gravitated to the different options we made available. This does not seem to have been a negative for linq. Indeed, it seems to be one of its strengths, and why it was so widely accepted and adopted.

Contributor

CyrusNajmabadi commented Mar 4, 2016

So yes, freedom of choice is all well and good - but sometimes, just sometimes, developers want to be told what to do (even if they don't admit it!).

I'm really hesitant to go there. The language has thrived so far making judicious choices about allowing people multiple ways to do things. Linq is a great example. When i talk to many C# developers it's literally at the top of their list for things they love about C#. Turns out that flexibility and non-presciprtive attitude turned out to be a huge benefit, even if it means we don't have total consistency over the entirety of C# code out there. It turned out there to be great not having a 'one size fits all' attitude. As you yourself mentioned, you've see all sorts of different developers who have each gravitated to the different options we made available. This does not seem to have been a negative for linq. Indeed, it seems to be one of its strengths, and why it was so widely accepted and adopted.

@alrz

This comment has been minimized.

Show comment
Hide comment
@alrz

alrz Mar 4, 2016

Contributor

@CyrusNajmabadi I meant if we use is new to hint the compiler that it's ok to use those sort of matching of parameter and property names, I'd be ok with that. That is truly symmetrical construction and deconstruction.

Contributor

alrz commented Mar 4, 2016

@CyrusNajmabadi I meant if we use is new to hint the compiler that it's ok to use those sort of matching of parameter and property names, I'd be ok with that. That is truly symmetrical construction and deconstruction.

@CyrusNajmabadi

This comment has been minimized.

Show comment
Hide comment
@CyrusNajmabadi

CyrusNajmabadi Mar 4, 2016

Contributor

@alrz

Potentially, though i can see issues with that. We've always used 'new' to indicate that you're constructing. So in this specific case you would not want the 'new' because you would explicitly not be constructing. Indeed, as you'd be doing the opposite, there's a strong argument to be made that 'new' could be considered very inappropriate here.

Note that the symmetry i was referring to was in positionally passing data into the object as i construct it, vs positionally extracting that data back out again. As that was something you could do with constructors, it's something i would like with de-constructors. (man, i keep wanting to say 'destructors', and that's just not right ;-) ).

I think there's some really nice consistency here if we go that route: "Node(...)" tells you the type and the positional information you need. When you use "new" you're saying "now construct me one of these" and if you leave off "new" you're saying "now deconstruct this guy".

Contributor

CyrusNajmabadi commented Mar 4, 2016

@alrz

Potentially, though i can see issues with that. We've always used 'new' to indicate that you're constructing. So in this specific case you would not want the 'new' because you would explicitly not be constructing. Indeed, as you'd be doing the opposite, there's a strong argument to be made that 'new' could be considered very inappropriate here.

Note that the symmetry i was referring to was in positionally passing data into the object as i construct it, vs positionally extracting that data back out again. As that was something you could do with constructors, it's something i would like with de-constructors. (man, i keep wanting to say 'destructors', and that's just not right ;-) ).

I think there's some really nice consistency here if we go that route: "Node(...)" tells you the type and the positional information you need. When you use "new" you're saying "now construct me one of these" and if you leave off "new" you're saying "now deconstruct this guy".

@CyrusNajmabadi

This comment has been minimized.

Show comment
Hide comment
@CyrusNajmabadi

CyrusNajmabadi Mar 4, 2016

Contributor

Note: no syntax is set in stone. We'll definitely want to evaluate a bunch of options here.

Contributor

CyrusNajmabadi commented Mar 4, 2016

Note: no syntax is set in stone. We'll definitely want to evaluate a bunch of options here.

@alrz

This comment has been minimized.

Show comment
Hide comment
@alrz

alrz Mar 4, 2016

Contributor

@CyrusNajmabadi Yes, but I'm thinking that another keyword should do, because is Node(..) in case of a non-record type is way different than simple matching of the type and "positional properties", it matches the "names" not positions (yes positions are significant in the pattern itself but eventually the compiler depends on names), so in my opinion it deserves its own keyword because semantics are different. PS: I don't have a strong disagreement here because symmetric construction and deconstruction is definitely a good thing, just to suggest some alternatives, that's it.

Contributor

alrz commented Mar 4, 2016

@CyrusNajmabadi Yes, but I'm thinking that another keyword should do, because is Node(..) in case of a non-record type is way different than simple matching of the type and "positional properties", it matches the "names" not positions (yes positions are significant in the pattern itself but eventually the compiler depends on names), so in my opinion it deserves its own keyword because semantics are different. PS: I don't have a strong disagreement here because symmetric construction and deconstruction is definitely a good thing, just to suggest some alternatives, that's it.

@ayende

This comment has been minimized.

Show comment
Hide comment
@ayende

ayende Mar 5, 2016

One thing that worries me when I'm reading this is performance. In particular, runtime performance of those features.
Linq, as a good example, is already a no go feature in high performance code, because while it is extremely readable, the cost of delegate invocations and lambda allocation can be extremely high.

Looking at the proposed work the compiler has to do to support those features, I'm afraid that this would turn into another feature that high perf code cannot touch.

The fact that this is compiler generated code doesn't matter for the costs we are going to have to pay.

ayende commented Mar 5, 2016

One thing that worries me when I'm reading this is performance. In particular, runtime performance of those features.
Linq, as a good example, is already a no go feature in high performance code, because while it is extremely readable, the cost of delegate invocations and lambda allocation can be extremely high.

Looking at the proposed work the compiler has to do to support those features, I'm afraid that this would turn into another feature that high perf code cannot touch.

The fact that this is compiler generated code doesn't matter for the costs we are going to have to pay.

@CyrusNajmabadi

This comment has been minimized.

Show comment
Hide comment
@CyrusNajmabadi

CyrusNajmabadi Mar 5, 2016

Contributor

@ayende Could you be more specific. What work in particular do you think would be an issue?

Contributor

CyrusNajmabadi commented Mar 5, 2016

@ayende Could you be more specific. What work in particular do you think would be an issue?

@alrz

This comment has been minimized.

Show comment
Hide comment
@alrz

alrz Mar 6, 2016

Contributor

@CyrusNajmabadi

But i don't want to write it as a record. I want to write it as a class

I thought about this and now I think I understand why. There are types that are not records really but happen to have some immutable properties which are initialized in the constructor by parameters. For example,

public class Person {
  public string FirstName { get; }
  public string LastName { get; }
  public string MiddleName { get; }
  public Person(string firstName, string lastName) {
    this.FirstName = firstName;
    this.LastName = lastName;
    // presumably not possible with records
    // and perhaps not appropriate for a record
    // because record's constructor is intended to be 'dumb'
    // it just gets parameters and sets properties, one by one
    this.MiddleName = GetMiddleName(); 
  }
 }

And of course, it totally makes sense to have a symmetrical construction and deconstruction for this type.

The solution proposed here is dealing with two issues:

  1. The convention difference between parameter and property names which leads us to case insensitivity.
  2. It assumes that each parameter (hopefully) initializes a property with the same name, modulo case. This is a big assumption, not that one might actually do it, but if a code is not correct, it should not be possible to write it I believe, and when I say that, I mean it should produce a compile-time error. It would be a shame if the compiler itself does depend on such assumptions.

I've proposed some kind of parameters (#9234) that might address both of these issues. For example,

public class Person {
  public string FirstName { get; }
  public string LastName { get; }
  public string MiddleName { get; }
  // equivalent to the constructor above 
  public Person(this.FirstName, this.LastName) {
    this.MiddleName = GetMiddleName(); 
  }
 }

This is somehow similar to the feature that facilitates With method,

abstract Person With(string firstName = this.FirstName, string lastName = this.LastName);

But in constructors we don't need an optional argument, because the property itself is being initialized, so we just write it instead of the parameter, and with no doubt, the first parameter here will be assigned to the FirstName property and so on.

Same can be helpful for implementing GetValues or is operator.

public void GetValues(out this.FirstName, out this.MiddleName, out this.LastName);

And in this case, the compiler doesn't even need to actually call this method. It is merely a shape for a custom positional deconstruction (besides of the constructor itself). So,

if(person is Person("Mickey", var middleName, *)) {}
// equivalent to
if(person.FirstName == "Mickey") {
  var middleName = person.MiddleName;
}

No additional out parameter passing and no assumptions.

Contributor

alrz commented Mar 6, 2016

@CyrusNajmabadi

But i don't want to write it as a record. I want to write it as a class

I thought about this and now I think I understand why. There are types that are not records really but happen to have some immutable properties which are initialized in the constructor by parameters. For example,

public class Person {
  public string FirstName { get; }
  public string LastName { get; }
  public string MiddleName { get; }
  public Person(string firstName, string lastName) {
    this.FirstName = firstName;
    this.LastName = lastName;
    // presumably not possible with records
    // and perhaps not appropriate for a record
    // because record's constructor is intended to be 'dumb'
    // it just gets parameters and sets properties, one by one
    this.MiddleName = GetMiddleName(); 
  }
 }

And of course, it totally makes sense to have a symmetrical construction and deconstruction for this type.

The solution proposed here is dealing with two issues:

  1. The convention difference between parameter and property names which leads us to case insensitivity.
  2. It assumes that each parameter (hopefully) initializes a property with the same name, modulo case. This is a big assumption, not that one might actually do it, but if a code is not correct, it should not be possible to write it I believe, and when I say that, I mean it should produce a compile-time error. It would be a shame if the compiler itself does depend on such assumptions.

I've proposed some kind of parameters (#9234) that might address both of these issues. For example,

public class Person {
  public string FirstName { get; }
  public string LastName { get; }
  public string MiddleName { get; }
  // equivalent to the constructor above 
  public Person(this.FirstName, this.LastName) {
    this.MiddleName = GetMiddleName(); 
  }
 }

This is somehow similar to the feature that facilitates With method,

abstract Person With(string firstName = this.FirstName, string lastName = this.LastName);

But in constructors we don't need an optional argument, because the property itself is being initialized, so we just write it instead of the parameter, and with no doubt, the first parameter here will be assigned to the FirstName property and so on.

Same can be helpful for implementing GetValues or is operator.

public void GetValues(out this.FirstName, out this.MiddleName, out this.LastName);

And in this case, the compiler doesn't even need to actually call this method. It is merely a shape for a custom positional deconstruction (besides of the constructor itself). So,

if(person is Person("Mickey", var middleName, *)) {}
// equivalent to
if(person.FirstName == "Mickey") {
  var middleName = person.MiddleName;
}

No additional out parameter passing and no assumptions.

@TonyValenti

This comment has been minimized.

Show comment
Hide comment
@TonyValenti

TonyValenti Mar 6, 2016

I like deconstruction but I feel as though the property names themselves
should be specified. I believe this would leave the code much less brittle
and a lot easier to understand.

Why not do something like this?

if(person is Person(FirstName = "Mickey", var mname = MiddleName)) {

}

On Sun, Mar 6, 2016 at 12:29 AM, Alireza Habibi notifications@github.com
wrote:

@CyrusNajmabadi https://github.com/CyrusNajmabadi

But i don't want to write it as a record. I want to write it as a class

I thought about this and now I think I understand why. There are types
that are not records really but happen to have some immutable properties
which are initialized in the constructor by parameters. For example,

public class Person {
public string FirstName { get; }
public string LastName { get; }
public string MiddleName { get; }
public Person(string firstName, string LastName) {
this.FirstName = firstName;
this.LastName = lastName;
// presumely not possible with records
// and perhaps not appropriate for a record
// because record's constructor is intended to be 'dumb'
// it just gets parameters and sets properties, one by one
this.MiddleName = GetMiddleName();
}
}

And of course, it totally makes sense to have a symmetrical construction
and deconstruction for this type.

The solution proposed here is dealing with two issues:

  1. The convention difference between parameter and property names
    which leads us to case insensitivity.
  2. It assumes that each parameter (hopefully) initializes a property
    with the same name, modulo case. This is a big assumption, not that one
    might actually do it, but if a code is not correct, it should not be
    possible
    to write it I belive, and when I say that, I mean it should
    produce a compile-time error. It would be a shame if the compiler itself
    does depend on such assumptions.

I've proposed some kind of parameters (#9234
#9234) that might address both
of these issues. I'll explain.

public class Person {
public string FirstName { get; }
public string LastName { get; }
public string MiddleName { get; }
// equivalent to the constructor above
public Person(this.FirstName, this.LastName) {
this.MiddleName = GetMiddleName();
}
}

This is somehow similar to the feature that facilitates With method,

abstract Person With(string firstName = this.FirstName, string lastName =
this.LastName);

But in constructors we don't need an optional argument, because the
property itself is being initialized, so we just write it instead of
the parameter, and with no doubt, the first parameter here will be assigned
to the FirstName property and so on.

Same can be helpful for implementing GetValues or is operator.

public void GetValues(out this.FirstName, out this.MiddleName, out this.LastName);

And in this case, the compiler doesn't even need to actually call this
method. It is merely a shape for a custom positional deconstruction
(besides of the constructor itself). So,

if(person is Person("Mickey", var middleName, *)) {}
// equivalent to
if(person.FirstName == "Mickey") {
var middleName = person.MiddleName;
}

No additional out parameter passing and no assumptions.


Reply to this email directly or view it on GitHub
#9330 (comment).

Tony Valenti

I like deconstruction but I feel as though the property names themselves
should be specified. I believe this would leave the code much less brittle
and a lot easier to understand.

Why not do something like this?

if(person is Person(FirstName = "Mickey", var mname = MiddleName)) {

}

On Sun, Mar 6, 2016 at 12:29 AM, Alireza Habibi notifications@github.com
wrote:

@CyrusNajmabadi https://github.com/CyrusNajmabadi

But i don't want to write it as a record. I want to write it as a class

I thought about this and now I think I understand why. There are types
that are not records really but happen to have some immutable properties
which are initialized in the constructor by parameters. For example,

public class Person {
public string FirstName { get; }
public string LastName { get; }
public string MiddleName { get; }
public Person(string firstName, string LastName) {
this.FirstName = firstName;
this.LastName = lastName;
// presumely not possible with records
// and perhaps not appropriate for a record
// because record's constructor is intended to be 'dumb'
// it just gets parameters and sets properties, one by one
this.MiddleName = GetMiddleName();
}
}

And of course, it totally makes sense to have a symmetrical construction
and deconstruction for this type.

The solution proposed here is dealing with two issues:

  1. The convention difference between parameter and property names
    which leads us to case insensitivity.
  2. It assumes that each parameter (hopefully) initializes a property
    with the same name, modulo case. This is a big assumption, not that one
    might actually do it, but if a code is not correct, it should not be
    possible
    to write it I belive, and when I say that, I mean it should
    produce a compile-time error. It would be a shame if the compiler itself
    does depend on such assumptions.

I've proposed some kind of parameters (#9234
#9234) that might address both
of these issues. I'll explain.

public class Person {
public string FirstName { get; }
public string LastName { get; }
public string MiddleName { get; }
// equivalent to the constructor above
public Person(this.FirstName, this.LastName) {
this.MiddleName = GetMiddleName();
}
}

This is somehow similar to the feature that facilitates With method,

abstract Person With(string firstName = this.FirstName, string lastName =
this.LastName);

But in constructors we don't need an optional argument, because the
property itself is being initialized, so we just write it instead of
the parameter, and with no doubt, the first parameter here will be assigned
to the FirstName property and so on.

Same can be helpful for implementing GetValues or is operator.

public void GetValues(out this.FirstName, out this.MiddleName, out this.LastName);

And in this case, the compiler doesn't even need to actually call this
method. It is merely a shape for a custom positional deconstruction
(besides of the constructor itself). So,

if(person is Person("Mickey", var middleName, *)) {}
// equivalent to
if(person.FirstName == "Mickey") {
var middleName = person.MiddleName;
}

No additional out parameter passing and no assumptions.


Reply to this email directly or view it on GitHub
#9330 (comment).

Tony Valenti

@orthoxerox

This comment has been minimized.

Show comment
Hide comment
@orthoxerox

orthoxerox Mar 6, 2016

Contributor

I wrote my primary constructor proposal in #9517. The idea is to avoid trying to deduce a deconstructor from a constructor if the constructor is not primary. This way positional deconstruction will work only in those cases where it is likely to be used.

Contributor

orthoxerox commented Mar 6, 2016

I wrote my primary constructor proposal in #9517. The idea is to avoid trying to deduce a deconstructor from a constructor if the constructor is not primary. This way positional deconstruction will work only in those cases where it is likely to be used.

@paulomorgado

This comment has been minimized.

Show comment
Hide comment
@paulomorgado

paulomorgado Mar 6, 2016

@CyrusNajmabadi,

There is a lot of pushback against positional deconstruction/matching in certain circumstances.

There is a lot of pushback from very few. It might be the case that a large mass of people agree with your view and think you're defending your points in very reasonable way. I do!

@CyrusNajmabadi,

There is a lot of pushback against positional deconstruction/matching in certain circumstances.

There is a lot of pushback from very few. It might be the case that a large mass of people agree with your view and think you're defending your points in very reasonable way. I do!

@orthoxerox

This comment has been minimized.

Show comment
Hide comment
@orthoxerox

orthoxerox Mar 6, 2016

Contributor

@CyrusNajmabadi I agree that positional deconstruction is a good and useful feature, as long as it's opted into by implementing a custom deconstructor or a primary constructor.

Contributor

orthoxerox commented Mar 6, 2016

@CyrusNajmabadi I agree that positional deconstruction is a good and useful feature, as long as it's opted into by implementing a custom deconstructor or a primary constructor.

@timgoodman

This comment has been minimized.

Show comment
Hide comment
@timgoodman

timgoodman Mar 7, 2016

I like the proposed positional deconstruction syntax. I'm guessing the folks objecting to it or finding it confusing aren't familiar with pattern matching in other languages. But adding pattern matching to C# is very high on a lot of people's wish lists (including mine). A great thing about C# has been the willingness to add awesome features of other languages even if it means people had to learn some new syntax - e.g., lambdas.

I like the proposed positional deconstruction syntax. I'm guessing the folks objecting to it or finding it confusing aren't familiar with pattern matching in other languages. But adding pattern matching to C# is very high on a lot of people's wish lists (including mine). A great thing about C# has been the willingness to add awesome features of other languages even if it means people had to learn some new syntax - e.g., lambdas.

@bbarry

This comment has been minimized.

Show comment
Hide comment
@bbarry

bbarry Mar 7, 2016

I'm not opposed to it either, I think it should be implicit for records and tuples and explicitly opted in via extension methods or something like that for other types. Further I think it should result in basically the exact same IL for deconstruction that property patterns do (there should be 0 runtime cost using this form vs property patterns).

bbarry commented Mar 7, 2016

I'm not opposed to it either, I think it should be implicit for records and tuples and explicitly opted in via extension methods or something like that for other types. Further I think it should result in basically the exact same IL for deconstruction that property patterns do (there should be 0 runtime cost using this form vs property patterns).

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Mar 7, 2016

@bbarry

I'm not opposed to it either, I think it should be implicit for records and tuples and explicitly opted in via extension methods or something like that for other types. Further I think it should result in basically the exact same IL for deconstruction that property patterns do (there should be 0 runtime cost using this form vs property patterns).

Isn't that self-contradicting? If decomposition is enabled by an extension method it would have to invoke different IL than a pattern property, namely a static method call, which would then proceed to populate it's out variables (or tuple return value) in any way that it saw fit.

HaloFour commented Mar 7, 2016

@bbarry

I'm not opposed to it either, I think it should be implicit for records and tuples and explicitly opted in via extension methods or something like that for other types. Further I think it should result in basically the exact same IL for deconstruction that property patterns do (there should be 0 runtime cost using this form vs property patterns).

Isn't that self-contradicting? If decomposition is enabled by an extension method it would have to invoke different IL than a pattern property, namely a static method call, which would then proceed to populate it's out variables (or tuple return value) in any way that it saw fit.

@jnm2

This comment has been minimized.

Show comment
Hide comment
@jnm2

jnm2 Mar 7, 2016

Contributor

Just addressing the casing issue: isn't the best of both worlds available? Property names should still be PascalCased and parameter names should still be camelCased. To match, the parameter name should be required to be identical to the property name but with the first character changed to lowercase.

No case-insensitivity necessary. I agree that it would be an unwanted cost to add insensitivity here.

Contributor

jnm2 commented Mar 7, 2016

Just addressing the casing issue: isn't the best of both worlds available? Property names should still be PascalCased and parameter names should still be camelCased. To match, the parameter name should be required to be identical to the property name but with the first character changed to lowercase.

No case-insensitivity necessary. I agree that it would be an unwanted cost to add insensitivity here.

@bbarry

This comment has been minimized.

Show comment
Hide comment
@bbarry

bbarry Mar 7, 2016

Not necessarily. A GetValues() method isn't needed at all beyond the fact that it has a signature that provides a way to impose order on the relevant properties. This order could be derived for example from the With() method as I described above #9330 (comment)

bbarry commented Mar 7, 2016

Not necessarily. A GetValues() method isn't needed at all beyond the fact that it has a signature that provides a way to impose order on the relevant properties. This order could be derived for example from the With() method as I described above #9330 (comment)

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Mar 7, 2016

I threw together a little crappy microbenchmark comparing some different decomposition strategies. Mileage may vary but here's the numbers it spits out for me:

Benchmark Debug 32-bit Debug 64-bit Release 32-bit Release 64-bit
Properties 8.63 8.76 1.01 0.51
Instance out 22.37 21.36 1.51 0.50
Instance tuple 50.60 52.75 4.53 10.57
Static out 27.05 27.02 1.83 0.50
Static tuple 1:04.14 1:13.82 18.65 21.17

Not terribly surprising. The JIT likely is successfully inlining both the property accessors and the out methods in those simple cases.

Here's the benchmark, feel free to tear it apart:

https://gist.github.com/HaloFour/d2ca5c056ba1139245f0

HaloFour commented Mar 7, 2016

I threw together a little crappy microbenchmark comparing some different decomposition strategies. Mileage may vary but here's the numbers it spits out for me:

Benchmark Debug 32-bit Debug 64-bit Release 32-bit Release 64-bit
Properties 8.63 8.76 1.01 0.51
Instance out 22.37 21.36 1.51 0.50
Instance tuple 50.60 52.75 4.53 10.57
Static out 27.05 27.02 1.83 0.50
Static tuple 1:04.14 1:13.82 18.65 21.17

Not terribly surprising. The JIT likely is successfully inlining both the property accessors and the out methods in those simple cases.

Here's the benchmark, feel free to tear it apart:

https://gist.github.com/HaloFour/d2ca5c056ba1139245f0

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Mar 7, 2016

@bbarry What is the benefit of that over the argument names of the constructor? Seems that it's still voodoo, it's just been moved to a different place and still requires new syntax with new grammatic rules beyond being a simple extension method. How would those special with methods be represented by normal IL/C#? Attributes?

HaloFour commented Mar 7, 2016

@bbarry What is the benefit of that over the argument names of the constructor? Seems that it's still voodoo, it's just been moved to a different place and still requires new syntax with new grammatic rules beyond being a simple extension method. How would those special with methods be represented by normal IL/C#? Attributes?

@bbarry

This comment has been minimized.

Show comment
Hide comment
@bbarry

bbarry Mar 7, 2016

Constructors that currently exist for types aren't necessarily one to one with properties on the type. I don't think anything special is necessary in applying that contextual keyword to the method signature other than applying those semantic rules to the method.

bbarry commented Mar 7, 2016

Constructors that currently exist for types aren't necessarily one to one with properties on the type. I don't think anything special is necessary in applying that contextual keyword to the method signature other than applying those semantic rules to the method.

@bbarry

This comment has been minimized.

Show comment
Hide comment
@bbarry

bbarry Mar 7, 2016

That said I am not sure that specific syntax is a great idea; only that it is a possible way to achieve the functionality.

bbarry commented Mar 7, 2016

That said I am not sure that specific syntax is a great idea; only that it is a possible way to achieve the functionality.

@eyalsk

This comment has been minimized.

Show comment
Hide comment
@eyalsk

eyalsk Mar 8, 2016

It isn't crystal clear to me whether With(...) and GetValues(...) are methods that people need to implement or it's just methods that the compiler generates, can someone please clarify that for me?

eyalsk commented Mar 8, 2016

It isn't crystal clear to me whether With(...) and GetValues(...) are methods that people need to implement or it's just methods that the compiler generates, can someone please clarify that for me?

@orthoxerox

This comment has been minimized.

Show comment
Hide comment
@orthoxerox

orthoxerox Mar 8, 2016

Contributor

@eyalsk You can implement them yourself if you need them, records will implement them for you, the open question is whether any other classes should get them for free or not.

Contributor

orthoxerox commented Mar 8, 2016

@eyalsk You can implement them yourself if you need them, records will implement them for you, the open question is whether any other classes should get them for free or not.

@eyalsk

This comment has been minimized.

Show comment
Hide comment
@eyalsk

eyalsk Mar 10, 2016

@orthoxerox thank you for the clarification. :)

eyalsk commented Mar 10, 2016

@orthoxerox thank you for the clarification. :)

@gafter

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Apr 25, 2016

Member

Design notes have been archived at https://github.com/dotnet/roslyn/blob/future/docs/designNotes/2016-02-29%20C%23%20Design%20Meeting.md but discussion can continue here.

Member

gafter commented Apr 25, 2016

Design notes have been archived at https://github.com/dotnet/roslyn/blob/future/docs/designNotes/2016-02-29%20C%23%20Design%20Meeting.md but discussion can continue here.

@gafter gafter closed this Apr 25, 2016

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