Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Proposal: Destructuring (assignment?) statement #6400

Closed
gafter opened this issue Oct 28, 2015 · 168 comments
Closed

Proposal: Destructuring (assignment?) statement #6400

gafter opened this issue Oct 28, 2015 · 168 comments
Assignees
Labels
4 - In Review A fix for the issue is submitted for review. Area-Language Design Feature Request Resolution-Fixed The bug has been fixed and/or the requested behavior has been implemented

Comments

@gafter
Copy link
Member

gafter commented Oct 28, 2015

The spec has been moved

The spec for the proposed let statement has been moved to https://github.com/dotnet/roslyn/blob/future/docs/features/patterns.md

Below is a snapshot that may be out of date.


Inspired by an F# feature and a conversation on github, we could support decomposition like this:

block-statement
    : let-statement
    ;

let-statement
    : 'let' identifier '=' expression ';'
    | 'let' complex-pattern '=' expression ';'
    | 'let' complex-pattern '=' expression 'else' embedded-statement
    ;

let is an existing contextual keyword.

The form

let identifier = expression ;

is shorthand for

let var identifier = expression ;

(i.e. a var-pattern) and is a convenient way for declaring a read-only local variable.

Semantically, it is an error unless precisely one of the following is true

  1. the compiler can prove that the expression always matches the pattern; or
  2. an else clause is present.

If an else clause is present, it is an error if the endpoint of its embedded-statement is reachable.

Any pattern variables in the pattern are in scope throughout the enclosing block. They are not definitely assigned before the else clause, and are definitely assigned after the let-statement. It is an error to use these variables before their point of definition.

A let-statement is a block-statement and not an embedded-statement because its primary purpose is to introduce names into the enclosing scope. It therefore does not introduce a dangling-else ambiguity.

@gafter gafter added this to the C# 7 and VB 15 milestone Oct 28, 2015
@gafter gafter self-assigned this Oct 28, 2015
@gafter gafter changed the title Destructuring assignment for tuples and other types [Proposal] Destructuring assignment for tuples and other types Oct 28, 2015
@gafter gafter changed the title [Proposal] Destructuring assignment for tuples and other types Proposal: Destructuring assignment for tuples and other types Oct 28, 2015
@mattwar
Copy link
Contributor

mattwar commented Oct 28, 2015

Why

let (int x, int y) = ComputePoint();

when you could have

(int x, int y) = ComputePoint();

@gafter
Copy link
Member Author

gafter commented Oct 28, 2015

@mattwar I'm not sure what that syntax is supposed to be an example of. If you're proposing that we have a new statement form

    pattern = expression ;

Then we'd run into many syntactic ambiguities with the existing "assignment statement" and local-declaration forms.

@mattwar
Copy link
Contributor

mattwar commented Oct 28, 2015

I'm not assuming anything about patterns. I'm assuming tuple deconstruction. As far as I can tell, a tuple deconstruction syntax without a let keyword is not ambiguous.

@tmat
Copy link
Member

tmat commented Oct 28, 2015

I vote for:

let x, y = ComputePoint()

or

let (x, y) = ComputePoint()

@gafter
Copy link
Member Author

gafter commented Oct 28, 2015

@mattwar @tmat Sure, if we wanted single-level decomposition for tuples but not for any other data types or pattern forms, we could design a one-off construct for that use case. I believe that is not the only use case for decomposition. See, for example, records in #206.

The proposal here would support

struct Coordinates(double Longitude, double Latitude, double Height);
struct Destination(string Name, Coordinates Place);

and then

Destination d = ...
let Destination { Place is Coordinates { Longitude is var longitude } } = d;
... longitude ... // longitude is in scope here

or, using positional notation

Destination d = ...
let Destination(*, Coordinates(var longitude, *, *)) = d;
... longitude ... // longitude is in scope here

and many other decomposition forms.

@alrz
Copy link
Contributor

alrz commented Oct 28, 2015

What about

ComputePoint() case (int x, int y);

Implying a complete pattern. By the way, I didn't like these let x = expression; var x = expression; side by side.

@gafter
Copy link
Member Author

gafter commented Oct 29, 2015

@alrz Are you suggesting that case be a pattern-matching operator? We already have is as a pattern-matching operator.

By the way, I didn't like these let x = expression; var x = expression; side by side.

Why? They have the same effect, except the former gives you a read-only variable.

@alrz
Copy link
Contributor

alrz commented Oct 29, 2015

Are you suggesting that case be a pattern-matching operator?

Yes, is is for incomplete patterns, hence returns a bool. but in this case we are considering a single case that happens to be complete.

@gafter
Copy link
Member Author

gafter commented Oct 29, 2015

So I guess we would call this a "case statement"?

@alrz
Copy link
Contributor

alrz commented Oct 29, 2015

@gafter That would be it.

@gafter
Copy link
Member Author

gafter commented Oct 29, 2015

If I had to choose between

ComputePoint() case (int x, int y);

and

let (int x, int y) = ComputePoint();

I'd choose the latter. I think it is a bit confusing to have a statement (other than an expression-statement) start with an expression.

@alrz
Copy link
Contributor

alrz commented Oct 29, 2015

That is a match or switch with a single case so why introduce new keyword, there is no other places that = does the deconstruction (e.g. in property-pattern we use is) so I think that's more consistent than let.

e case (int x, int y); ...
e match(case (int x, int y): ... );

I admittedly, prefer expression first syntaxes, like match.

@HaloFour
Copy link

@alrz The scoping would be entirely different, though. The pattern variables don't escape their case in a match expression. With let the variables would be in the same scope as any other variable.

@alrz
Copy link
Contributor

alrz commented Oct 29, 2015

I didn't say they are exactly the same but syntactically similar. I'd say it's a match with a single case because it's complete with a single case. That's it.

.Select(t => (let (int x, int y) = t; x + y));
.Select(t => (t case (int x, int y); x + y));
.Select(t => t match(case (int x, int y): x + y));
.Select(t => t case (int x, int y): x + y);
.Select(case (int x, int y) => x + y);

That last line is from #6067 which I found most concise and expressive. The standard possible way would be using match in this context which I think can be simplified to the 4th line.

@HaloFour
Copy link

@alrz

I'd say it's a match with a single case because it's complete with a single case. That's it.

Which is why it doesn't work, the scope is all wrong.

The rest of your reply seems to be irrelevant in the context of this proposal. This has nothing to do with lambdas or extending parameter syntax with patterns.

@gafter gafter changed the title Proposal: Destructuring assignment for tuples and other types Proposal: Destructuring assignment statement Oct 29, 2015
@gafter
Copy link
Member Author

gafter commented Oct 29, 2015

.Select(t => (let (int x, int y) = t; x + y));

This would only make sense if we extend #6182 to include let statements, which we would if we do both #6400 and #6182. However it is not certain that we'll do both.

.Select(t => (t case (int x, int y); x + y));

This is your proposed alternative, which I find less readable.

.Select(t => t match(case (int x, int y): x + y));

This is already proposed as #5154. This issue is about a statement analogue that keeps the variables in scope.

.Select(t => t case (int x, int y): x + y);

This is an expression form not under consideration, nor does it have much to do with this issue (there are no statements in this example).

.Select(case (int x, int y) => x + y);

Ditto.

@gafter gafter changed the title Proposal: Destructuring assignment statement Proposal: Destructuring (assignment?) statement Oct 29, 2015
@alrz
Copy link
Contributor

alrz commented Oct 29, 2015

They all contain a single case complete pattern, that's why I mentioned them, anyway.

@ghord
Copy link

ghord commented Oct 29, 2015

Implicit var in identifier-only version

let x = 3;

should also extend to tuples (and records?):

let (x,y) = ComputePoint();

@tpetrina
Copy link

Why not borrow ES6 syntax and allow the following

.Select(t => {x, y} = t; x + y)

Or, maybe allow destructuring directly available in parameters

.Select( t is {x,y} => x + y )
.Select( vector is Polar p => p.angle )
.Select( user is {Name, Paycheck} => ... })

It feels that almost any syntax is terse and unreadable.

@orthoxerox
Copy link
Contributor

What would this look like if ComputePoint() returned a Point with an overridden is operator? let Point(x, y) = ComputePoint();?

@Thaina
Copy link

Thaina commented Jan 18, 2016

And where was I change meaning of legal code?

const int i = 1 will be the same as other legal code, nothing change

What I would like to propose is to add it functionality

const i = 1 just be the same as const int i = 1 what meaning of it was changed?

struct S { public int i; }
class A
{
    void DoSomething()
    {
        const S s0 = new S(); /// Make this constant struct which cannot be anymore changed in this scope
        const s1 = new S(); /// Make this the same as above, still the same meaning
    }
}

And as I said if you really care that const must never be accept any runtime changed, I could accept readonly but not let. But even it permit const to accept value from runtime in local function, it does not break any code we currently have

And I don't see the benefit to not permit const to use as readonly
If we consider const as legacy keyword from C++, then in C++ it can use const at function parameter, which could be change value isn't it?

Man, as I said. If we talk about semantic, you should not allow using this at parameter for extension method right?

@Thaina
Copy link

Thaina commented Jan 18, 2016

@HaloFour If you really think it up to C# team to decide then you would stop argueing with me now

This is political debate that you and I have our own agenda. I don't like let , and you like it so you try to debate with mine propose. Which I thinks it fine

But don't try to mislead me. They would decide with our voice and reason has some weight so we debate here. You should not make political lying to stop other people

@HaloFour
Copy link

@Thaina

So, having a completely new context for let for the purposes of destructuring is bad, but having const mean two different things depending on the expression is somehow good? const S s0 = new S(); is illegal for a reason, the expression must be a constant expression and the instantiation of a new type is very specifically not that. Having it sometimes be a local constant alias and sometimes be a readonly local slot is an awful terrible idea. The readonly keyword already provides those exact same semantics, so if anything it makes more sense to reuse that keyword.

And, again, const has NOTHING to do with destructuring. Beyond the simple case of let x = 1234; the use of const makes absolutely no sense whatsoever. const Person { GPA is var gpa } when gpa > 3.5 = person; makes no sense.

@alrz
Copy link
Contributor

alrz commented Jan 18, 2016

@Thaina Constants will be erased at compile-time but let rather introduce a variable. You might argue that this is an additive functionality, but they don't really share any semantics to be merged together.

@Thaina
Copy link

Thaina commented Jan 18, 2016

@HaloFour Because I think it not so difference. As I said, from C and C++ we were using const keyword at parameter to make it immutable. That is also a purpose of const

this is also has two meaning. A keyword reference to object itself and a keyword to make extension method. Why you don't go against it?

In the world right now how many const you would used to make it in compile time. How many you want it to be immutable? What people used cases?

Actually this decision should be automatically decide by compiler as a compiler optimization. If it could be compile time constant then compiler would just compile it as is. If it is able to changed at runtime then just compile it as immutable, done

And your argument about "expect const i = SomeMethod(); to be illegal" is really invalid
Because I would expect let a = x as compile error too because let should not be used without from

let a = x for me is make no sense too
from Persons let Person { GPA is var gpa } when gpa > 3.5 = person; like this is make sense
const a = x is make sense because we know a must be type of x. We can't make empty const a

@alrz
Copy link
Contributor

alrz commented Jan 18, 2016

because let should not be used without from

and if it does, who's responsible? I'm not going to jail for this.

@stepanbenes
Copy link

@Thaina I think the use of this keyword to mark the argument whose type is to be extended is justifiable and it is very clear to me. It is not bad to re-use the keyword in different context, it is bad to do it in very unrelated and surprising way that does not respect the meaning of english word. To support your argument, you should be instead pointing to more controversial examples of keyword reuse, such as fixed, into, void and maybe partial.

@HaloFour
Copy link

@Thaina

this is also has two meaning. A keyword reference to object itself and a keyword to make extension method. Why you don't go against it?

Because the two meanings are used in two completely separate and distinct contexts.

Actually this decision should be automatically decide by compiler as a compiler optimization
And your argument about "expect const i = SomeMethod(); to be illegal" is really invalid

If you say so, but semantics are important. Existing code and libraries depends on C# doing what the spec says. If the semantics were to change then the resultant IL could change unexpectedly which can result in breaks. And there's no reason to have const do two different things based on whether or not the type is inferred. And there's no reason to make const mean readonly when readonly exists. And there's no reason to make const have anything to do with destructuring in general, which is what this proposal is about.

Because I would expect let a = x as compile error too because let should not be used without from

Contextually different, not semantically different. Big difference between the two. I'd be perfectly fine with const being used as a modifier for functions or parameters as well since the context is different and could not be confused.

If you really think it up to C# team to decide then you would stop argueing with me now

Now there's a good point. You've made your case, everyone else has made theirs, why not just let it be?

@stepanbenes
Copy link

let it be

@sab39
Copy link

sab39 commented Jan 18, 2016

const it be!

;-)

@Thaina
Copy link

Thaina commented Jan 18, 2016

Existing code and libraries depends on C# doing what the spec says

Using let or const as this proposal changing spec anyway. It come to the point that what is more broken between extend keyword outside it scope or adding functionality of keyword in the same context

Which I go against the first and accept the latter while you think opposite. But we have our own reason of "breaking change"

As I said I could accept contextual difference from outside scope but I hate the lesser scope go outside. Because it would be use somewhere outside already. And if you really hate semantic change I would like you to consider readonly instead of let

But then again you never look at it

@Thaina
Copy link

Thaina commented Jan 18, 2016

@HaloFour And I said this

If you really think it up to C# team to decide then you would stop argueing with me now

To only YOU. Because you are the one who think it up to C# team to decide not ME

What I think is I should state my proposal and try to make argument to have some chance that my idea would affect their decision

You are the one who make that invalid statement even you yourself don't belief in. Because if you really belief that it up to C# team to decide then you would stop arguing. While I continue because I don't belief the same thing as YOU

If you really belief your word. Make example by your action

@gafter gafter added the Resolution-Fixed The bug has been fixed and/or the requested behavior has been implemented label Jan 28, 2016
@gafter
Copy link
Member Author

gafter commented Jan 28, 2016

This has been folded into the pattern matching spec.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
4 - In Review A fix for the issue is submitted for review. Area-Language Design Feature Request Resolution-Fixed The bug has been fixed and/or the requested behavior has been implemented
Projects
None yet
Development

No branches or pull requests