Implicit Variable Declarations #14697

Open
Kaelum opened this Issue on Oct 24 · 215 comments
@Kaelum
Kaelum commented on Oct 24 edited

The C# Design Language Notes for Jul 15, 2016 in #12939 are deeply concerning, and should be raising a red flag for everyone. Allowing a C based language to leak like this, throws out the more than 45 years of scoping rules that exist. It also allows variable declarations to be hidden, making them extremely difficult to find. I am saying this now, as people are just starting to realize the gravity of #12939. As an additional side effect #14651 was created, and there will very definitely be many more to come if this is allowed to remain as-is. All of this, just to allow a "nice to have feature"?

My proposal is to revert the scoping rules back to the way that they were prior to #12939, and change the implementation of the is int x and out int y "explicit variable declarations" to be "implicit variable declarations". Meaning, that if x exists, is in scope, and is an int, use it. If x exists, is in scope, and is not an int, a compile time error is thrown. If x exists, but is not in scope, or x does not exist, create x in the scope that it is implicitly introduced.

If anyone needs the "leaking" functionality of #12939, no changes need to be made. We have that ability in C#6 and earlier, where you just explicitly declare the variable in the scope that you need it. However, for those who would like to use "temporary" variables, which was the original vision of this, you will have many options. Consider the following:

For those who don't want the leaks:

int? age = (o is int i || (o is string s && int.TryParse(s, out int i)) ? i : null);

// i and s are not in scope here.

For those who like it leaky:

int i;
string s;

// The int in the out parameter is completely optional in this case.
int? age = (o is int i || (o is string s && int.TryParse(s, out int i)) ? i : null);

// i and s are in scope here, and no compile time errors.

In no case would I want i or s to forever be used. They are temporary variables, much like the variables used in Lamba expressions. Who wants i to be taken away from being used in a for statement. @MadsTorgersen also brought up the "bouncer pattern" and a few scenarios for its use. I will say flat out that I don't buy into all of those scenarios as being valid. If it smells bad, and looks bad, then it most likely is bad. Would anyone truly excpect the i below to remain in scope?

if (!(o is int i)) throw new ArgumentException("Not an int", nameof(o));

If I truly want i to persist, I would explicitly declare it:

int i;

if (!(o is int i)) throw new ArgumentException("Not an int", nameof(o));

Remember, it is our duty as developers to question anything, and everything, that someone is trying to force down our throats. It took well over 10 years to create the 1998 C++ specification, and I thank everyone who was involved for ensuring that absurd proposals like #12939 never saw the light of day.

Have an AWESOME day!

@alrz
alrz commented on Oct 24 edited

variable i is already declared in this scope

Binding to existing variables probably needs a different syntax (#13400).

Note that this is what we need to do with out parameters. you need to declare variables beforehand. So it doesn't add much value to out var.

@Kaelum
Kaelum commented 29 days ago

@alrz,

On the contrary, it adds significant value to out declarations where you do not want the value to persist. You have to look back at why this feature was even considered in the first place. out parameters can't be fields or properties. So, you are required to use a temporary variable to populate fields and properties. This is why the narrow scoping rules were created in the first place. It was never needed, nor designed, to be used in a persistent manor. Persisted functionality already exists by explicitly declaring the value in scope before use. The feature was designed to allow:

public MyObject MyMethod(object o)
{
    MyObject mo = new MyObject();

    if (o is int i || (o is string s && int.TryParse(s, out int i)))
    {
        mo.MyIntProperty = i;
    }
    else
    {
        // i is not in scope here, so you could nest additional if statements.
        mo.MyIntProperty = -1;
    }

    for (int i = 0; i < mo.MyIntProperty; i++)
    {
        // Code with a newly scoped i variable.
    }

    return mo;
}

With the rules as they are defined in #12939, the code above simply is not possible, as there would be variable scoping collisions.

@Kaelum Kaelum changed the title from Implicit Variable Declarations as opposed to #12939 to Implicit Variable Declarations 29 days ago
@vladd
vladd commented 28 days ago

I don't think that the implicit variable declarations is a good thing. In particular, it helps to hide typos. Consider:

int i;
if (!(first is int i)) return;

int j;
if (!(second is int i)) return; // here is a typo

Now, the value of i is silently overwritten, no warning is issued.
Having the variable declaration explicit would catch this problem.

@svick
Contributor
svick commented 28 days ago

out parameters can't be fields or properties.

They cannot be properties, because there is no way how setting a ref or out parameter would call the property setter. But they can be fields.

@Kaelum
Kaelum commented 28 days ago

@vladd

The difference is that I am not willing to pollute the scope for functionality that we already have. We already have explicit declarations across the board, which pollute the scope. When you need temporary variables, which covers approximately 99% of our usage of out variables, polluting the scope is a very significant problem.

@DerpMcDerp

I like eliminating the implicit leaks by using the old style way of using local variable declarations to control scope.

I don't like 1. the typo prone unification magic of this proposal 2. specifying the type of a variable more than once.

A better proposal would be more akin to something like:

int a;
foo(out a);

// or maybe a different keyword than "out"
if (3 is out a) { stuff; }
@Kaelum
Kaelum commented 27 days ago

@DerpMcDerp

In regards to:

1) I am not convinced that a "typo" scenario is a valid reasoning to reject any proposal.
2) This proposal is not about making a change to the existing is syntax, which is what your proposal is asking for. This proposal is for nothing more than to make "temporary use" variables, available again.

@vladd
vladd commented 27 days ago

@Kaelum Well, detecting typos is a primary reason why we need to declare variables at all. If we would consider guarding against typos not important, we could omit var altogether, and make all variable usages effectively "implicit declarations". Surely you don't want to propose this way, do you?

@Kaelum
Kaelum commented 27 days ago

@vladd

There is a difference between a "typo" that is invalid syntax, and a "typo" that is valid syntax. Your example is for the latter, which is "misuse" of a variable, and is why I am not convinced that it is a valid reason.

@AdamSpeight2008
Contributor

The variable being introduce should be to outer scope be default

if( !(o is int i) )
{
 /* i is in scope and definitely not assigned */
 throw new ArgumentException("Not an int", nameof(o));
}
/* i is in scope and definitely assigned. */

and explicitly require a code to specify a change to the inner. For example by prefixing ~ on the variable identifier.

if (o is int ~i)
{
  /* i is scope and definitely assigned */
}
/* i is not in scope. /*
if( !(o is int ~i) )
{
  /* i is scope finitely not assigned */
}
else
{
 /* I is scope and definitely assigned */
}
/* i is not in scope. /*

i prior exists

int i;
...
if( !(o is int i) )
{
 /* i is in scope and definitely maybe assigned */
 throw new ArgumentException("Not an int", nameof(o));
}
/* i is in scope and definitely assigned. */
int i;
...
if (o is int ~i)
{
  /* i is scope and definitely assigned */
 /*  also is and error as i is be reused for a variable declaration */
}
/* i is in scope. /*
int i;
...
if (o isnot int ~i)
{
  /* i is scope  not assigned a new value*/
 /* also is an error as i is being reused for a variable declaration */
}
else
{
 /* i is scope and definitely assigned a value.*/
 /* also is an error as i is being reused for a variable declaration */
}
/* i is in scope. /*

if the type of i is incompatible with the one in the pattern, it is an error in all cases.

@Kaelum
Kaelum commented 27 days ago

@AdamSpeight2008 if you want to propose a new feature, or change to existing functionality, you need to create a New Issue. Spamming 3+ different threads won't get your idea actioned, only creating an issue can do that.

@tamlin-mike
tamlin-mike commented 25 days ago edited

Actually, that spam made me think of something that possibly could solve this issue.

I'm totally against allowing scope escaping by default, but if someone really wanted it (for whatever reason), what if they could opt-in to that behavior?

I'm specifically thinking about an otherwise illegal token, where maybe ! could stand out?

Example:

if (o is int i) { /*' i' is live */ }
/* 'i' doesn't exist */
if (o is int !j) { /*' j' is live */ }
/* 'j' does exist */

If something like that could work, it would allow sane scoping rules for the vast majority of cases, while still allowing them who wanted to take aim at a foot and pull the trigger to do so.

@CyrusNajmabadi
Collaborator

I've looked into this bit, and i'm thinking it's not a good approach going forward:

and change the implementation of the is int x and out int y "explicit variable declarations" to be "implicit variable declarations". Meaning, that if x exists, is in scope, and is an int, use it.

From talking to >10 devs, (backgrounds in C/C++/Java/C#) every single dev found it unwelcome and surprising that such a declaration would implicitly use a declaration already in scope. They either expected a new variable that hid the existing variable, or a compiler error for the clash (even when the types matched).

Cheers!

@CyrusNajmabadi
Collaborator

If anyone needs the "leaking" functionality of #12939, no changes need to be made. We have that ability in C#6 and earlier, where you just explicitly declare the variable in the scope that you need it.

There is a suitable large number of requests that people be able to use the new "out var" feature to replace code that used to explicitly declare the variable before use. As such, the above point no longer holds. People do want the ability to use 'out var' with similar scoping to how they used to have to use 'out' before.

Talking to these people, they find it cleaner to do things this way, and find the scoping intuitive as it simply matches the scoping that they were used to. In other words, 'out var', to them, is just a simpler way to write the feature they have today. Having 'out var' have limited scope lessens the feature for them as it makes it too restrictive for the code cases they have today with 'out'. And, if the new feature doesn't fit nicely into the code they have today, and doesn't cleanly let them move that code forward, then they don't view the feature as an improvement for them.

As such, i think it's worthwhile that the feature be useful to the largest set of users possible. Wide-scoping provides that. It enables the feature for people who want to move existing code forward, while not restricting hte feature from those who would prefer narrow scoping. The only onus on the narrow-scoping usage is that fresh names be picked. In other words we had these options:

  1. Narrow scoping. Prevents usage from users who want wide scoping. Allows usage without name collisions for users who want narrow scoping.
  2. Wide scoping. Allows usage from users who want wide scoping and who want narrow scoping. But requires those who want narrow scoping to pick fresh names.

In the interest of making the feature widely applicable and useful to all users (not just those who prefer narrow scoping), we went with '1'.

Thanks!

@CyrusNajmabadi
Collaborator

For those who like it leaky:

Your solution is not pleasant to those who want wide scoping. There are people who want wide scoping without the need for extra statements. We think the solution provided so far can provide that without being unduely unpalatable for those who still prefer narrow scoping.

@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented 2 days ago edited

Remember, it is our duty as developers to question anything, and everything,

Please continue questioning anything you don't like and providing feedback as to your POV. It's really appreciated and helps a lot when we get together to design and make decisions on these sorts of things. By getting feedback and information from a wide variety of sources we can truly work toward solutions that we think will be hte best for the community overall. Note that even if we think something is best overall, that doesn't mean we think that everyone will like it :-) We're fully cognizant that there have been nearly zero changes we've made to the language that have been received positively by the entire community. Nearly every change is unpalatable to some (including me sometimes :D ).

that someone is trying to force down our throats.

Ultimately, we will make a decision here, and some developers will be unhappy. Were we to take the narrow scoping approach, then there would definitely be some upset developers. :) At some point we end up taking in all the feedback and deciding on what to do (which occasionally includes not doing anything). But, if we feel like we understand the space well enough, have received more than enough feedback to make a decision, and feel the overall feature will be a sufficient net positive, then we move forward on it.

For those who do not like the decision, i can see why you might describe it as something being "force[d] down our throats". However, please recognize that with pretty much any decision, some group is going to feel that way. Unless 100% consensus can be reached on new feature (which we've literally never been able to get given that there are groups that are adamantly against any new features at all), some group will always have the language go in a direction they do not want. While i'm sympathetic to being on the receiving end here (after all, i've routinely been there myself), it's not something that should prevent change if we think the overall benefit is worth it.

Thanks! And please keep the great feedback and ideas flowing :)

@Kaelum
Kaelum commented 2 days ago

@CyrusNajmabadi

I find all of your arguments faulty for one simple reason, narrow scoping becomes completely impossible with the changes defined in #12939 , and they violate 45 years of scoping rules that the C/C++ specifications have defined.

Is the issue presented here perfect? Maybe not, but it in no way prevents anyone from using narrowly scoped or widely scoped variable declarations. Narrow scoping of the is operator is of great importance to web developers. We get values from many different sources, usually boxed, and need to convert them to a specific type, which completely breaks with wide scoping.

By forcing the variable to be Explicitly defined for wider scoping (which is the C# 6 and earlier scoping), we are only talking about a single line of code, that visually defines the scope, and does not introduce any scope breaking changes. If you want to drop the Implicit nature for out declarations, that would probably be an acceptable change when using out, as that is how it exists in C# 6 and earlier. However, the is operator needs to Explicitly or Implicitly declare a variable, as it can't work any other way.

By forcing the is operator declaration to be Explicit, and leaky, you leave absolutely no options for those who want a narrow scope. There is absolutely no possible way to code a narrow scope under these circumstances, which is why all of your arguments are faulty. Even if you use a {if} syntax, it leaks to the else, which makes it useless.

I am going to wait for the other Enterprise Partners to chime in, as it appears that you guys just don't get how damaging this would be to the language. Breaking scope in the way that you are proposing is beyond ridiculous.

Sincerely,
Will

@CyrusNajmabadi
Collaborator

Narrow scoping of the is operator is of great importance to web developers. We get values from many different sources, usually boxed, and need to convert them to a specific type, which completely breaks with wide scoping.

Could you clarify this bit. What scenario 'completely breaks'? Thanks!

@CyrusNajmabadi
Collaborator

By forcing the is operator declaration to be Explicit, and leaky, you leave absolutely no options for those who want a narrow scope.

Yes. that is understood. If you want a narrow scope, you can't have that. But you can at least still use the feature and not take advantage of the wider scope if that's not of any interest to you.

The converse is not true. If we made the scope narrow, then there would be no option for those that want a wider scope. They would be locked out of the feature.

I'd rather have both camps able to use the feature, even if suboptimal for one camp. Cheers! :)

@CyrusNajmabadi
Collaborator

Breaking scope in the way that you are proposing is beyond ridiculous.

How is scope broken? Every existing C# 6 scope works exactly the same way as it used to. We're only adding a new feature and defining precisely what we want the scope of that to be. It's unclear to me how anything is 'broken' here :)

as it appears that you guys just don't get how damaging this would be to the language

Given how many people we've talked to who don't think anything is breaking here... it's not clear at all that there's any sort of consensus that what you're talking about it happening :)

--

Heck, we've made scope choices in C# in the past that were unfortunate (like how foreach scopes worked). We even were willing to change them (including accepting the breaking change fallout). At the end of the day, the sky was not falling. No 'partners' (enterprise or other) were really 'put out' by these decisions :) We're simply talking about a facet of a language feature. But it seems to have taken on life as something of life or death consequences :D

@Kaelum
Kaelum commented 2 days ago

Yes. that is understood. If you want a narrow scope, you can't have that. But you can at least still use the feature and not take advantage of the wider scope if that's not of any interest to you.

How? If we require narrow scoping, how can we use the feature? Are you saying that we need to pollute the variable scope with huge numbers of declarations?

This is my final comment:
out was improperly implemented, and now you are making changes that will forever destroy the language for that faulty operator. I'm sorry if you don't agree, or don't understand, but I can't agree with anything that you are saying.

Sincerely

@CyrusNajmabadi
Collaborator

How? If we require narrow scoping, how can we use the feature?

It's not clear to me how 'narrow scoping' is 'required'. :) I think that's the disconnect.

@HaloFour
HaloFour commented 2 days ago

I bet Microsoft assumed that they were making the "right decision" when they violated these simple tenets previously with C, only to have to follow up with breaking changes and yet another dialect. I still know organizations that cannot migrate off of Visual C++ 6.0 because of those bad decisions.

The syntax and grammar that C# chose to adopt comes with an assumption of behavior, including scope. Sure, C# has deviated in a variety of areas, particularly where it beat C++ to the punch (if var were a new proposal today I would be arguing that auto be the keyword). This is of the biggest deviations and directly contrary to the existing behaviors that have previously been hammered out through standardization. These arguments have all already happened.

Apart from the insanely late timeframe of these changes (seriously, you're talking RC2 here, there should be nothing even remotely up for discussion), what's worse is that this decision pollutes the entire future of pattern matching in the language. All because a few lazy developers don't feel like predeclaring their variables?

@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented 2 days ago edited

All because a few lazy developers don't feel like predeclaring their variables?

Please be respectful. No one is disparaging you for the style of code you like. :)

We have many developers who use are product and who evaluate things on a wide variety of axes. One particular developer (or group)'s usage of the language is likely to not at all be representative of the whole. Our goal is to evaluate our features against the broad spectrum of our users and come up with balanced approaches that we think will maximize the overall benefit to the larger ecosystem.

@CyrusNajmabadi
Collaborator

Apart from the insanely late timeframe of these changes (seriously, you're talking RC2 here, there should be nothing even remotely up for discussion)

We do not agree. We set our on schedules and we feel there is more than enough runway to both continue working and implementing language features. I'm sorry you feel otherwise. However, the control of the C# schedule is in the hands of the LDM and the compiler team. Both of these groups feel like there is enough time for the work we are doing. Both for getting implementation quality high enough, and for vetting if the language choice meets the goals we're setting out.

@CyrusNajmabadi
Collaborator

I bet Microsoft assumed that they were making the "right decision" when they violated these simple tenets previously with C

I don't know what "simple tenets" you're referring to :). The LDM has a ton of experienced minds who are deeply familiar with dozens of languages. This choice doesn't seem to be particularly contentious or out of line with anything we've seen elsewhere.

@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented 2 days ago edited

This is of the biggest deviations and directly contrary to the existing behaviors that have previously been hammered out through standardization.

What are you referring to specifically here? Thanks!

@HaloFour
HaloFour commented 2 days ago edited

@CyrusNajmabadi

Please be respectful. No one is disparaging you for the style of code you like. :)

They do all the time, and that's fine. Where that stops being fine (for me) is where it becomes legislated as part of the language. I call into question the motive behind these decisions.

This choice doesn't seem to be particularly contentious or out of line with anything we've seen elsewhere.

What are you referring to specifically here? Thanks!

It's completely in opposition to the standardized behavior of C++. I would hope that the LDM has at least a passing knowledge of that language.

@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented 2 days ago edited

They do all the time,

Two wrong and all that :)

and that's fine.

No, it's really not. We're adults here and it's paramount that we all be able to treat each other, and the greater C# community with respect. We don't disparage other developers because they use code patterns we find personally don't like. It's inappropriate for this sort of discussion. Thanks!

It's completely in opposition to the standardized behavior of C++. I would hope that the LDM has at least a passing knowledge of that language.

Could you be specific? Which specific behavior of C++? Can you point me to the relevant section you're referring to? I'm happy to take a look. But i don't have the time to simply slog through the entire spec trying to guess at what you might be referring to.

Thanks!

Where that stops being fine is where it becomes legislated as part of the language. I call into question the motive behind these decisions.

Uh... :) Not sure what to say about that. AFAICT, the motives are pretty simple: Change the language in ways that we think will be an overall benefit to the great C# and .net ecosystem.

@HaloFour
HaloFour commented 2 days ago

@CyrusNajmabadi

Could you be specific? Which specific behavior of C++? Can you point me to the relevant section you're referring to?

I don't know the exact part of the spec, but my understanding is that any C++ loop/condition construct that permits for the declaration of variables within the condition that a new scope is established within that condition. For example:

while (int i = get_data()) {
}
// i is out of scope here

if (int i = get_data()) {
    // i is in scope here
}
else {
    // i is also in scope here
}
// i is out of scope here
@CyrusNajmabadi
Collaborator

Ah, you're referring to declarations of variables, not in an argument context. That doesn't seem to be the same feature that we're talking about for out-variables. Note: for C# statement constructs that do allow local variables to be declared, we're keeping the same scoping for out-vars.

To keep things simple, we're keeping Pattern variable scoping the same as out-var scoping. We got lots of feedback that people did not what these to be different.

@HaloFour
HaloFour commented 2 days ago

@CyrusNajmabadi

Ah, you're referring to declarations of variables, not in an argument context. That doesn't seem to be the same feature that we're talking about for out-variables.

out var is the declaration of a variable. The fact that it's then used as an argument to a function isn't relevant, only the location of the declaration is.

The fact that while and if couldn't introduce new variables before didn't imply the nature of the scope to which said variables might be contained. That was directed by the syntax inherited from C++, but constrained by C#, not dissimilarly to how C# also constrained local shadowing or implicit case fall-through.

And speaking of switch, that is the example that torpedoes the entire argument about retaining existing scope behavior. Obviously a completely different discussion and decision happened there.

I would agree that out var and pattern variable scope should be consistent with each other. But pattern matching is a massive and significant shift to the paradigm of the language whereas out var is barely a "nice-to-have", less novel or helpful than most of the lower-hanging fruit proposed here. I'd rather out var never be implemented than to drag pattern matching along with it.

@CyrusNajmabadi
Collaborator

And speaking of switch, that is the example that torpedoes the entire argument about retaining existing scope behavior. Obviously a completely different discussion and decision happened there.

Switches couldn't have expressions in case labels that used 'out' :)

@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented 2 days ago edited

The fact that while and if couldn't introduce new variables before didn't imply the nature of the scope to which said variables might be contained.

Note: while and if statements could never introduce variables, and that behaviors remains today. Rather, poatterns and out-vars introduce variables. So nothing has changed here.

for and foreach statements can introduce variables. And we use the well defined scopes those constructs already have.

Perhaps that's the disconnect here? You see this as statements introducing variables, and deviating from where you'd like teh statement's variables to be placed. Otherwise see it as expressions declaring variables, and placing them in the current understood C# scopes for those things.

I hope that helps clear things up!

@CyrusNajmabadi
Collaborator

I'd rather out var never be implemented than to drag pattern matching along with it.

Your feedback is noted. But it certainly not felt universally :)

Indeed, we've heard about out and how people want it to be improved than patterns. Patterns themselves seem to be much more niche. Whereas 'out' affects pretty much all existing customers and how they use the existing .Net fx. As such, we def want a solution that benefits all the people who have been wanting this for so long.

Thanks for the input! It's been very helpful :)

@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented 2 days ago edited

out var is the declaration of a variable. The fact that it's then used as an argument to a function isn't relevant, only the location of the declaration is.

Yes. And we scope the vairable into the scope that the declaration is in :) We're not generating any new scopes**. So the scopes of C# remain and out-vars fit into them like current variable decls do.

--

** Technically this is not true. Clearly case labels have been granted a scope. But we felt that was ok because there could be no conflict with existing code. in other words, we could act as if there was always a scope there in the past. Also, because we want to convey the idea that the presence or absence of blocks shoudl have no effect on certain statements, we made it so that embedded statements acted as if they had the same scope as if you had a block there. This way removign/adding the block would not change semantics. :)

in both cases, you could not observe the difference. So we're treating it as if we went back and stated that C#6 and earlier had these scopes. It would not effect any existing programs, but it enables sets of programs we feel are valuable for C# 7 and onwards.

We felt, overall, that these were pragmatic choices that did not prevent anyone from using out-var if they wanted narrow scopes. But ensured that out-var would work well for those who wanted wide scopes.

I hope this helps clear things up! :)

@HaloFour
HaloFour commented 2 days ago

@CyrusNajmabadi

Perhaps that's the disconnect here?

The disconnect is that in the C++ family of languages the statement provides the child scope in which those expressions are contained. Variables introduced within the statements don't "leak" out. The LDM is making this decision that the statement has no scope of its own only based on the fact that C# didn't permit it before.

Indeed, we've heard about out and how people want it to be improved than patterns. Patterns themselves seem to be much more niche. Whereas 'out' affects pretty much all existing customers and how they use the existing .Net fx. As such, we def want a solution that benefits all the people who have been wanting this for so long.

Big shock, ask some developers of C# about features that don't exist in C# and you don't get a lot of excitement or understanding about it? Those same people questioned async and would have traded it in for a way to shave off three keystrokes elsewhere. Patterns are niche in C# because C# doesn't have patterns. And if you're basing it off of the excitement of "pattern matching" in C# 7.0 that might have to do with how significantly neutered it is compared to the larger proposals which I seriously hope are still slated for a future version of the language.

Clearly case labels have been granted a scope. But we felt that was ok because there could be no conflict with existing code.

Switches couldn't have expressions in case labels that used 'out' :)

Still can't, yet. But that still doesn't explain the logic as to why a pattern variable in a case label has a different (and more constrained) scope than a variable declared directly under that case label.

Can you name another language that has scoping rules anything like this? Where a child scope can be defined for a single clause yet variables declared within leak into part of the body of the parent scope?

I hope this helps clear things up! :)

Do you have any idea how patronizing this sounds? I get that you're trying to be friendly, but it really doesn't come off as such. Text is really an awful medium.

@CyrusNajmabadi
Collaborator

Big shock, ask some developers of C# about features that don't exist in C# and you don't get a lot of excitement or understanding about it?

I didn't claim it was shocking. I was just explaining that it's more niche and people care deeply about the bread and butter features they work with day in and day out. This was in response to: "I'd rather out var never be implemented than to drag pattern matching along with it."

We considered this but felt the overall sentiment indicated that others would rather us do this. I wasn't stating this as a surprise, just as way to help clarify that your individual position should not be considered representative :)

--

In general, that's a very difficult thing that people have to deal when discussing languages. Individual biases affect people a lot. And it's very common (heck, the norm), to see people focused deeply on the stuff they care about, and dismissive of areas they care less about. However, for any individual customer, the set of things that are cared about will be very different. It's helpful to keep that in mind when trying to evaluate this stuff. Thanks!

@CyrusNajmabadi
Collaborator

Do you have any idea how patronizing this sounds? I get that you're trying to be friendly, but it really doesn't come off as such.

I'm very sorry about that! I received feedback from other people on github that they didn't like when i didn't do this. It made them feel that their feedback was not appreciated or that they weren't being listened to. I've been trying to separate out the different concerns when talking to people. Letting them know that just because there are disagreements doesn't mean that their feedback isn't being closely listened to and weighed into the continual evaluations we have as we discuss things week in and out :)

@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented 2 days ago edited

But that still doesn't explain the logic as to why a pattern variable in a case label has a different (and more constrained) scope than a variable declared directly under that case label.

I personally agree with you here. I really dislike switch scoping in general. A big issue is that if we reuse switch (which i was on the fence about to begin with***) we can't change the scoping under the case label. 'cases' were interesting because we had three options we coudl go with:

  1. Have the case scoping be the same as the scope under the case.
  2. Have the cases have their own unique scopes.
  3. Have hte cases be scoped into the scope that the switch was in.

Each of these had pros/cons. '1' was consistent with case bodies, but very restrictive. '3' allowed us to not introduce a new scope in the compiler (but was neutral in terms of how we wanted to backport scopes to earlier versions of the language). '2' had the benefit of at least enabling the ability to reuse names multiple times (which seemed at least nice to have for some customers) while not restricting anything.

In effect, making this scope wider than '1' enabled a scenario that people seemed to like, without restricting any real world cases that people could bring up.

In effect, it was a pragmatic decision** :)

--

** I want to emphasize this bit as i think it sometimes gets lost with the hundreds of messages going back and forth. When i started on C# i was a very 'ivory tower' developer :) I disagreed with so many decisions being made in C# because i felt tehy were impure, or inconsistent. However, over 10+ years of working on this language i've grown a deep appreciation for the pragmatic approach the LDM has taken on many decisions. While not perfect, they tend to value facets of the language that people then really appreciate moving forward. I started to see that my own perspective was optimizing to optimize toward levels of consistency that would end up making the language less pleasant to use in practice.

With 'switches' this is what we've attempted to do here. An approach that enables certain patterns that we think is valuable, while not really restricting any code we think will arise much in practice. In effect, this approach drives a lot of decisions we make. We're always able to find esoteria where our language decisions fall over. However, it's often that those cases are ones that will be incredibly rare in practice (including being something that will never be seen). In those cases, i'm ok with optimizing the language for the cases we strongly believe will be more widepsread over those that will be very rare, or will never appear.

Does that help clear things up? Thanks!

--

*** I was a strong proponent for not reusing switch and just having a new match statement and match expression. But, it was felt to be better to reuse 'switch'. We went into that knowing htat it would lead to some really wonky stuff due to the heritage of switches that we already opted into when we adopted switches in c# 1.0.

@CyrusNajmabadi
Collaborator

Still can't, yet.

Yes. But they can have pattern variables (and we discussed previously the desire for pattern variable and out-var scoping to be the same). We felt there were valuable code patterns enabled by this, and no reasonable code patterns hurt by it.

So switches are wonky... true. But we inherited much of that wonkyness in C# 1, and we can't change it. This was an approach that accepted that wonkyness, but enabled a code pattern we thought was valuable enough.

@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented 2 days ago edited

@Kaelum I wanted to address this point:

When you need temporary variables, which covers approximately 99% of our usage of out variables,

I was curious about what the actual percentage would be. I started by looking at the Roslyn codebase (which has already been adopting C# 7 features). I analyzed all the current usages of 'out var' to see how they were used.

There are currently 1969 usages of out-var in the Roslyn codebase. I used Roslyn to analyze every local generated by these out-vars. I bucketed each out-var into one of two cases:

  1. the local declared is only used within the containing statement.
  2. the local declared is used outside of the containing statement.

The counts were:

Used-only-inside: 821 cases
Used-outside: 1148 cases

This was interesting to me as the number of cases used outside was greater than the number used inside. Nearly 60% of all cases are ones where we use the variable outside of the containing statement! My personal guess that it was only going to be 20%. Being 3x that was not what i expected :)

So, i'm going to push-back against the claim that narrow-scoping "covers approximately 99% of our usage of out variable". From our very own code, narrow scoping would only cover 40% of all cases. Designing out-var such that it wouldn't work for 60% of the cases we currently use it for would be a pretty bitter pill :-/

@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented 2 days ago edited

I found another ~1400 cases that weren't being checked. For a total of 3389 cases. crazily enough, the end numbers are:

Used-only-inside: 1357 cases
Used-outside: 2032 cases

Which continues the pattern of 40% 'used only inside the statement' and 60% 'used outside the statement'. For our own development practices, it seems like we consistently use 'out' in wide-scoping cases 60% of the time. Fascinating!

@DavidArno
DavidArno commented 2 days ago edited

@CyrusNajmabadi,

I really don't understand what you are doing here. The decision is made to change the C# language scoping rules to make the out var feature slightly easier to use. Despite a huge range of opinions here, we're almost unanimous in our condemnation of this decision. Almost the only voices (outside of the language team) who like this feature speak to you in private about it, and so we have to take your word that these people really exist. But the decision is made. We all hate it, but we accept it's going to happen. It's a done deal. It's a bad deal; but it's a done deal So why have you appeared here after the thread had been dormant for a month and provoked us all into an argument with you again?

What are you trying to achieve by goading us?

@AdamSpeight2008
Contributor
AdamSpeight2008 commented 2 days ago edited

That's why I proposed earlier, that it should by-default enclosing scope (nearest outer scope aka widening) rather then enclosed scope (nearest inner scope aka narrowing). It simplifies the more common usage case. For the other case it should require explicit declaration (that the scoping is enclosed).
For example

 M( out var x )  /* Enclosing scope */`

vs

 M( out let x ) /* Enclosed scope */

If in either case the variable is already in scope, it should be a compile-time error. Thus prevents variable shadowing and accidental mal-usage. As existing constructs / code patterns let you use the existing variable.

examples

Enclosing Scope

int x = 0;
switch ( obj )
{
  case obj is int x:
    /* use exist variable */
  case obj is double x:
    /* (error) */
}
switch ( obj )
{
  case obj is int out x:
    /* declare x into enclosing scope */
  case obj is double out x:
    /* (error) redeclaration of x is not allowed */
}

Enclosed Scope

switch ( obj )
{
  case obj is int let x:
    /* declare x into enclosed scope */
  case obj is double out x:
    /* declare x into enclosed scope  */
}
int x = 0;
switch ( obj )
{
  case obj is int let x:
    /* (error) redeclaration of x is not allow */
  case obj is double out x:
    /* (error) redeclaration of x is not allow */
}

Additional
out var x and out let x also work for introducing variables in expressions.

@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented 2 days ago edited

the thread had been dormant for a month

This thread was linked to from #12939 (which still is under active discussion). I was collecting information and positions to see if anything was being missed. I realized there were a few points made here that hadn't been covered, so i thought it might be useful to discuss them :) There had been no language designer input here, and i thought there was enough interest in the topic to warrant some information on at least how one of the LDM partcipants was thinking about things :-)

I really don't understand what you are doing here.

Discussing the changes with community members :)

What are you trying to achieve by goading us?

I'm trying to do no such thing. I came to discuss the issue because i find language design fascinating and i absolutely love conversing about it with anyone interested in the topic. It's why i work on the language and one of the things i love about this forum. I thought the data and information i could provide would be helpful.

And, frankly, it has been to me. The conversation with HaloFour helped me better understand one of the objections to the feature. The more communication, the better understanding, and the better informed we can be when making future changes :)

Now, if you don't want more discussion in this thread, that's fine. But if that's the case, we shoudl probably close this. I thought there was value in discussing this topic so i felt like it was worth participating :)

@AdamSpeight2008
Contributor

@DavidArno
I think the issue is that the community think the change is wrong, as the "actual" scoping is subtle and change depending on context. Which could easily be missing when reading the source code.
The community would like to be give to option to decide what scope it should in there own code.

@CyrusNajmabadi
Collaborator

@AdamSpeight2008 I would not be opposed to a future language feature that limited scope to, say, the containing statement.

That might provide a best-of-all-worlds for people. People who desire wide-scoping can get it, but those who prefer narrow scoping could opt-into that as well.

@AdamSpeight2008
Contributor
AdamSpeight2008 commented 2 days ago edited

@CyrusNajmabadi I'm thinking of the "average" - professional C# programmer would expect to the code to do.
It removes the potential ambiguity (even if that ambiguity "technically" doesn't exist).

@CyrusNajmabadi
Collaborator

hrmm... good question. My gut tells me they would expect to be able to use the variable. That restricting scoping of a local would be 'nice to have', but not really something they would care first and formost about.

@DavidArno
DavidArno commented 2 days ago

@AdamSpeight2008,

I completely agree that, even if one accepts there is merit to out var, then accepts there is merit to leaking scoping, the fact that these variable declarations leak in different ways depending on the language construct will likely cause great confusion. But, again, the decision is made. Yes it's the wrong decision, but we have to lump it.

@AdamSpeight2008
Contributor

There will be only a couple of exception, which they should be aware of that of for and foreach, which can't be easier changed without breaking existing code.

It also easier (for me at least) to understand in direction (aka scope) the variable is going to available from

Enclosing (Widening)  <<-- out var
                           let var -->> Enclosed (Narrow)
@Kaelum
Kaelum commented 2 days ago

@CyrusNajmabadi I will send the private email that you requestted, this week. However, it is very clear that you know nothing about C++ scoping. I will cover that in my email. Good day...

@AdamSpeight2008
Contributor

@DavidArno Personally I'd change it now, so it doesn't be come another billion dollar mistake.

@CyrusNajmabadi
Collaborator

There will be only a couple of exception, which they should be aware of that of for and foreach, which can't be easier changed without breaking existing code.

I don't even see it much as 'exceptions'. for/foreach already clearly introduce variables today. So i think it will be intuitive to users that out-vars for those constructs go in hte same scope as the existing iteration variables. Things like 'if/while' don't introduce variables, so the variables declared there go into the same scope that they would have gone into originally.

@CyrusNajmabadi
Collaborator
Enclosing (Widening)  <<-- out var
                           let var -->> Enclosed (Narrow)

This approach would also have some slight symmetry with javascript, as they used 'let' to denote a block scoped variable.

@AdamSpeight2008
Contributor

@CyrusNajmabadi
Is there an example, which show that use-case. As I can't think of one.

@CyrusNajmabadi
Collaborator

@Kaelum

I will send the private email that you requestted, this week.

Thanks!

@CyrusNajmabadi
Collaborator

Is there an example, which show that use-case. As I can't think of one.

Sorry, which use case are you referring to? Thanks!

@AdamSpeight2008
Contributor
AdamSpeight2008 commented 2 days ago edited

@CyrusNajmabadi Sorry, let me clarify use of out in for / foreach

@CyrusNajmabadi
Collaborator

the fact that these variable declarations leak in different ways depending on the language construct will likely cause great confusion.

I can see the concern there. Though, to me, it's an issue we've already had to deal with (and it was no biggy). For example, the scope of a variable in a for-statement is dissimilar (at a cursory glance) to the scoping in a foreach-statement. And it can bite people (i'm sure there was at least one Roslyn bug report about this :D). But, overall, it's not really a big deal and only amounts to a tiny bit of confusion in the rare case when people are affected by it.

@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented 2 days ago edited

@AdamSpeight2008

foreach (var v in GetValuesAndError(out var error)) {
   if (!error) {
       DoSomething
   }
   else {
      DoSomethingExceptional
   }
}

// neither 'v' nor 'error' are in scope here

Personally, i think it woudl be pretty rare to end up with out-vars in foreach locations. But, if you do use it, having the out-var be scoped to the same scope as the foreach-variable seems at least somewhat explainable :)

@mikedn
mikedn commented 2 days ago

I have stopped long ago commenting on Roslyn issues, partly due to the noisy nature of the community and partly because myself I'm not a language designer even though I have a certain amount of experience with compilers. I'll make an exception this time though.

When this feature was originally proposed I thought that it's a nice feature. Mainly because it allows you to write something like if (tryx(out int x)) { use x; } without polluting the surrounding scope with an unnecessary variable and without introducing the risk of using x after the if. It was a new feature and it provided something that otherwise wasn't possible (except by creating an extra scope by adding { } around the if - too ugly to consider).

Now, I tend to favor short/terse code so I certainly would have like to also be able to write if (!tryx(out int x)) { return/throw; } use x;. Especially that I also favor early returns. But if the cost of this is widening the scope to the point that the above mentioned advantage is lost then no thanks, I do not need this feature. I can very well declare x as I did before and I'll probably do so even if this feature is made available in its current form. In it current form this isn't a new feature, it's just rather useless syntactic sugar. And it's the worst kind of syntactic sugar, the kind that makes the code less clear and can result in bugs. And I still remember VC++ for scoping bug, bad stuff sometimes tends to stick around for a long time.

What's even worse about this new "feature" is that it is backed by statements like:

There is a suitable large number of requests that people be able to use the new "out var" feature to replace code that used to explicitly declare the variable before use. As such, the above point no longer holds.

That's like saying "I talked to a bunch of kids and most of them said that they like sugary drinks. So let's give'em sugary drinks.". Because of course, it is easier to cave in to the requests of the majority and just give them what they want, not what they actually need.

The amount of energy that already got spent on this is amazing. There were zillions of requests for features that would have would have saved a line of code, a couple of characters or whatnot and they naturally all got more or less ignored. But this one sticks for unknown reasons. Personally I would have thrown this out of the upcoming language version and waited a bit more for things to settle.

Just my 2 cents...

@AdamSpeight2008
Contributor
AdamSpeight2008 commented 2 days ago edited

@CyrusNajmabadi That kinda make sense.
In the current proposal it would be narrowing. with mine it be widening. So would need to be let var

foreach (var v in GetValuesAndError(let var error))
{
   if (!error) {
       DoSomething
   }
   else {
      DoSomethingExceptional
   }
}

// neither 'v' nor 'error' are in scope here
@AdamSpeight2008
Contributor

@mikedn The narrowering declaration is the minor usage case as the research so far suggests.

@CyrusNajmabadi
Collaborator

@AdamSpeight2008 Agreed. Though, tbh, i think these cases are really that interesting. In practice people are pretty darn unlikely to use an out-var in loop construct. The vast majority of cases are going to be either:

  1. Expression statements
  2. If-conditions

Indeed, looking at roslyn itself, this was 100% of all cases (ignoring a small handful where we did have an out-var in a loop, but the value was never used, and will be a _ discard once that language feature is done).

As such, it's important to figure out what should happen in those two cases as they account for nearly all expected usages. Narrow scoping has some undeniable benefits. But it also comes with some pretty unfortunate drawbacks. It also hampers the feature for those who have wanted an improved 'out' experience since C# 1.0.

The wider scoping def has some downsides, but it improves feature availability and usefulness for more users.

I imagine we'll continue paying attention to this in C#.next timeframe. If there is enough community interest in narrower scoping, then that's something we could totally add in the future. Similar to how properties were very blunt instruments in C#1.0 but have been continually refined over many versions to provide clean and lightweight solutions to many common cases people run into.

@CyrusNajmabadi
Collaborator

The narrowering declaration is the minor usage case as the research so far suggests.

Well, they're close enough (to me) to indicate that both uses are totally valid and common. While narrowing appears to be the minority case, it's not at all the vast minority. And it's close enough to 50/50 for me to just want to make sure that both cases are strongly considered as we move forward.

@CyrusNajmabadi
Collaborator

But this one sticks for unknown reasons

Because we were doing patterns and wanted to ensure that the work done now with patterns would not cause problems with other features that also interact with scopes later on down the line.

@AdamSpeight2008
Contributor

@CyrusNajmabadi
Let's increase the sample size, could you do that research say over the .net framework or repos in the dotnet "family" on github (https://github.com/dotnet)

@CyrusNajmabadi
Collaborator

@AdamSpeight2008 I intend to. Hopefully soon after Thanksgiving. I have a ton of stuff on my plate for RC2. I just really wanted to get some initial data to help out here as certain claims were made and i honestly didn't know how well they stacked up to reality (or, at least, the reality of the Roslyn codebase that we vet lots of features against).

@AdamSpeight2008
Contributor
AdamSpeight2008 commented 2 days ago edited

@CyrusNajmabadi Would it be possible for you to post the code used, and I'll do the run.
Plus I'm kinda interested in examples that use Roslyn.

@CyrusNajmabadi
Collaborator

That's like saying "I talked to a bunch of kids and most of them said that they like sugary drinks. So let's give'em sugary drinks.". Because of course, it is easier to cave in to the requests of the majority and just give them what they want, not what they actually need.

Please, let's be respectful. We have an enormous customer base with hugely varied needs and desires. C# is not a language that strives for any sort of ideological purity. It is a language that, first and foremost, wants to help people solve real problems they're facing on a day to day basis. We do not want to dismiss any group of users just because another group looks down on them as if they were "a bunch of kids". Instead, we want to see the common threads and needs tying so many of these developers together and we want to try to come up with solutions that we think can bring the most value to our entire ecosystem :)

@AdamSpeight2008
Contributor
AdamSpeight2008 commented 2 days ago edited

@mikedn It's also about controling where the leaky scope goes. 😉

@CyrusNajmabadi
Collaborator

@AdamSpeight2008 Sure! I'll need to clean it up though. Right now it uses lots of Roslyn internals (like IncrementalAnalyzers). That's usually how i do analysis as i can just add the code right into Roslyn itself, f5, and then analyze some project.

One difficulty is that in order to extract this out into a workable standalone app, you'll also need to be referencing the latest roslyn bits (as that's where things like out-var and hte new scoping logic is defined). So it can be a bit of a PITA to do it the regular .nuget way.

--

That said, the algorithm is fairly simple. You can see https://github.com/dotnet/roslyn/blob/master/src/Features/CSharp/Portable/InlineDeclaration/CSharpInlineDeclarationDiagnosticAnalyzer.cs for how we do a bit of this analysis.

  1. Look for 'out' arguments.
  2. Ensure they're just of the form 'out x'.
  3. Ensure 'x' is a local.
  4. Ensure that if x is initialized that the initialization is side-effect free.
  5. if so, get the statement containing the out-var. Then run SemanticModel.GetDataFlow(statement) on that statement.
  6. See if the variable is read/written outside the statement.

If the variable is read/written outside the statement, then you need wide-scoping in order to use out-var. If the variable isn't read/written outside the statement, then narrow-scoping would suffice.

@AdamSpeight2008
Contributor

@CyrusNajmabadi Cool, I'll have a look at attempt that later. As I may need a pot of coffee on standby.

@CyrusNajmabadi
Collaborator

Note: i'm not sure if the above will work perfectly (as the compiler may view the trivial initialization of the variable as a "write" to it. As such, you may need to actually scope the data-flow analysis to just the statements following the local-declaration, all the way to the last statement of the block you're contained in.

@mikedn
mikedn commented 2 days ago

Please, let's be respectful

There's not an ounce of disrespect in my comment. I just picked a random analogy and you have chosen to give the use of "kids" more importance than it has in the context. Please don't use misplaced political correctness to dismiss criticism and support language features.

It is a language that, first and foremost, wants to help people solve real problems they're facing on a day to day basis.

This particular feature doesn't solve a real problem. It's just a minor annoyance that it tries way too hard to get us rid of.

@CyrusNajmabadi
Collaborator

It's disrespectful because it presumes that these developers don't know what they actually need. Let's not slip into the state of thinking that just because people have different priorities than others that they're any less worthy of having the language improve for their needs :)

@CyrusNajmabadi
Collaborator

This particular feature doesn't solve a real problem. It's just a minor annoyance

One person's 'minor annoyance' is another's 'real problem'.

Take, for example, Tuples. To some there is no benefit to this feature. They could always just define their own 'ValueTuple' type and use that. Or just use an nominal type. Such work only counted as a 'minor annoyance' and Tuples do not solve a 'real problem' for them. But that's not everyone. There are others who are routinely working with and creating all sorts of disparate tiny pieces of aggregated data that they need to work with. The existing language was a consistent point of friction for them. Sure, they could deal with it (and have had to for 6 versions of the language), but it was still a PITA. Was this not a 'real problem' for them just because this issue was a minor annoyance for others?

@DavidArno
DavidArno commented 2 days ago

@mikedn,

Absolutely spot on. The whole out var feature should have been thrown away when it was realised that it wouldn't be very useful. I think your sugary drinks and children analogy is a perfect summary of how ridiculous this situation is.

@DavidArno
DavidArno commented 2 days ago edited

@AdamSpeight2008 ,

@DavidArno Personally I'd change it now, so it doesn't be come another billion dollar mistake.

Sure, I'd change it too. In fact I'd pull the whole out var feature and would seek to educate the "kids demanding sweets" that a piece of fruit would be a better choice (tuples at the moment, hopefully followed by maybe/option types in v8). But, assuming @CyrusNajmabadi's views are representative of the language team, what we'd do is irrelevant. They've made their decision and it ain't changing, no matter the cost of that mistake.

@CyrusNajmabadi
Collaborator

Well, it remains to be seen if it's actually a mistake :D

@AdamSpeight2008
Contributor

@DavidArno There already is a maybe/option type. Just that it is within the Roslyn codebase and not System

@mikedn
mikedn commented 2 days ago

It's disrespectful because it presumes that these developers don't know what they actually need.

If you put it that way then I'll say that yes, some developers don't know what they actually need and no, stating that is not disrespectful. It's just how things are and language designers have the responsibility of talking this into account when processing feedback. And most of the time they do, otherwise the language would have transformed in a mess a long time ago.

One person's 'minor annoyance' is another's 'real problem'. Take, for example, Tuples. To some there is no benefit to this feature.

I don't see the connection with tuples, they don't have any downsides that I'm aware of. Sure, some may abuse tuples and use them in places where perhaps they shouldn't be used (e.g. in library APIs). But

  • That already happens with many other language features.
  • When useful, tuples tend to save a lot more work. Creating a new type has significant overhead, a lot of lines of code and possibly a separate file as well.

There's a lot that can be said here about tuples vs. out var but I think this can be summed up easily: tuples can start with -100 points and have a good chance to work their way to the top, out X x can only do that if it counts the same point multiple times. And ironically, if we had tuples then maybe out would have been used less and then it would have been even more difficult for out X x to get itself out of the hole.

And speaking of points, it's good that you're trying to bring some numbers to the table but numbers themselves are shallow. You're telling us that there 2000 uses cases for this feature in the Roslyn code base but what does that exactly mean? That you'll have 2000 less lines of code if you use the feature? So what? Is the code more readable? More efficient? More correct? More anything?

Well, it remains to be seen if it's actually a mistake :D

Please be respectful and drop the use of :). While I consider the occasional use of :) to be good for the overall atmosphere of a discussion your repeated use starts to look like you're not serious.

@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented 2 days ago edited

If you put it that way then I'll say that yes, some developers don't know what they actually need and no, stating that is not disrespectful.

Let's not slip into the state of thinking that just because people have different priorities than others that they're any less worthy of having the language improve for their needs.

I don't see the connection with tuples,

The point was that depending on who you talk to you'll get different perceptions of the overall value of the feature. To some it's just syntactic sugar (i mean... it's pretty close to that at the end of the day), and doesn't solve a problem they have. The areas where it is useful are just a 'minor annoyance' to them.

Note: this is not hypothetical. I can't recall a version release where we did not have very similar feedback about some feature we were doing. And i've gotten this feedback myself from customers about tuples, and how they don't see enough value in them for their own use cases.

And speaking of points, it's good that you're trying to bring some numbers to the table but numbers themselves are shallow.

The numbers were just intended to address the claim made about what the primary usage of temp vars with outs. I brought it because i thought it would be helpful to show that one person's usage of our language may not be reflective of how others use it.

For @Kaelum he may be spot on in how temp vars are used with 'out' with the code he works with. However, such experience may not be reflective of the larger community. I would never claim that Roslyn itself is at all representative of how people code. But it at least serves as a large counter example that demonstrates that people do use 'out' in this manner.

You're telling us that there 2000 uses cases for this feature in the Roslyn code base but what does that exactly mean? That you'll have 2000 less lines of code if you use the feature? So what? Is the code more readable? More efficient? More correct? More anything?

Of all of those, i'm thinking that 'more readable' is probably the most likely. I mean, the same thing could be said about so many of the small nice-to-have features we've introduced over the years. => expression bodies for methods is a good example. One could ask the same questions: "Is the code more readable? More efficient? More correct? More anything?" And yet, we really enjoy using them ourselves in roslyn. It only saves "{return}", but that's enough to make things feel that much more lightweight and enjoyable.

Same with things like ?? and ?. Same with "object initializers" and "collection initializers". Same with so many of the features we've added. Prior to these features it was always possible to write the same code that compiler would generate (and that's how we used to write things!). But it was still pretty annoying :D

your repeated use starts to look like you're not serious.

I'm simply trying to keep the mood light and pleasant. I really appreciate the time and effort many in the community take to constructively provide deep input into the decisions we make. It's amazing. I love it. And i really love talking about these topics and sharing with everyone the sorts of factors that go into decisions. It's really awesome to be able to discover and reflect on all the facets that people use to weigh out a language.

Cheers!

@dsaf
dsaf commented 2 days ago

@CyrusNajmabadi you mentioned team talking to people regarding this issue. Was this some sort of a target group identified and asked through some methodology or is it just an anectodic evidence collected through random casual conversations?

Also isn't there a discrepancy between strategic vision and tactical decisions the team is making? Why bother with pattern matching, tuples and other functional features if your target audience considers leaky scoping to be ideal? Wouldn't it make more sense to introduce things like language-level singletons and some other imperative features?

Not being mean, just trying to understand, thanks

@DavidArno
DavidArno commented 2 days ago

@CyrusNajmabadi,

I'm simply trying to keep the mood light and pleasant.

Only speaking for myself here, obviously, but:

  1. I think you are making a serious mistake and I do not feel that a jovial, "light and pleasant", mood is the appropriate one for discussing this issue,
  2. Unfortunately you use of smilies has made some of your replies appear highly patronising, rather than friendly. I accept that isn't your intention, but it has had that unintended consequence.
@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented 2 days ago edited

@dsaf We have many channels that we get feedback through. Github is only one of them. We've been doing C# for quite a long time, so there are many ways existing ways that information flows that are still around. My hope would be that eventually everything related to our work is just all done here. But you can't stop feedback coming in through other avenues that existing customers are used to.

Also isn't there a discrepancy between strategic vision and tactical decisions the team is making? Why bother with pattern matching, tuples and other functional features if your target audience considers leaky scoping to be ideal?

I don't see the dichotomy. Having out-vars be in wider scope doesn't seem to be relevant to the question about if we feel that pattern matches/tuples/functional features are valuable. I love pattern matching, tuples and functional programming features. But that doesn't mean I'm against wider scopes. Nothing about this seems like an either-or proposition :)

In my very personal opinion, C# is a very mixed bag language. It's a jack-of-all-trades language. It does not try to excel in any one area (except the area of providing a lot of tools to a developer to use to solve their problem). As such, we introduce features that are of varying levels of value depending on if you're primarily an imperative developer, a functional developer, an OO developer, a dynamic-com developer, etc. etc. (and any mix of all of those). Some features may be of zero use to some audiences, others may be huge valuable to just one of those groups. Others may be of mild value to a few groups. And on and on and on.

Speaking for myself, my "vision" is to provide a useful language with useful tools for developers. That inspires me and makes me want to spend time with the language and time with all of your. It's what makes me pour so much time into working on tooling improvements.

So, to that end, to me, this "tactical decision" doesn't feel like a discrepancy. It feels like it's furthering those goals.

Wouldn't it make more sense to introduce things like language-level singletons and some other imperative features?

Maybe next round :) We're constantly evaluating what we think is important enough for a release. Our evaluations led to the current feature set for this release, but we're not going to stop there. C# will (hopefully) continue to get many more releases, and we'll definitely always be (re)evaluating what we think the best set of features will be in the time we have.

@CyrusNajmabadi
Collaborator

@DavidArno What can you do? Both styles seem to have irked some people reading the posts. I'd rather err on the side of being friendlier than not.

I accept that isn't your intention,

Great!

I think you are making a serious mistake

I totally get that. I can firmly state that your opinions have never been in doubt (either here or on other topics you've weighed in on). Your reasoning has been totally clear, extremely logical and definitely something that has been given a lot of thought to. It's been amazingly appreciated and i can't wait to see your thoughts moving as we move forward!

and I do not feel that a jovial, "light and pleasant", mood is the appropriate one for discussing this issue,

Fair enough. Thanks for letting me know!

@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented 2 days ago edited

@DavidArno Would love to chat more about communication styles and whatnot. However, this is clearly not the right venue for that. I'd be happy to chat about this directly, but it doesn't seem appropriate in this topic. Feel free to reach my by any means (email/gitter/whatever). I'd really appreciate your help in improving how I discuss things with all of you! :)

@HaloFour
HaloFour commented 2 days ago

@CyrusNajmabadi

One person's 'minor annoyance' is another's 'real problem'.

You'd find just as much support for removing semicolons, curly brackets, parenthesis, dots and plenty of the other expected syntax of C#. Read back through some of the closed proposals here (sometimes even mocked by the LDM). Written as if someone glued a razor blade to their shift key. Are they really "real problems"?

Same with things like ?? and ?. Same with "object initializers" and "collection initializers". Same with so many of the features we've added.

No, not even remotely the same thing. All of those features were simply syntax candy and not a single one of them had wider ramifications to the language or to the foundation of larger language features to come. Not a single one of those features permanently altered/established rules pertaining to something as fundamental as scoping.

This approach would also have some slight symmetry with javascript, as they used 'let' to denote a block scoped variable.

A hack to fix what had been realized to be a massive mistake in the language design. Repeating history is fun.

There are currently 1969 usages of out-var in the Roslyn codebase. I used Roslyn to analyze every local generated by these out-vars.

There are two problems with this:

  1. You're using Roslyn as the sample. Compilers are often very different beasts written by a different class of developers. If the Roslyn team were to specifically target features to make writing Roslyn easier then I wager that the shape of C# over the years would have been significantly different.
  2. The results are predictable given that it's impossible for you to declare that variable in a narrower scope today. You wrote the code to fit the limitations of the language making that code self-selecting for wider scoping.

Well, it remains to be seen if it's actually a mistake :D

By which time there would be absolutely no possibility of fixing it. You don't have to look far to find evidence that this is a mistake. The scoping rules of JavaScript demonstrate this. So does Microsoft's own mistakes with for loops in C++. I'm sure someone there made a compelling argument for using the iterator variable based on their own use cases and code examples.

By the way, 3.3.3/1 [basic.block.scope]:

A name declared in a block (6.3) is local to that block; it has block scope. Its potential scope begins at its point of declaration (3.3.2) and ends at the end of its block. A variable declared at block scope is a local variable.

And 3.3.3/4:

Names declared in the for-init-statement, the for-range-declaration, and in the condition of if, while, for, and switch statements are local to the if, while, for, or switch statement (including the controlled statement), and shall not be redeclared in a subsequent condition of that statement nor in the outermost block (or, for the if statement, any of the outermost blocks) of the controlled statement; see 6.4.

@HaloFour
HaloFour commented a day ago

@CyrusNajmabadi

I mean, => only descreases verbosity by "{return}", but we still did it :)

And has no further side effects. I've mentioned this before.

Could you clarify this bit? I'm not sure what you mean by it. Thanks!

#14697 (comment)

@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented a day ago edited

So, what did you find uncompelling about guard? Seems quite well regarded in the Swift world.

I, personally, didn't find anything uncompelling about guard. The decision though came down to if it was really worth adding both out-vars and guards to attempt to provide both narrow and wide scoping semantics in this release of the language. The LDM discussed this at length and deemed it not important enough. Wide scoping was permissive enough for both use cases, while having a limitation that we felt would not be a significant issue in practice. We're cognizant of the desire for different scoping though, and we certainly could move to a place in the future where there is syntax for people who want he narrow approach.

In other words, the current approach didn't restrict future work here, and enabled scenarios for the majority of customers. And it did so with less new syntax and semantics. The LDM just didn't feel like there was enough value in the narrow-scoping case to warrant a new language feature for it from the start. We'll always be reassessing though. And as we move forward if we feel that that has changed, then we could certainly add it**

--

** "it", of course, wouldn't be 'guard' (as that givs wide scoping, which we already have). But it would the feature that gives narrow scoping for those who want it. Perhaps narrow scoping and readonly. I'm a fan of 'let' for this purpose as that's the semantics you get with it in a query-expression. but all syntax TBD and all that!

@CyrusNajmabadi
Collaborator

is worth torpedoing the entire discussion regarding the scope (and mutability) of pattern matching permanently in the language.
Could you clarify this bit? I'm not sure what you mean by it. Thanks!
#14697 (comment)

Sorry, i'm still not quite getting the point you're making. The comment you linked to talks about the argument being torpedoed because of switch scoping. As i mentioned already in my responses, we felt we were taking a pragmatic approach with 'switch'. Of the three options we had, it enabled the most scenarios, with the least restrictions (a similar reason to why we went with what we did for out-var).

Could you clarify as i think i'm missing something. Thanks!

@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented a day ago edited

I mean, => only descreases verbosity by "{return}", but we still did it :)
And has no further side effects. I've mentioned this before.

"out-var" in the majority of cases, has no further side effects. it can be swapped in almost all of the time in code. That's because today's code declares the variable in a certain scope, and nearly all cases of out-var will declare into that same scope. i.e. before you had wide-scope. And after you have wide-scope. So no side effects occurred due to the change **

--

** This is of course only true if you local was in the directly contained scope. If it was in a higher scope than that then observable semantics might change (i.e. around captures and whatnot). But those cases are extremely esoteric, and we optimize out-var for the extraordinarily common case of the local being in your directly contained scope.

@HaloFour
HaloFour commented a day ago edited

@CyrusNajmabadi

let might work in place of out var, but even then only in the unambiguous overload case. For patterns (specifically type-switch patterns) I can't even fathom how that syntax would work:

// baseline, doesn't explain what's going on, but whatever
if (int.TryParse(s, let i)) { ... }

// what?
if (Foo.TryParse(s, let int i) { ... }
if (Foo.TryParse(s, out int let i) { ... }

// ew
if (o is let int i) { ... }

As i mentioned already in my responses, we felt we were taking a pragmatic approach with 'switch'.

Pattern matching isn't switch. It starts with if and ?: (the latter being the closest we get to a match expression today).

"out-var" in the majority of cases, has no further side effects.

The side effect is in establishing these permanent scoping rules for pattern matching. out var cannot be looked upon as a simple verbosity improvement as a result.

@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented a day ago edited

@HaloFour I deeply regret even bringing up syntax. It leads to too much bikeshedding. :)

Let's just say that i believe that we could provide a syntax for narrow scoping, just as we've provided a syntax for wide scoping. Just as we coudl have if we went with narrow scoping by default, and introduce 'guard' to enable wide scoping.

The only different is what default we're going with (narrow vs wide) and what impact new syntax would have. Some people clearly would prefer narrow, with a syntax for going wide. I personally see no problem going wide with some syntax (TBD :) :) :) ) for going narrow.

@CyrusNajmabadi
Collaborator

Pattern matching isn't switch.

I still do not understand what argument it is you are making. You mentioned torpedoing two times. When asked about the latter, you linked to a post with the former. That post talked about how switch scoping torpedoed the arguments about scoping.

I thought i addressed those arguments, but you clearly feel differently. The problem is that i cannot actually tell what actual argument you're putting forth at this time. I don't want to presume or guess as i would rather address your real concerns and not some potential strawman i might erect. So i would really appreciate clarification on what your argument is here.

Thanks!

@HaloFour
HaloFour commented a day ago

@CyrusNajmabadi

The argument specifically is that due to the goal of keeping type-switch (and variable patterns) consistent with out var that implementing out var explicitly sets into place these permanent scoping rules before we can even restart the discussion as to what pattern matching will look like post C# 7.0, where hopefully it will be fleshed out significantly more.

@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented a day ago edited

The side effect is in establishing these permanent scoping rules for pattern matching.

Sorry, i'm not seeing how that's hte case. By that argument, if we had narrow-scoping-by-default, we couldn't add 'guards' later as we would have 'established permenant scoping rules for pattern matching'. Clearly, we can make changes to the language (possibly through new syntax) that allow for different sorts of scoping.

If it's acceptable to you to have narrow-scoping but then add syntax to make scoping broader, why is the reverse unacceptable? Why can we start with wide-scoping, but then add narrow scoping if we feel there is enough value there?**

--

** This is also how i would describe where we are with variables and mutability. We started the language with writable variables being the default. And that's not really going to change. But that does not stop us in the future from providing new syntax that makes it so that non-writable variables can be created. For situations where there are good reasons for both styles (writable/non-writable, narrow-scoped/wide-scoped), it seems totally acceptable to me that both be possible for the user to create.

@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented a day ago edited

@HaloFour See my last post. I don't think that that argument is correct. Nothing is made permanent here. No more so than if we'd provided narrow scoping, but then added wide-scoping escape-syntax in the future.

@alrz
alrz commented a day ago

Because we think the feature is good enough and has enough good to outweigh this particular concern.

You guys have gone through all the trouble and rewritten the entire compiler instead of "fixing" the old one. Now here you are trying to "fix" out params instead of investing on new idioms - procrastinating the actual value. I can't understand the the thought process behind these decisions.

@CyrusNajmabadi
Collaborator

@alrz Sorry, i'm not sure what additional information i can provide. I've explained the reasoning of the LDM and tried to explain a lot of the criteria by which we assess language features.

FWIW, i can definitely understand your perspective (and it totally makes sense to me). I'm sorry i haven't provided enough clarity to help your understand ours!

@HaloFour
HaloFour commented a day ago

@CyrusNajmabadi

See my last post. I don't think that that argument is true.

Not looking at the two holistically also means that whatever comes around the second time would have been an afterthought complete with the awkward syntax to go along with it. I would've implemented guard and let right out of the gate, so that wide- and narrow-scoping were both catered to and both first class citizens.

@alrz
alrz commented a day ago

@CyrusNajmabadi Sure, the feature is good enough on its own, but when we want to maintain consistent scoping rules between out var and pattern variables, it doesn't seem to be a standalone feature with no side effect to other features. As I said, out var, unlike pattern matching, has some special usage patterns, it requires special scoping rules that clearly affect scoping for pattern variables. I hope this clarifies my point.

@CyrusNajmabadi
Collaborator

Not looking at the two holistically also means that whatever comes around the second time would have been an afterthought complete with the awkward syntax to go along with it.

Something coming after the fact in no way implies 'awkward syntax'. Properties have evolved over pretty much every C# release, and i think the least awkward syntax has come later in their lifetime.

I would've implemented guard and let right out of the gate,

Fair enough :)

so that wide- and narrow-scoping were both catered to and both first class citizens.

I think things can be first class citizens even if they're added in subsequent releases. That does appear to be a differing of opinion when conversing on these topics. It's likely because i've been involved in so many language iterations over the years. I'm fully familiar with features not making the cut one release, only to make it 1, 2, 3, 4 releases down the line :D In practice this hasn't felt like a substantial problem, and i'm not averse to pulling in features that i think are worth it for one release, and the moving other changes later down the pipe.

@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented a day ago edited

@alrz Ah, i see what you mean. Thanks for the clarification! This was a complicated constraint that we looked at from all different angles. If it helps, i also started with the position that "out var, unlike pattern matching, has some special usage patterns, it requires special scoping rules."

@Kaelum
Kaelum commented a day ago

@CyrusNajmabadi
I have been quiet, as I wanted to state this and other things in the private email that you requested. However, I can't tolerate this passive-aggressive behavior any longer. You, and at least one of the LDM team members have engaged in this behavior, and it is a behavior that is not tolerated in any corporate environment that I am aware of. If Microsoft allows it, so be it. But it is not welcomed here. Directing a discussion into a never ending circle that doesn't exist, is passive-aggressive behavior.

That being said, I am extremely happy that you said you piece, as the other Enterprise Partners will see exactly what we are dealing with. As stated previously, I will send you that private email later this week, but it comes down to 2 things in the end. 1) Save a line of code for no other reason than to save a line of code, or 2) add a feature that can be extremely useful. This is my last public response to you, and anyone on the LDM team, until the other Enterprise Partners have had their say.

P.S. Lets call a spade, a spade. What you are referring to as narrow scope is actually normal scoping as defined by the official C++ Language Specification. And your wider scope is actually a leaky scope.

@HaloFour
HaloFour commented a day ago

@CyrusNajmabadi

Something coming after the fact in no way implies 'awkward syntax'.

I guess we'll have to wait another year or two and see.

The analogy of properties is quite poor given how verbose they started. You can't get much less verbose than type identifier. And yes, I do think that type !identifer is awkward in comparison, and I can't even begin to imagine how you'd cram a let in there without it being even worse.

@alrz
alrz commented a day ago

i also started with the position that "out var, unlike pattern matching, has some special usage patterns, it requires special scoping rules."

And that's it Ö? Am I supposed to take this as an answer or perhaps your sentence didn't get to the point where it actually makes your point. "This was a complicated constraint that we looked at from all different angles" certainly didn't make anything clear. Can you please elaborate?

@CyrusNajmabadi
Collaborator

@Kaelum

The Roslyn github repro is for discussing Roslyn related manners. That include language design around C#. I find huge value in conversing with the contributors here. I've learned a lot and it's helped me get a lot of clarity around certain perspectives and desires around the language.

@HaloFour, in particular, helped clarify several points that have helped me better understand how some people think about these features. I hope that i can apply this better understanding to future work, and i'm really glad i had the opportunity to learn from him, and others here!

As far as i'm concerned, as long as i feel like i can supply better insight in our decision making process, i'll continue to do so. I want to work towards as transparent a process as possible, and this is just one small step there. And, as well as that, as long as i feel like there is more i can learn from a discussion, then i will continue to participate. I don't want to make decisions made by turning a blind eye and not engaging with such a passionate community.

I'm sorry that my words have upset you. I take full responsibility for that. As @DavidArno pointed out though, he could accept that this wasn't my intent. My intent is to learn and share. In particular, i thought it would be super valuable to share actual investigations into usages of 'out' so that we could speak about real world, large scale, code in the wild without resorting to hunches, or gut feelings and whatnot. So i hope you can accept that my intent has been to improve understanding and communication in both directions. Thanks!

I look forward ot conversing with you later this week!

@CyrusNajmabadi
Collaborator

@alrz

And that's it Ö? Am I supposed to take this as an answer or perhaps your sentence didn't get to the point where it actually makes your point. "This was a complicated constraint that we looked at from all different angles" certainly didn't make anything clear. Can you please elaborate?

I didn't think i had anything additional to add. All i would have said was: after a large amount of investigation and conversation on the topic, i ended up in the position i'm in now. Namely of all the options possible, this one was the best balance of pros/cons.

I didn't feel the need to rehash all the arguments since i felt like i would just be repeating something you'd already heard from me over and over again.

@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented a day ago edited

And yes, I do think that type !identifer is awkward in comparison

Note: it probably won't be !. We're already considering that operator for something else. That's why i'd like to avoid syntax discussions for now.

And yes, I do think that type <new syntax> is awkward in comparison,

That may very well be the case. But here's how i see it. From my own cursory examination of how code is written, the majority case seems to be wide-scoped, and the minority is narrow-scoped. I don't mind the majority case getting the cleaner syntax. (and possibly only very slightly cleaner, or even not cleaner at all depending on if we do invest here in the future).

And, if it really comes down to it feeling a bit 'awkward' to have a some new syntax for narrow scoping, then i can honestly live with that. I see both use cases as valid and valuable, and it seems like, if we pick syntax and semantics, then one side may get the more 'awkward' syntax. That honestly doesn't seem like a big deal to me...**

--

** Another aside! Back when we did async/await there was contention about the precedence of the 'await' operator. With higher precedence i could then write something like await GetListAsync().Length whereas today i have to write (await GetListAsync()).Length. I actually wanted 'await' to have 'smart' precedence, whereby it would break apart the expression to find the innermost 'awaitable' so that you could easily write this sort of thing without needing all the extra parens. I find the current precedence and lack of smarts as something that leads to awkward code and i've wanted a less awkward system. Turns out, it is annoying, but not really a big deal. People parenthesize and move on. We may attempt to make this better in the future. But it's not really impeding people much.

This is a case where the common case got pretty good syntax, and hte less common case got more 'awkward' syntax. But, at the end of the day, it turned out to be no big deal . Sure, it may irk some people (myself included), but the language is still hugely useful even with that tiny issue.

@AlgorithmsAreCool

137 replies and 73 of them are from @CyrusNajmabadi in the last 22 hours.

What kind of keyboard do you use? I think I could double my typing productivity with one. :)

@CyrusNajmabadi
Collaborator

@AlgorithmsAreCool Lenovo4Life :) I love this keyboard.

@jnm2
jnm2 commented a day ago edited

The content you are editing has changed. Please try again.

I wouldn't have chimed in, but from my perspective as a non-participant, @CyrusNajmabadi is one of the least aggressive voices here. He seems to be calming the rhetoric and staying objective. I'm bewildered that there is so much energy spent towards personal conflict by a number of you. If you wouldn't mind sticking to more self-deprecating wording that doesn't encourage these side trails- I'm having a lot of fun following the actual language design discussion here. (I think I slightly side with the conservative scoping, in spite of the rhetoric of some of its proponents.)

The content you are editing has changed. Please try again.

Attach files by dragging & dropping or selecting them. Uploading your files… We don’t support that file type. with a PNG, GIF, JPG, DOCX, PPTX, XLSX, TXT, PDF, or ZIP. Attaching documents requires write permission to this repository. with a PNG, GIF, or JPG. We don’t support that file type. with a PNG, GIF, or JPG. Yowza, that’s a big file. with a file smaller than 10MB. This file is empty. with a file that’s not empty. This file is hidden. with another file. Something went really wrong, and we can’t process that file.

Nothing to preview

@eyalsk
eyalsk commented a day ago

@jnm2 I agree with you on the language but some people are just frustrated by the decisions made and I can relate to them on that even though I respect these decisions.

@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented a day ago edited

I think an important thing for healthy discussion is to accept that not every decision will be one that you personally agree with. In the past i've mentioned many a decision we've made that i haven't personally agreed with. But i still love working in this space and i'm still happy where we've gotten to, even if there are individual choices that weren't my cup of tea :)

Anyways, i hope everyone has a good thanksgiving. Then we can all talk politics and throw objectivity and calming talk entirely out the window! :D

@HaloFour
HaloFour commented a day ago

@eyalsk

but some people are just frustrated by the decisions made

And the process, which I've noted before. This isn't the first contentious issue to occur. String interpolation went around in circles for a bit. So did nameof. Not to mention private protected which remains punted. But in all of those cases the threads of logic were at least spelled out on CodePlex and there was a feeling of inclusion within the community. You could see the arguments being considered and addressed in each iteration and the comments to follow. Even if you disagreed with the end result it was hard to fault the process.

In this case it was like the team disappeared for months with exceptionally little information. Then, all of a sudden, just as issues are being marked as milestone RC, there is this big reversal on something originally "settled" back on CodePlex, on top of which more than a dozen other issues are derived. The arguments and discussions all happened internally and if we're lucky we might get an inkling as to the logic behind them.

I get it, sausage making is a messy business. But I think that most of the participants here would rather sit and watch the meat go in rather than have the curtains draw closed only to eventually reveal something resembling mystery meat.

@jnm2
jnm2 commented a day ago edited

The content you are editing has changed. Please try again.

@eyalsk I understand that feeling. I feel the same about the decision to include netstandard 1.6 in 2.0 and have MissingMethodExceptions. But your attitude about it is one I respect.

"Where people are involved, there are no solutions; only tradeoffs." -Thomas Sowell

The content you are editing has changed. Please try again.

Attach files by dragging & dropping or selecting them. Uploading your files… We don’t support that file type. with a PNG, GIF, JPG, DOCX, PPTX, XLSX, TXT, PDF, or ZIP. Attaching documents requires write permission to this repository. with a PNG, GIF, or JPG. We don’t support that file type. with a PNG, GIF, or JPG. Yowza, that’s a big file. with a file smaller than 10MB. This file is empty. with a file that’s not empty. This file is hidden. with another file. Something went really wrong, and we can’t process that file.

Nothing to preview

@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented a day ago edited

The arguments and discussions all happened internally and if we're lucky we might get an inkling as to the logic behind them.

We probably did do a poorer job with this particular feature than would be desired. But i, and others, have at least tried to explain the logic behind it. I don't think there's any lack of information now (i hope!). We've just settled on a final decision that isn't one that some don't happen to like.

If there's any more information/'logic' you'd like clarified, i'm happy to provide whatever information i can!

@dsaf
dsaf commented a day ago

@CyrusNajmabadi

We probably did do a poorer job with this particular feature than would be desired. But i, and others, have at least tried to explain the logic behind it.

I think the problem is that the logic behind it was based on politics and business (making entrenched legacy developers from strategic enterprise partners happy - which is fair) rather on some more "pure" language design logic. It would be more honest to shut off the discussion on spot as it happened earlier.

@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented a day ago edited

I think the problem is that the logic behind it was based on politics

Changing the language in a way that we feel benefits the most developers is not a political decision :)

At least, it isn't for me. Political would be if we actually felt the language changes were bad, but we needed to do it because some other group pressured us to make changes against what we thought were best. So far, thankfully, we've never had that. We've had our own autonomy to make the changes we think serve our customers and ecosystem best, and we've thankfully been able to leave politics out of it :)

and business

Yes... we're a business. We're going to take the language in the direction we feel makes the most sense for us. We're delivering a product, and we want that product to be widely useful and valuable to our ecosystem. I think that's sensible. If this was my own language, and i was doing it on my own, i might say "to heck with my customers, i'm going to do it my way!" But that's how we do languages here. We have customers and an ecosystem and we develop with our minds cognizant of both.

rather on some more "pure" language design logic.

This is a non-goal. I mentioned this earlier, but it bears repeating. C# does not aspire to be some ivory tower bastion of pure language design. It is not designed in some sort of <insert paradigm of choice> vacuum and then thrust into the world :) it is designed to be a language that is good for solving problems and for meeting developers where they are instead of insisting that they come along to where some might want them to be.

This irked me initially when i started working on the language. There were so many things we did that i felt were 'messy' and could have been handled in a 'purer', but ultimately less useful fashion. Over the years i came to truly appreciate the pragmatic approach taken by many in the design space to move the language forward in ways that would overall be quite helpful to the large ecosystem of developers we have, while consciously accepting that some of the designs would have unclean corners that we would have to live with moving forward.

This pragmatism, and the non-desire to serve 'pure language design logic' is one the strengths i see in C#. You'll also see it in other languages we develop (like VB and TS). I don't think we ever approach the problem from "what is the pure solution we can deliver". I think we approach it from "how can we best solve problems that users are facing?". I would expect this trend to continue as we move forward in C# in later versions.

@eyalsk
eyalsk commented 23 hours ago

@CyrusNajmabadi

If there's any more information/'logic' you'd like clarified, i'm happy to provide whatever information i can!

You do an outstanding job and excel at communicating everything, personally I appreciate it but sometimes people want to see things change more than they want explanations! 😆

In my opinion, the issue here is and I've noticed this in many posts is that we as a community expect too much from these discussions and get burnt by them.

A bit comical or surreal but many threads feels like this:

Design Team: We think 1, 2, 3 and we settled on 3.
Community: We think none of these are good enough, we propose 4.
Design Team: We thought about it but we don't think so.
Community: What? Why?
Design Team: We asked other devs, we researched, we... yeah 3 is better.
Community: We really dislike this decision.
Design Team: Don't worry it's the right decision.
Community: But..?

Now, to the Design Team finale we have few options:

a. It's too late now but the future is bright.
b. It's C# X but in X+1 we would change the world/scope.
c. It's the last feature for C# X but it comes with a price.

Don't take it so seriously please, this is just an observation.

Throughout many posts that I've read there was always this vibe that the design team already nailed down almost everything and you discuss it with the community only to double check corner cases.

I believe that these vibes and feelings are coming from the lack of transparency in the design process and the uncertainty of the community about their value maybe if there was a post that elaborated more on how much you value our feedback and where we fit in, then we, as a community would have some foundation to work with and it might change our perception about these discussions and we might engage these discussions from a completely different angle.

@AdamSpeight2008
Contributor

@CyrusNajmabadi
Here is my implementation's gist is this correct?

@DavidArno
DavidArno commented 13 hours ago edited

@jnm2,

I wouldn't have chimed in, but from my perspective as a non-participant, @CyrusNajmabadi is one of the least aggressive voices here. He seems to be calming the rhetoric and staying objective.

Thanks for chiming in. I'm aware that you clearly don't like some of my comments here, so it's helpful to understand what you don't like. It'll be no surprise that I disagree with you, but I won't rehash that topic as it's been gone over already.

(I think I slightly side with the conservative scoping, in spite of the rhetoric of some of its proponents.)

And that in a nutshell sums up the issue here. Even you, who doesn't like the way many of us are saying it, and who therefore wants to side with @CyrusNajmabadi, are not buying the content of what he's saying. And that's why many of us are getting frustrated and the tone is as it is.

You can only read "thanks for you opinion, but other people have other opinions" and "we have a duty to deliver what the majority want and most people are asking for this feature" type comments so many times, with no details of who these mystery people are, without getting frustrated and annoyed. When people get frustrated and annoyed, the tone of their communications change.

@eyalsk
eyalsk commented 11 hours ago edited

@CyrusNajmabadi

This pragmatism, and the non-desire to serve 'pure language design logic' is one the strengths i see in C#. You'll also see it in other languages we develop (like VB and TS). I don't think we ever approach the problem from "what is the pure solution we can deliver". I think we approach it from "how can we best solve problems that users are facing?". I would expect this trend to continue as we move forward in C# in later versions.

Fair enough, being pragmatic is extremely important, I completely understand your point of view but to me the current approach means you're taking big steps where you shouldn't.

You can be pragmatic and yet conservative.

I think that there are two issues here:

  1. We as a community don't know whether the amount of people that really want widened scope is actually greater than these who don't, I wonder if you as the design team have the numbers for it?

    If you do, then maybe this should be the default but I'd argue that many of us coming from C/++ or Java are used to have narrowed scope when variables are declared as part of a statement.

    I fully realize that C# is a complete different language with its own style, rules, features but because C# belongs to the C family some basic assumptions should hold.

  2. Now, regardless to whatever the default is, it could have been nice if we had a way to change the scoping rules of the declared variables.

    Maybe this is another topic but I feel like instead of tackling small, individual problems, then maybe a general solution is available that can be applied to other areas of the language but this require scoping rules to be consistent across the language.

@CyrusNajmabadi I hope that you still have some stamina left! 😉

@AdamSpeight2008
Contributor

@eyalsk That's my approach what is the best holistic thing we can do? Can we do more than one thing / unify things if we tweak the design? Good example LINQ being based on patterns rather than a interface.
Another is Implicit Line Continuations in VB.net it only removed 1 or 2 character from a statement, but made big difference to the look of the language.

Introducing variables into narrowing scope, is currently hard to do if not impossible a counter-example would be humble accepted, We should be extremely cautious when using existing language keywords in a new context. Eg out and var.
out would suggest the variable is being "pulled" into the current (wider) scope. If we require that we "push" variable into a narrower scope, a change to one of the keywords would help in conveying that intention.

Why don't we use out var and in var ? Using in and out to say where the variable is to going to be introduced. I kinda like this a lot.

@HaloFour
HaloFour commented 9 hours ago

@AdamSpeight2008

The out is the direction of the parameter, it has nothing to do with scoping. The scope is inherited from the declaration, which would be the var keyword (which is, of course, not necessary to use there).

The question is what should the scope be for variables declared within an if or loop condition. The entire family of C languages interprets that as one way and C# has decided to go a completely different way (sometimes, it's not even going to be consistent with itself regarding that fact).

But it's too late. This will be the butt of many, many jokes.

@AdamSpeight2008
Contributor

C# isn't C or C++.

@DavidArno

@AdamSpeight2008,

Why don't we use out var and in var? Using in and out to say where the variable is to going to be introduced. I kinda like this a lot.

To my mind this won't fix the real issue with this language change: the fact that is var will have the same leaky behaviour that out var does.

@AdamSpeight2008
Contributor
AdamSpeight2008 commented 9 hours ago edited

@HaloFour @DavidArno What happens if I use it a non-if or loop? eg

var result = ( obj is Int32 var x );
var ok = Int,TryParse( text , out var y);
@HaloFour
HaloFour commented 9 hours ago

No it's not, but it decided to borrow that syntax and the behavior that it implies. Sure, they can deviate, but doing so breaks expectations, particularly one this universal.

The only other C-like language to break so completely from these established scoping rules is JavaScript, and it's scoping rules are absolutely considered a joke and a massive design failure, listed under the "Awful Parts" of Douglas Crockford's book. The other instance of breaking these scoping conventions is when Visual C++ expanded the scope of variables declared within for loops, and that is also considered a joke and a massive design failure.

What happens if I use it a non-if or loop?

The syntax would be:

var result = ( obj is int x );

And result would be a bool in scope and x would be an int in scope, but not definitely assigned (aka, you aren't even legally allowed to read it).

@AdamSpeight2008
Contributor

It could be assigned.

@HaloFour
HaloFour commented 9 hours ago

@AdamSpeight2008

It could be assigned.

But not "definitely assigned", and the C# compiler does not permit reading variables that it cannot guarantee has been assigned. That above statement would be akin to the following:

int x;
if (obj is int) {
    x = (int)obj;
}

Console.WriteLine(x); // compiler error
@AdamSpeight2008
Contributor
AdamSpeight2008 commented 9 hours ago edited

@HaloFour
Easy yeah, just like the this for inner scoped.

if ( obj is int ) 
{
   var x = (int)obj;
   if( x < 0 )
   {
     throw new ValueOutOfRangeException(nameof(x) );
   }
}

Which kinda put dent into some the arguments provided.

The inner scoped version of this is simple to write.

It the inner scoped TryParse usage that's tricky.

@HaloFour
HaloFour commented 9 hours ago

@AdamSpeight2008

Only thing worse than an identifier unnecessarily in scope is an identifier that is unnecessarily in scope that you can't even use until you reassign it.

The example using pre-C# 7 syntax would be the obvious and idiomatic:

if (obj is int) {
    int x = (int)obj;
    Console.WriteLine(x);
}
// no x in scope here
@DavidArno
DavidArno commented 9 hours ago edited

@AdamSpeight2008,

It's why is var should not leak as most of the time that leaked variable is unusable, as it's not guaranteed to be assigned.

The only two use-cases for leaking is var's seem to be:

if (!(o is int i)) i = 0;
  ...; // would be nice if i was in scope and definitely assigned here

if (!(o is int i)) throw new ArgumentException("Not an int", nameof(o));
  ...; // we know o is int, but i is out of scope :-(

Both are handled using other c# 7 syntax though:

var result = o is int i ? i : 0;
  ...; // def assigned result is in scope

var result = o is int i ? i : throw new ArgumentException("Not an int", nameof(o));
  ...; // o is int and value is in result

There is no scope surprise here at all and both statements are at least as easy to read as the scope-leakage versions. Regardless of whether out var's leak, there is no justification for is var to do so.

@AdamSpeight2008
Contributor
AdamSpeight2008 commented 8 hours ago edited

@DavidArno Don't change the structure of the code, to use a different construct.
We are discussing If statements not If expressions usage.

That's where having a way specify the direction of the introduction helps.

if ( !(o is int out x ){ x = 0 }; // ie it borrows syntax from out var of out parameters.
... ; // x  is in scope and definitely assigned here

if ( !(o is int let i ){ // x is in scope and definitely  assigned };
... ; //  x isnot in scope and may not be 

result = (obj is Int32 i) ?? 0;

@DavidArno
DavidArno commented 8 hours ago edited

@AdamSpeight2008,

Sorry, but I have no idea what you are trying to say. What is o is int out x?

And "@DavidArno Don't change the structure of the code, to use a different construct.
We discussing If statements not If expressions
". Sorry, I don't understand. Are you suggesting that we should introduce a scoping change just so people can use if instead of the ternary expression operator? 😕

@AdamSpeight2008
Contributor

@DavidArno May I politly suggest that you reread the issue then 😃

The conditianal clause the obj is int x part, has changed signature;-
from Expr<Bool> returns a single value.
to Expr<(result: Bool, value: Option<T>> (that effectively returns two value) )

@DavidArno

@AdamSpeight2008,

Yep, I get that bit. But what is that out doing in o is int out x?

@AdamSpeight2008
Contributor
AdamSpeight2008 commented 8 hours ago edited

It's marshalling the direction in which the value being is passed or introduced.
out aka var x akin to bool M( out int x ) { ... }
in aka let x

@HaloFour
HaloFour commented 7 hours ago

@AdamSpeight2008

I could almost back let as the proposed modifier keyword, but that does have a few issues to resolve:

  1. How to best combine the keywords without it turning into word soup.
  2. Can it be applied to out declarations or only pattern variables? If both, how to apply with out declarations so that their usage makes sense syntactically.
  3. Several layers removed from the original narrow scoping rules and immutability of pattern variables it was proposed that a simple let declaration be effectively shorthand for readonly var. Does that imply that pattern variables decorated with let would be both narrow in scope and readonly?

I'll make the assumption that none of this will be on the table for C# 7.0 or probably even C# 7.1.

@eyalsk
eyalsk commented 7 hours ago

@AdamSpeight2008 I don't know about you but neither is int let x nor is int var x reads well.

@jnm2
jnm2 commented 7 hours ago edited

The content you are editing has changed. Please try again.

if (Inserted(outlet object fork)) ...

The content you are editing has changed. Please try again.

Attach files by dragging & dropping or selecting them. Uploading your files… We don’t support that file type. with a PNG, GIF, JPG, DOCX, PPTX, XLSX, TXT, PDF, or ZIP. Attaching documents requires write permission to this repository. with a PNG, GIF, or JPG. We don’t support that file type. with a PNG, GIF, or JPG. Yowza, that’s a big file. with a file smaller than 10MB. This file is empty. with a file that’s not empty. This file is hidden. with another file. Something went really wrong, and we can’t process that file.

Nothing to preview

@DavidArno

@AdamSpeight2008,

Hmm, I feel we are going around in circles here. So, rather than use "existing" C# 7 syntax to allow the type of a variable to be tested and bought into scope with a guaranteed value, you want to:

  1. Keep is var variable leakage, even though those leaked variables are unusable in most cases,
  2. Stop them leaking by shoehorning let in there to create x is T let y expressions.

And your reason for not wanting to solve this with ?: expressions, is?

@AdamSpeight2008
Contributor

To give the people the choice, my proposal work with both forms of expression, can make exist "leaky" and narrower shorter to write.
Whereas you could be interpreted as saying you only want it to work with ? : expression.

@AdamSpeight2008
Contributor

@eyalsk Neither does is int x baa

@eyalsk
eyalsk commented 5 hours ago

@AdamSpeight2008 Yup, I never was a fan of the syntax. 😉

@Velocirobtor
Velocirobtor commented 5 hours ago edited

Here are my thoughts on the discussion:

  1. Since both wide and narrow scope have their own pros and cons: please add BOTH or NONE of them to the language. Adding only one would lead to awkward stuff in the future (like cramming a let in there somewhere)
  2. Please make the current out var syntax narrow. I think it's much more intuitive to have it narrow and I really like @alrz syntax for the leaky version (just reuse the tuple syntax).
  3. Since I think if (!(o is int i)) throw...; looks hella awkward anyway, I propose adding a not operator that leaks any scoped variable in the following boolean expression (Allowing it on any boolean expression would make it useful for negated Try... patterns too, but it probably works and looks best on is Type t expressions).

To wrap it up:

if(o is int i){ /*use i */ }
//i not in scope

if(not o is int i) { /*i in scope but definitely not assigned */ }
else{ /* i in scope and definitely assigned */ }
//i still in scope and maybe assigned
// (definitely if there was a throw, return or assignment to i in the if block)

if(int.TryParse(s, out int i)){ /*use i*/ }
//i not in scope

if(not int.TryParse(s, out int i)){...}
//i still in scope

var result = SomeMethod(out var x, out var y, ou var z);
// x, y, z not in scope :(

var (result, x, y, z) = SomeMethod();
//x, y, z are in scope :) and it's shorter :D
@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented 5 hours ago edited

@eyalsk

Fair enough, being pragmatic is extremely important, I completely understand your point of view but to me the current approach means you're taking big steps where you shouldn't.

You can be pragmatic and yet conservative.

That's definitely true. And that was on the table. At the end of the day we made a judgement call. A call i can tell that several people here disagree with, but that we're happy with. Note that that's been the case with pretty much all the features we've done. With every feature there has been feedback and complaints that people didn't where we landed and that it should change.

With all of this feedback we've attempted to get enough information, through all available channels to us, and we've used that to either change things, pull things out, or decide to keep things as is. You'll see examples of each of those in the C#7 process. This is actually a case where we decided to change, precisely because we got enough feedback about our previous solution. We made the change with ample discussion that made us believe it was the right thing both for this release, while also allowing us to make suitable changes in the future if we thought it was necessary.

There are no perfect solutions. Just tradeoffs. And eventually you make a call when you think you've made the right set of decisions to balance those tradeoffs. If we were not able to do that, we would literally be unable to ship any language features at all. :)

I think that there are two issues here:
We as a community don't know whether the amount of people that really want widened scope is actually greater than these who don't, I wonder if you as the design team have the numbers for it?

I haven't ever claimed that the amount of people is greater. What i've stated is that we settled on a design that would be usable by both groups. And that the downside of that design was not sufficient to warrant exclusion of this feature.

I fully realize that C# is a complete different language with its own style, rules, features but because C# belongs to the C family some basic assumptions should hold.

Totally fair critique.

Now, regardless to whatever the default is, it could have been nice if we had a way to change the scoping rules of the declared variables.

Sure. I don't disagree with this. We'll see, moving forward, how much of an issue this is. And if it's a large issue, i'd have no problem with us providing a way for people to change the scoping if it's really important.

@CyrusNajmabadi I hope that you still have some stamina left!

Lol. I am super slammed with dev work. But i love these convos. So i'll def being continuing to participate here as long as this stays so productive and helpful :)

@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented 4 hours ago edited

@DavidArno

The only two use-cases for leaking is var's seem to be:

There are at least two more that we got feedback on that people cared about:

Something value;
if (!cache.TryGetValue(key, out value))
{
     Complex();
     Initialization();
     value = That();
     And();
     cache[key] = value;
     Stuff();
}

// use value

As well as:

Some some;
Thing thing;
JustGetSutff(out some, out thing);

Also, wrt to:

var result = int.TryParse(..., out var i) ? i : 0

We got strong feedback that people did not like the idea that any construct could declare multiple variables and not have those variables be in the same scope. i.e. 'var result' and 'var i' being in different scopes was really disliked by many people. We discussed at length and decided we agreed with that sentiment for the default behavior of var-declarations.

Now, once we settled on that, that helped motivate both teh 'for' and 'foreach' semantics. They already have a scope where variables are declared, so it was intuitive and reasonable for out-var variables to go into that same scope.

Next, we asked ourselves about the intuition of LocalDeclarationStatements and ExpressionStatements. It was our strong belief that these behave the same way:

SomeType t = Initialize(...);

// vs.

SomeTime t;
t = Initialize(...);

And as we viewed LocalDeclarationStatements as having only one scope (that of their container), and not having a scope for their initializer, it made sense to us that in the ExpressionsStatement case, just having the initializer would not have its own scope and any variables declared would flow into the outer scope.

Ok, so that covers nearly everything**. What we basically had left at that point was 'if'. 'if' is clearly quite contentious, with totally rational reasons for desiring separate scoping. At the end of the day we made a judgement call that we didn't want to preclude users of either scoping style from using out-vars in 'if's. If we went with narrow scoping, then people who wanted wide-scoping wouldn't be able to use it at all. f we went with wide-scoping then people who like narrow scoping would be inconvenienced with having to pick unique names for disparate types.

We felt like that was an acceptable tradeoff. One approach made the feature unusable for a group that we felt had a legitimate use case. One approach made the feature slightly more clunkly for a group that had a legitimate use case. Given the options of excluding a group vs inconveniencing another, we went with the latter.

Note that we're still open and aware that narrow scoping is desired by some. I'd have no problem with us giving a narrow scoped construct to help out for that use case in the future.

--

**

There are a couple of other cases i left out, (like while/do-while, switch, and embedded statements). But i don't think they're particularly common, nor do i think the decision making around them is particularly contentious. I'm happy to dive into the details of them if you want. But i felt they would clutter the discussion otherwise.

@CyrusNajmabadi
Collaborator

Since both wide and narrow scope have their own pros and cons: please add BOTH or NONE of them to the language. Adding only one would lead to awkward stuff in the future (like cramming a let in there somewhere)

I don't understand the logic there. If you believe that it would be possible to add 'both' now, why would it not be possible to add one now, and another later?

@eyalsk
eyalsk commented 4 hours ago

@CyrusNajmabadi Thanks for the replies, I really appreciate it! 😉

@AdamSpeight2008 I can but won't provide a video of a person working on .NET team struggling with the C# 7 syntax for pattern matching and tuples while lecturing and previewing the new features, it was amusing and sad at the same time. 😄

Now, we are following the development of the language so it's trivial for us but when you see a person that is working on the .NET framework and wrote quite a bit of C# struggling then it makes you wonder whether the syntax make sense.

@CyrusNajmabadi
Collaborator

I appreciate it but sometimes people want to see things change more than they want explanations! 😆

Ha! That's fair. But i don't want to misrepresent my interaction here. I'm here to learn and discuss. To share and absorb. I'm not here to promise change. I'm not a politician ;-)

What i will say is that we take all this feedback seriously, but that we tend to take the 'long view' on things. We expect C# to have many more releases (and personally, i expect to see more rapid releases moving forward), so we're quite willing to make incremental improvements, with the belief that we will be able to continue improving things as we move forward.

Will changes here happen in the short term. My gut would say no. I don't think much of our thinking has changed here (though i will roll all this feedback back up again). Will change happen in the medium term in this area? I'm honestly not sure. it will definitely depend on how much feedback we get from this across so many of the different development areas we see C# being used in. We'll continue to adjust priorities based on the information we get, and perhaps this will get more work done in the future. I know that i would personally like it :)

@CyrusNajmabadi
Collaborator

Now, we are following the development of the language so it's trivial for us but when you see a person that is working on the .NET framework and wrote quite a bit of C# struggling then it makes you wonder whether the syntax make sense.

I've been working on C# for nearly 15 years. And i still can't get operators right ;-)

@HaloFour
HaloFour commented 4 hours ago

@CyrusNajmabadi

I don't understand the logic there. If you believe that it would be possible to add 'both' now, why would it not be possible to add one now, and another later?

The disadvantages here being:

  1. Having to reprove the point of the necessity or utility of having a second syntax. Basically starting from scratch through the proposal process.
  2. The syntax used now will be the simplest form of the syntax which would mean that anything added later will inevitably be more awkward. It is literally impossible to get simpler than [out] (var|type) identifier. Considering both forms at the same time allows for potential accommodation in current syntax to better avoid this.
@HaloFour
HaloFour commented 4 hours ago

@eyalsk

person working on .NET team struggling with the C# 7 syntax for pattern matching and tuples while lecturing and previewing the new features

Which is a separate problem but I think why so many people really didn't understand what pattern matching was going to bring to the table which is why there was so little excitement for it. Pattern matching should have generated at least as much buzz as null-propagation, if not more. Any time I demonstrated use cases for recursive patterns you could see the eyes light up with the realization of the sheer amount of code that could be avoided simply navigating object models to obtain information necessary to make a decision.

I am not a functional programmer. My primary languages are C# and Java. I've messed with F# and Swift and written just enough Scala to make me believe that said language was designed as a cruel practical joke. But I recognize that pattern matching has a huge potential to impact how we write code, to the same degree that LINQ and async does. With all of this talk about the long game all I'm seeing here are short-term decisions intended to barely dent existing solutions.

@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented 4 hours ago edited

Having to reprove the point of the necessity or utility of having a second syntax

We'll have to do that no matter what if we want both a wide and narrow scoped system. We can't avoid that.

Doing the work now doesn't change that. It just means that that features arrives in C# 7 instead of C#7+

@CyrusNajmabadi
Collaborator

With all of this talk about the long game all I'm seeing here are short-term decisions intended to barely dent existing solutions.

Here's how i like to think about it: We make features of all sizes. Sometimes features are massive (Generics, linq-in-aggregate, async, etc). Somtimes they're teeny weensy (binary-literal-separators, interpolated strings, ??, etc.). What we've found is that it's great to work and expand on the big ideas, and we should shy away from those. But we also think that it's really important to continue providing the little things that just end up being delighters for people who feel yet another piece of friction in the language melting away.

@DavidArno
DavidArno commented 3 hours ago edited

@CyrusNajmabadi,

The only two use-cases for leaking is var's seem to be:

There are at least two more that we got feedback on that people cared about:

Um, your examples are of using out, not the new is T x syntax (or is var's). Have out var leak all over people's code if you want. But having is var's leak in the same way is nonsensical as the variable ends up in scope, but not guaranteed to be assigned and thus unusable in most use cases.

That's all.

@HaloFour
HaloFour commented 3 hours ago

@CyrusNajmabadi

Which is a great thing. Those little improvements in the day-to-day do help out a lot, particularly in the reduction of verbosity in the common cases. I have no problems with idea behind out declarations, only to the extent that the implementations decisions impact other (potential) features.

As much as I prefer consistency, I think I side with @DavidArno. Make pattern variables narrow. Their nature makes definite assignment a much trickier prospect in a wider scope anyway leading to scope pollution of effectively useless identifiers. While I'm sure that you can find examples of existing code using is and as with casting and null checking to favor a wider scope I would also expect that to be in the minority.

@CyrusNajmabadi
Collaborator

But having is var's leak in the same way is nonsensical

The tension here is that there is a ton of feedback that having these different types of variables have different scopes would be very undesirable.

@CyrusNajmabadi
Collaborator

Make pattern variables narrow.

I hold that position myself, and represented it during the discussions. There was enough sentiment and compelling arguments that the downsides of that would outweigh the benefits.

@HaloFour
HaloFour commented 3 hours ago

@CyrusNajmabadi

There was enough sentiment and compelling arguments that the downsides of that would outweigh the benefits.

Such as what? How does that reconcile with the fact that you can't use the variables if the compiler can't guarantee that the pattern succeeded?

@CyrusNajmabadi
Collaborator

Such as what?

That having different expression-that-introduce-variables put those variables into different scopes was extremely confusing.

@DavidArno
DavidArno commented 3 hours ago edited

@CyrusNajmabadi,

The tension here is that there is a ton of feedback that having these different types of variables have different scopes would be very undesirable.

They have different scopes:

  • An out var is accessible both for reading and writing outside of an if or while statement.
  • An is var is always write-accessible outside of an if or while statement, but only read-accessible if it's guaranteed to have been assigned.

There are three separate scoping rules for out var. is var introduces a forth one. Any claims this is about consistency and that there's been a "ton of feedback" asking for these four rules over every other suggestion made here is dubious at best (and I'd suggest, downright untrue).

@HaloFour
HaloFour commented 3 hours ago

@CyrusNajmabadi

<sarcasm>
Yeah, because the different rules between if, while, for, foreach and switch are intuitive.
</sarcasm>

If the variable cannot be read there's no reason to have it in scope. I can guarantee you that this will be more confusing, not less.

@alrz
alrz commented 3 hours ago

I agree, now that I think about it, there is no reason for them to be consistent, in fact, I think declaration expressions also can use narrow scoping. out var is a special case and should not affect other scopings. Pattern variables are introduced via case and is and the former already has a different scoping. and don't forget about let which allows wider scoping for patterns.

@CyrusNajmabadi
Collaborator

@DavidArno as i mentioned "I hold that position myself". :)

@CyrusNajmabadi
Collaborator

Yeah, because the different rules between if, while, for, foreach and switch are intuitive

They were to me. 'for'/'foreach' are definitely intuitive. I would be loath to have different semantics for them. 'if' and 'while' are also intuitive to me. Namely, the variables go into the scope that variables always went for them. All four of these are intuitive for me because the intuition is "the scope is the same as its always been for C#".

The only outlier is 'switch'. But a major problem for me there is that nothing is intuitive. That's because switch itself (in the absense of out-vars or patterns) already has such incredibly wonky, inherited, scoping. We could have kept that scoping, and we certainly considered it. However, we also realized we could enable a different scoping that wouldn't negatively affect anyone, but would enable some reasonable code patterns. So we made an exception there, precisely because switch is so strange and exceptional :)

FWIW, 'intuition' is rather interesting. From talking to many developers it's clear there is no consensus on this. And, from my own experience investigating this, the majority of devs i've talked to did not think scoping would behave as you outlined it. So their intuition did not match yours. That's not to say that your approach is wrong. Just that supporting it with the 'intuition' claim may not hold up :)

@CyrusNajmabadi
Collaborator

I agree, now that I think about it, there is no reason for them to be consistent,

I think there's a reason for them to be consistent. But i also think being consistent is unfortunate and i would personally prefer them to not be consistent.

Oh well, i lost that battle, but i'm used to losing at least a few battles per release by now :-)

@Kaelum
Kaelum commented 2 hours ago

@CyrusNajmabadi, so it's perfectly fine to add functionality to the flawed/improperly defined out while at the same time purposely adding flawed/unusable functionality to is? Forgive my French, who is the idiot who thinks that this is a good idea? If I were the CEO of Microsoft, I know who I'd be handing out pink slips to.

@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented 2 hours ago edited

@Kaelum Yes. Language design is about tradeoffs. :)

With nearly every feature we implement there are flaws in any part you examine**. That's always been the case. If we fired people every time that happened, we would not have been able to even ship a second version of the language :)

--

** Trust me, i know pretty much every little wart and niggle that we eventually settled on with every feature. in the end though, we struck a balance where we felt that those set of issues was acceptable given the positive value we were providing.

@DavidArno

@CyrusNajmabadi,

I think there's a reason for them to be consistent. But i also think being consistent is unfortunate and i would personally prefer them to not be consistent.

You're not making sense here. They aren't consistent, so there can't be a "reason for them to be consistent". out var's and `is var''s both leak, but in different ways. That's not consistent. There is no consistency in anything to do with this topic beyond the universal dislike of it by the community here.

@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented 2 hours ago edited

They aren't consistent, so there can't be a "reason for them to be consistent".

To you. Not to others. I've covered this before. Both introduce variables. As such, some people want things be consistent in terms of how they introduce those variables (i.e. which scopes they go into).

That consistency is something totally reasonable to want, even if it's not something you find important or desirable. I, personally, don't care about that aspect of 'consistency'. However, i'm strongly cognizant that there are many others who do want it. It's my place to discuss things different options and work to build consensus among the LDM as to where we should go. In this case, consensus decided that that consistency was important enough to factor into the design. I personally disagreed, but that's that. I'm not some sort of dictator that can just bend the langauge to my personal whims. And, that's probably a pretty great thing :D

@HaloFour
HaloFour commented 2 hours ago

@CyrusNajmabadi

And, from my own experience investigating this, the majority of devs i've talked to did not think scoping would behave as you outlined it. So their intuition did not match yours.

I question your sample size.

That consistency is something totally reasonable to want, even if it's not something you find important or desirable.

No, it's not reasonable to want when that variable is not usable.

@jnm2
jnm2 commented 2 hours ago edited

The content you are editing has changed. Please try again.

I question your sample size.

@CyrusNajmabadi This has come up in a number of discussions now and it has me curious. It's a bit like tax returns. I can only think of one reason why you wouldn't want to be transparent about the sample size.

The content you are editing has changed. Please try again.

Attach files by dragging & dropping or selecting them. Uploading your files… We don’t support that file type. with a PNG, GIF, JPG, DOCX, PPTX, XLSX, TXT, PDF, or ZIP. Attaching documents requires write permission to this repository. with a PNG, GIF, or JPG. We don’t support that file type. with a PNG, GIF, or JPG. Yowza, that’s a big file. with a file smaller than 10MB. This file is empty. with a file that’s not empty. This file is hidden. with another file. Something went really wrong, and we can’t process that file.

Nothing to preview

@alrz
alrz commented 2 hours ago

You can't use is var the same way you use out var. is can't be used as an statement unlike a method invocation. the latter is always definitely assigned while the compiler can determine if is yields a value. sames true for declaration expressions, they are always initialized therefore they are definitely assigned. and case var is a totally different beast.

@Kaelum
Kaelum commented 2 hours ago edited

@CyrusNajmabadi the fact is that we know that we are being lied to, and now everyone is begining to realize it.

For those who don't know what passive-aggressive personality disorder is, look it up. Other members of the LDM team have also displayed this disorder.

@CyrusNajmabadi
Collaborator

@Kaelum I have no idea what you're talking about. I don't believe i've lied to any of you. If you feel otherwise, please absolutely email me to let me know when you think that happened. I certainly have never been trying to intentionally deceive. But it's possibly i may have accidentally misstated something, or done somethign unintentional. If you can email me examples of where you think this has happened, i'd be happy to take a look. Thanks!

For those who don't know what passive-aggressive personality disorder is, look it up. Other members of the LDM team have also displayed this disorder.

Can we please keep the discussion on topic. These asides seem to add no value and detract from this interesting and valuable discussion. Thanks!

@CyrusNajmabadi
Collaborator

You can't use is var the same way you use out var.

I agree :)

--

Note: i'm happy to keep discussing this, but i want to make it clear that you probably don't need to convince me of the value or narrow-scoping and that there are difference between these constructs. I definitely already believe that. That said, my beliefs (and your beliefs) are just one factor into shaping the language. They are not paramount and they were eventually outweighed by other positions.

I'm only saying this so you aren't operating under a mode where you think you need ot convince me of this :) But i am still happy to continue discussing it.

@HaloFour
HaloFour commented 2 hours ago

@Kaelum

I don't believe that's fair. At least @CyrusNajmabadi is willing to discuss the subject. But as I've said before and has been confirmed, this had been argued, decided and finalized internally with zero visibility or transparency. By the time it was mentioned here it was too late. I do think that the LDM did that on purpose, knowing full well the reaction that would result. This is absolutely awful and will have far-reaching ramifications to the trust of the development of the language despite the fact that it was an outlier.

@jnm2
jnm2 commented 2 hours ago edited

The content you are editing has changed. Please try again.

Hey, @CyrusNajmabadi. I think this is fair and on-topic: Sample size has come up in a number of discussions now and it has me more curious the more it's avoided. It's a bit like tax returns. I can only think of one reason why you wouldn't want to be transparent about the sample size.

This is certainly a PR problem that I see causing legitimate dissatisfaction.

The content you are editing has changed. Please try again.

Attach files by dragging & dropping or selecting them. Uploading your files… We don’t support that file type. with a PNG, GIF, JPG, DOCX, PPTX, XLSX, TXT, PDF, or ZIP. Attaching documents requires write permission to this repository. with a PNG, GIF, or JPG. We don’t support that file type. with a PNG, GIF, or JPG. Yowza, that’s a big file. with a file smaller than 10MB. This file is empty. with a file that’s not empty. This file is hidden. with another file. Something went really wrong, and we can’t process that file.

Nothing to preview

@CyrusNajmabadi
Collaborator
CyrusNajmabadi commented 2 hours ago edited

I can only think of one reason why you wouldn't want to be transparent about the sample size.

It's large enough to make us satisfied. And it's likely larger than at any other point in C# history. While i can't put exact numbers on it, i would say it's likely that there's been direct conversation with hundreds, and disseminated information, with requests for feedback, to far more (i.e. thousands+).

We've also explicitly proffered direct feedback on this precise concern. i.e. didn't just say "here's a bunch of new features, what do you think?". We said specifically things akin to "we're considering these sets of behaviors around scopes with these new constructs, these are the pros and cons. Let us know what you think and if you like/dislike it". GitHub is but one channel for this. It's a new one, and a great one, and one i want us to move to using more and more in the future. But there are many existing channels with customers we view as just as equally important as everyone her.e

This is similar to approaches we've taken in the past where we've often received huge swaths of feedback. We took the feedback we got from this, assessed it, and also evaluated it against the context of how we've received feedback in the past. With all of that, we landed on the decision we're at now.

@CyrusNajmabadi
Collaborator

@jnm2 I saw your first post :)

@jnm2
jnm2 commented 2 hours ago edited

The content you are editing has changed. Please try again.

@CyrusNajmabadi Thanks so much! Wasn't sure, edited in the @mention after the fact.

I'm not in any danger of taking this personally. =D (See: last quote in https://en.wikiquote.org/wiki/The_Hobbit#Chapter_XIX:_The_Last_Stage)

Hopefully these facts help others though.

The content you are editing has changed. Please try again.

Attach files by dragging & dropping or selecting them. Uploading your files… We don’t support that file type. with a PNG, GIF, JPG, DOCX, PPTX, XLSX, TXT, PDF, or ZIP. Attaching documents requires write permission to this repository. with a PNG, GIF, or JPG. We don’t support that file type. with a PNG, GIF, or JPG. Yowza, that’s a big file. with a file smaller than 10MB. This file is empty. with a file that’s not empty. This file is hidden. with another file. Something went really wrong, and we can’t process that file.

Nothing to preview

@HaloFour
HaloFour commented 2 hours ago

@CyrusNajmabadi

At least with private protected you guys engaged the general public via SurveyMonkey. Now whether or not that can be considered a good or scientific survey is certainly up for debate.

@CyrusNajmabadi
Collaborator

By the time it was mentioned here it was too late. I do think that the LDM did that on purpose, knowing full well the reaction that would result.

No. We simply did things like we always do. We go to meetings and take the issues issues we feel are important and we discuss them. If we feel like we're lacking enough information to make a decision, we go out and do investigation and other activities to help drive the discussion. We keep doing that until we feel like there's enough information to make a decision and then we make a decision. We don't sit there nefariously going "let's purposefully do things late jut to get a reaction from teh guys on github! mwuhahaha" :D

In this case, we had an original design, and we got feedback taht there were issues with it. We examined and investigated many different options and we landed on a solution we thought was better. We felt like it was the right call and there was ample runway to see through to C# 7. So we decided to do it.

with zero visibility

I agree that currently there is little visibility onto our discussions. That's because we love just getting together in a room and hashing things out. And while we keep notes, and do publish them, they lack the visibility of a full conversation happening over a public text medium.

I'm sorry about that. We're human. We like personal interaction. We've been doing language design this way, over many languages for man-decades. Changing that is going to take a lot of time and effort, and it's not going to come easily. And even if we do majorly change things, it's likely not going to change the fact that i still sit next to someone and may have a deep discussion about a topic that never gets any public visibility.

or transparency.

I disagree. I think i've been very transparent on this topic. I've laid out the thinking here. I've explained my own positions, the positions of others, and the set of criteria that the team was trying to balance. I've explained the issues that i personally agree with and those i don't agree with. I've given hours of time, and umpteen thousands of words to give as much information as possible on this topic. Precisely because I think this communication is so valuable and i want to be able to continue this with all of you in the future.

I'm sorry that we're not as transparent as you would like. We're learning. Remember that we're at the bleeding edge of this sort of thing at MS, and we're also a team that just was used to doing things the MS way for decades. We're human. We're slow to change. But we're constantly striving to do better. Please accept that that's a process and that, no matter what, we're never going to be perfect at this.

@DavidArno

@CyrusNajmabadi

To you. Not to others. We've covered this before. Both introduce variables. As such, some people want things be consistent in terms of how they introduce those variables (i.e. which scopes they go into).

No, it's not a case of "To [me]. Not to others". This isn't my opinion; it's a factual observation on the language design. "some people want things be consistent in terms of how they introduce those variables (i.e. which scopes they go into)" and since the scopes are different (guaranteed readable in the outer block for out var, not so for is var) then these people haven't got what they wanted.