Closed corliss closed 6 years ago
I know that keywords over symbols is a tenet of C# design. However, perhaps it is worth questioning that in this case.
No, I don't think it is. Sure, if you're more familiar with Go then Go-syntax would be easier for you to digest. But would you argue that Go should adopt C# syntax so that Go can be adopted easier by C# programmers? No, you wouldn't. C# shouldn't try to syntactically be every other language.
And I personally find the Go syntax pretty ugly. A simple and obvious declaration has been replaced by a jumble of punctuation. It also removes the one most important element from focus, namely that this is a variable declaration.
You are allowed to have an opinion, which is why you can create proposals here. But I am also allowed to have an opinion, which is why I can comment on it. 😀
Geez, no need to close the issue. I'm just posting my opinion. It's not up to me whether such a feature would be implemented or not.
Reopening. There is no reason for us to not consider this.
--
Can we please bit an end to the back/forth about this and keep all future conversation about the appropriateness of this feature to the C# language. :) Thanks!
Personally, while i'm a fan of brevity, this PR does have some concerns for me.
First, having more ways to do things is not really a virtue in my mind. At least, not when there aren't huge benefits to the proposal. Here, i'm seeing only minor benefits.
Second, this would dramatically affect the IDE experience. Effective any identifier following a statement could be the start of a declaration. We would ahve to massively curtail what intellisense was allowed to do here because we'd never know if you were declaring a new variable, or referencing an existing symbol. That alone would make me very cautious. We've had similar issues int he past with lambdas. But there the brevity was so important (and we could effectively detect the situations where it would happen) that we accepted it. Here, neither of those apply.
However, even if intellisense optimistically assumed that it was an existing symbol, e.g. an autocomplete suggestion, that might be ok. Can you think of an intellisense example that would confuse the user?
It would definitely be a problem. Consider something as simple as this:
i<space_key>
in this case, the user might be wanting to complete out to int<space>
, or they may want to write i := ...
Because this point is both a declaration and reference point we would have to not allow intellisense to be committed automatically here. That would def degrade the experience from today when users can expect that this is only a reference location, and thus they can type a portion of the intellisense item, hit a character (like space or dot) and have the right item auto-commit.
That would only occur when the entire variable name is the prefix of an existing symbol.
No. it also happens if the name if a sub-portoin of an existing symbol. Or if the existing name camel-case matches. And, in the future, it will happen with our better fuzzy-matching logic that detects when you might be mispelling something.
Also, this is exceedingly common. Names are heavily reused by people. For example:
var list
var button
var token
etc. etc. these names are commonly equal to (case-insensitively) or part of an existing larger name in scope.
If the auto-commit were allowed, Ctrl+Z is always available to undo the auto-commit.
And this would be a pretty terrible experience. Imagine a person that only wants to use :=. Effectively, much of the time they'd be having to ctrl-z what the IDE was doing. That's not a good experience.
Are there other cases in the product at present where the user needs to undo this way?
Basically, no. And that's purposeful. As we've designed the language, we've taken the editing and IDE scenarios into account. One of the few places we have this is with lambdas. After all, when you type:
Foo(c
, then 'c' might be the start of a expression that references existing symbols, or it might be the start of a lambda like so Foo(c => c.Age > 21)
.
However, we do work to at least detect this by seeing if Foo
at least binds, and if it takes an argument that is a delegate. In that case, we do change intellisense behavior. However, importantly, we do not regress the common case here. We only tweak behavior when we have the data to strongly indicate the user is likely typing a lambda. No such heuristic exists with your proposal. literally the start of any statement could now be an expression that is referencing symbols, or it could be the declaration of a variable. There is no way to tell. There is no surrounding context we can use to limit things.
@CyrusNajmabadi @corliss I have a particularly relevant demo of what IntelliSense would be able to do in such a scenario as part of this video (starting at the 5 minute mark and going to the end). My experience found the following:
That said, the following come to mind as well:
var
) for type inference scenarios:=
(First, i did watch your video. Don't want you to think i'm jumping ahead or anything :))
The part i'm skeptical about is '2'.
We have a lot of users, and we get immediate complaints when things change and muscle memory is broken. The model you've presented is very similar to what we did for lambdas. i.e. the item is 'soft selected', and only the <tab>
commit character forces commit. However, this would be taking that model and enforcing it in a super common location which feels like it would really step back the experience.
Note: i am agreeing this is a possible mitigation. However, as i stated originally, it seems like a large take-back for a feature of very marginal benefit.
@CyrusNajmabadi To be clear, what I meant is it's easy (predictable) for a user to know where to expect the suggestion mode to appear, even if they aren't thinking about it. But it does appear a lot.
On the issue as a whole, I would say the approach led to a dramatic productivity improvement for developers working in Go, but not so much that it made the editor experience as a whole more productive compared to C#. The IntelliSense drawback is very real, very relevant, and probably a primary motivating factor for staying away from this syntax. It's just not quite so severe as one would guess at first look.
C# has already made one-line functions terse with => expression syntax, and this proposal will hopefully make the most common type of function - medium sized ones - easier to read.
I'm also skeptical about this point. One of the benefits of a keyword-oriented language approach is that is scans incredibly simply from a left-to-right perspective. That's a reason i actually dislike C# with some of our syntax (since we share too much left-side syntax with things like modifiers). 'var' actually improved readability of C# quite a bit as one had clear syntax indicating a variable was being introduced.
Brevity and terseness of expressoins/statements come with varying levels of tradeoffs. I'm very familiar with languages like go/swift/f#/etc. and they all attempt to strike a balance here between these factors. C# has chosen a balance that has err'ed on not overly preferring brevity at all cost. You can see that with our list-comprehensions and many other language features. Indeed, this has been so ingrained, that we have nearly no cases where this left-edge ambiguity exists and we have pushed against it unless we've felt the value is super high. Lambdas warranted it. But a '2-4 character saving' for variables likely does not.
may be easier for the compiler to scan
I was speaking solely from the perspective of user readability. The initial token tells you immediatley what you're looking at. You don't need to go forward to see "is this 'foo' a declaration or a reference?"
but I'd say that foo := bar is hard to argue with in terms of readability.
It's very easy to argue. The argument is simple, one form tells you what it is on the very first token, and the very first piece of non-whitespace. The other form forces you to look past the first token (and possibly past some long sequence of tuple-variables) to determine what it is you're looking at. :)
and the operator itself is mostly unspoken in the mind.
The operator must be understood to know what's going on. In both cases you're creating a variable with a name and assigning a value to it.
The operator is of utmost important in the following:
class C {
int foo;
public void M1() {
foo = 123;
}
public void M2() {
foo := 123;
}
}
Ignoring existing users' preferences and learned behavior, the main readability case that puts :=
in C# at a direct disadvantage compared to :=
in Go is the fact that there would be two common ways to declare a variable with type inference instead of just one. Lack of a type (or var
) on the left side of an identifier would no longer mean that the item is a reference. This could get pretty awful, e.g. if one developer on a project prefers (or has long experience with) var
while another on the same project prefers :=
.
I find that its easier to scan for the "meaning" of things on the left side than the right, since the left edge is the only location in C# where one can reasonably expect to find consistent vertical alignment. The gofmt tool provides this expectation in more places in Go code, with an emphasis on ensuring the :=
is readable.
I disagree with that assertion. I don't see how var x = 123;
is any harder to parse than x := 123;
. If anything the latter makes you take pause to ensure that what you're looking at is a declaration vs. an assignment. I have no problem mentally parsing and understanding the current implicit declaration form. I know right away the most important aspect of the statement; that it is a declaration of a new variable.
We already have two common ways
Not "with type inference".
and the former is just understood to be an archaism.
This is not true.
I assume you mean the cases where you need to explicitly state the type
Yes. It is not considered an archaism. Full stop. Indeed, there are full teams (like the .net team and the roslyn compiler team) that have this as an actual rule they are required to follow.
Why are we even arguing about this then?
You filed the request. I'm simply giving you my perspective as one of the language designers about how i feel about the proposal and the specific things i'm looking at when i judge it.
@corliss
To avoid understanding that the form "x = y" has a special quality that has made it the core representation of mathematics
Can you elaborate on this special quality? all I see is an assignment in the context of some programming languages which is actually an equality in mathematics but what makes it special?
is to miss the point.
The point is not being missed. Trust me, the designers here are well aware of the wealth of syntactic choices we have, and hte designs taken by many other languages. What i'm trying to explain is that there are many concerns on the table that we consider on top of that and we have to evaluate the proposal in that context.
For example, we must take into account the fact that C# is not being sprung into the world as a V1 language. Any language changes must address how those changes fit into the existing language and the wealth of code that already exists. Any feature that provides an equivalent way to do something must justify that it provides enough benefit to warrant the potential bifurcation of syntax.
Another, common, way to think about is this: All language features start out with a huge net negative score. That's the cost they have to the entire ecosystem for people to learn it. For the tools to adopt it. To deal with the confusion of having multiple ways to do things. Etc. etc. In order to do a feature the value has to be so much that it outweighs that net negative scope. Right now, the only benefit is the perceived slightly better readability. That's incredibly marginal and really hard to justify.
@corliss Yeah.. interesting, indeed.
It's inconsistent with what I would expect of C# syntax
When I see this, and if I knew nothing about it name := 5; My mind would jump to this name = name : 5;
Like how this name += 5; is the same as this name = name + 5;
imo this is pretty bad. its difficult to spot in code as in c# variable declarations ALWAYS specify the variable name before the name and then one variable type isnt defined that way for some reason
I'm going to cast a contrary +1 for this. I actually quite like the syntax. However, I accept that, based on the overwhelming number of -1's and @CyrusNajmabadi's point that it would play havoc with intellisense, it isn't ever going to happen.
I will add though that rather than replacing var
, I'd see it as an alternative to let
/readonly
, but that's a different proposal, I guess. 😸
May we have this in "when heck freezes over" milestone?
@DavidArno The syntax is indeed nice but I don't know why people think that this is readable:
y := 1;
x := y;
x += 1;
@eyalsk,
I agree, x += 1;
makes me cry, filling my eyes with tears, and thus making it hard to read. 😝
@CyrusNajmabadi
Reopening. There is no reason for us to not consider this.
I assumed there is a limitation to how invasive you are willing to be with the established syntax. Could moving type information to the right side in every C# language construct be realistically considered as well? I've heard it provides many syntactic benefits for both developers and language designers (hence Rust/Scala/Swift/Nemerle all doing it).
const int x = 3;
static int foo (int x, string y)
{ ... }
vs
const x : int = 3;
static foo (x : int, y : string) : int
{ ... }
Please let me know if I should open a separate issue for this.
@DavidArno
I'm going to cast a contrary +1 for this.
Bet no one saw that coming or anything ;-)
I've heard it provides many syntactic benefits for both developers and language designers
@dsaf So I think JS actually did it right long time ago. No type annotation to be worry about.
@alrz (not sure if you are joking) whatever was done in JS was driven by lack of time and lack of types. The most promising fix of JS - Typescript - puts optional typing on the right side (wouldn't be surprised to see this in ES8/9). Just consider stopping and seriously considering such syntax for a minute.
I assumed there is a limitation to how invasive you are willing to be with the established syntax.
True. But I don't think any reasonable proposal should be thrown out immediately without at least discussing and laying out the pros/cons. At the very least, it helps clarify some of the decision making that some LDM members make when considering this sort of thing.
--
I will point out that, in general, we're cautious about adopting new syntax. That's especially true if it's syntax to do the same thing as what we already have. Indeed, in C# we've rarely ever done that. Generally, new syntax comes either from:
delegate (c) { return c.Age > 21; }
instead of c => c.Age > 21
.We don't take new syntax for existing features "just because" or just if is slightly better than what we have. New syntax really needs to carry it's own weight and truly justify its addition to the language.
In my personal opinion, :=
doesn't come close. It's only marginally better than what we have, and comes with definite baggage that we'd be concerned about.
--
Could moving type information to the right side in every C# language construct be realistically considered as well?
Sure! But, again, would it have enough value to warrant it. I do want to reiterate though that C# versions are not designed in a vacuum. In other words, we don't design saying "man... if we could do it all over again, here's what we would do". We design given the language as it has evolved so far, and given the literal billions of lines of code out there. Massively upending a core choice the language has made can be incredibly disruptive and would really need to be super valuable to warrant it.
Syntax changes for the sake of syntax changes can rarely justify themselves. Again, AFAICT, we've only done it once. In order for syntax changes to be justified, they really have to enable something amazing, or they must truly and dramatically improve things. In general, that's extremely rare.
Also, please note that i'm not at all trying to rain on anyone's parade. Man do i love syntax. When we do work on syntax in the LDM we pore over every detail. We nitpick. We bikeshed. We feel the pain intimately when we see the problems with it.
But, i just need to make it clear that C# is not a language that embraces routine syntax change. We want every step of the language to feel like a natural evolution. And we want all that learning and muscle memory, and pattern recognition that happens in your brain, to still be just as relevant today as it was in C# 1. Indeed, C# 1 should look extremely familiar and normal to someone using C# 6. today. Yes, there will be places where things can be dramatically nicer today. But nearly all of it should still feel like it is C#.
The more we lurch around with syntax, the more that C# feels like a target you're always chasing. One that you're never quite sure you can trust or invest in. If we add :=
we'll have vast swaths of people never using it. And we'll have people who will always be concerned "should i even use this? or are they jsut going to come up with something later (maybe <-
) to just replace this in the future?"
--
So, to reiterate: Proposing syntax: great! Thinking and discussing syntax: great! But don't be too disappointed if the way we develop the language means that many/most of the syntax proposals don't happen.
So, to reiterate: Proposing syntax: great! Thinking and discussing syntax: great! But don't be too disappointed if the way we develop the language means that many/most of the syntax proposals don't happen.
The best way to get syntax changes is to really be able to demonstrate the value, or to attach to new valuable features that warrant addition to the language. We can then judge if there is enough merit. For example, when you have C# 1, you'll see this all over the place:
private readonly int _age;
public int Age
{
get
{
return _age;
}
}
Even reducing the formatting still gives you:
private readonly int _age;
public int Age { get { return _age; } }
This is (what i like to call) "token salad". The intent of the code is so masked by all the pomp and circumstance of the language. Compare to current versions of C# which can give you either:
private readonly int _age;
public int Age => _age;
or even
public int Age { get; }
That's 8 lines to 1, and that's 16 tokens to 7, and that's 50chars to 23. Here this was just a matter of syntax, but it really pulled it's weight in terms of cleanup and clarity. And, importantly, it felt like a natural evolution of the language. We didn't replace existing properties with something like:
Age := public int
We could have. It's shorter, and uses less tokens. But it would not feel like something that naturally evolved from the existing language. These are very important things to consider with syntax. And they're more important the longer the language has been around.
If i had the chance to do it all over again, much would be different in C#. But we're working on C#7. Not Cyrus#1. :)
@CyrusNajmabadi,
But don't be too disappointed if the way we develop the language means that many/most of the syntax proposals don't happen.
As long as the match
keyword - with accompanying pattern matching expressions (and ADT's and records of course) - appears in v7.1, I'm sure most of us will forgive the LDM rejecting almost all other syntax change requests 😉
@CyrusNajmabadi Thanks, your answer should be pinned to homepage or something :).
I wish Microsoft Research had a team developing a research C# clone with this motto, though:
"if we could do it all over again, here's what we would do"
As long as the match keyword ... appears in v7.1
If it makes you feel better, i'm pretty sure there is near unanimous support for a good, expression based, approach to pattern matching and whatnot. I can't promise anything, and i certainly can't promise a release vehicle. But i can say that it's a prime focus and we're definitely enthusiastic about working on it.
We met yesterday to deal with some of the awfulness that comes about from 'switch', and i think most people felt that life would be so much nicer and we would be able to address so many issues cleanly if we just had a new, focused, syntax for these scenarios that fit far better into the style that so many have adopted for functional/pattern-matching language constructs.
--
My personal hope is that sooner rather than later, we'll be able to put these features back on the table (like ADTs, expression matching, recursive patterns, etc.) and i hope that they'd make it to you in a timely fashion. All these hopes are my own. No promises or anything beyond that :)
@CyrusNajmabadi So this is what C# really stands for Cyrus#! 😆
And i'm still trying to squeeze in the "Naj" (nudge) operator ~>
into the language somehow...
In exactly the same way, if := were implemented, then a choice for i := could appear in the intellisense list. The order doesn't matter, and the user gets to choose whether to accept the default, change it, or temporarily Escape out of intellisense.
You have now broken muscle memory. I want tok<space>
to continue completing to token
for me. It now would not do that.
What you are describing is precisely what we have for the lambda ambiguity case. However, now you're making teh start of every statement ambiguous. With lambdas we could accept the intellisense pain because the value of hte feature was so high, and because we could limit the places where we would need to do this. With your suggestion we would have to vastly extend this sort of case to now impact the common typing cases.
Or, in other words, our heuristic actually works well for lambdas, as much of the time a person can type a delegate, they will end up writing a lambda. So the degradation isn't so bad. The same is not true for the start of a statement. Massive amounts of stated start with a reference to a symbol or a keyword. You are now making it so that we cannot confidently select and insert those automatically because of hte ambiguity.
I'm not arguing that there's no solution. I'm arguing that the solution is a serious step back in experience, and dramatically will impact muscle memory and user satisfaction moving forward.
--
This will also especially affect users using multiple versions of VS. People do commonly use multiple versions, and they will now have a dramatically different typing experience across versions. That's not something that can be done lightly, and not for a feature that isn't significantly valuable to warrant it.
@corliss
The difference being that instead of there being a list of reasonable identifiers/keywords that the Intellisense must now assume that literally anything already typed could also be a newly declared identifier. In order to not break existing muscle memory Intellisense must always assume that even if the current token might be a new identifier that it's not going to be. That would make the experience pretty awful for the people who might want to adopt the new syntax.
If the user wants := they select that from the list or hit Escape.
You're now making it so that people have to take extra steps to get the syntax you want. That's an extra burden on them and will make the typing experience less pleasant.
I mean... how good will you feel about having to type, for every new variable, tok<down><down><down><down><space>
or tok<esc>
? :)
We design our language with teh consideration of IDE scenarios in mind, and with the known expectations people have about the tooling we provide. That was a core reason, for example, that our linq-query syntax developed like it did. We have from <id> in <expr>
for a very specific reason. So that the variables will be known before they are used later in the query. That's why we did not go with traditional SQL syntax (where the from-clause is at the end) because it would interfere greatly in an environment where the tooling is actively assisting the user.
Also please don't change a single
to 4 's.
Why not? There may be 4 items that match 'tok' that would be listed above tok :=
.
Intentional exaggeration is not something I want to defend against.
It's not intentional exaggeration. It's trying to realistically assess what the experience could be like for users.
I'm not bringing up this argument haphazardly. I'm pointing out explicitly the complication that your feature is adding to the language. You are introducing syntax which introduces a left-ambiguity between a word being a keyword, symbol reference, or symbol declaration. That is a something we have experience with and something which can be quite problematic to the IntelliSense experience.
Again, you have to think in terms of muscle memory. Muscle memory means that people have expectations that what they type will produce the same results. Tweaking that is something you need to be extremely careful around.
We also need to consider the mainline case for both styles of users. Let's say we have a user who really likes :=
. They now potentially have to take this extra step on every declaration they type. And that extra step may rquire keys that are quite annoying for typing**.
**This is not hypothetical BTW. On my keyboard, typing arrows or escape are both extremely unpleasant. They take me out of my core keyboard space and really interfere with smooth typing.
--
My overall point is that these issues must be considered. Your proposal puts forth :=
almost entirely from a readability perspective. As someone working on the tooling, i have to strongly consider the perspective of how conducive this is for actual writing of code in the context of the tools we already have.
As a data point: We introduced a bug that affected how enums were preselected automatically when typing. The impact on the team was incredibly annoying. And, unfortunately, before we fixed it, it escaped into the wild. We got tons of feedback from people who were incredibly thrown off by what was happening.
These people could work around the issue by using the arrow keys, or 'esc', but such typign patterns would be substantially aggravating to them.
(after all the user doesn't memorize list entries from one version of Visual Studio to the next!)
Of course they do. Even i do this :)
That's what muscle-memory is. You get used to interacting with the tooling in a certain way, to the point that you're just doing it automatically.
@corliss I'm not jumping the gun. I do understand what you're saying. And i'm telling you from 15+ years of direct experience on this topic **, that what you said is incorrect. People, including myself, do memorize list entries, and they do build up an automatic muscle memory expectation of where things are.
We have screwed this up in this past, and we do hear about it almost immediately.
--
** Some background: I've worked on and have owned IntelliSense and the editing experience here since i started working at MS. I've worked on several rewrites of these systems, and i can tell you that even incredibly subtle changes are felt by our users. Sometimes we must make the change (like with lambdas), or we feel like there is substantial benefit to a change, even if it means negatively affecting so many people when we do it. But, like with a language change, there is an enormously high bar to cross to warrant it.
Look, if you don't want to do this proposal
I am simply providing you with my full opinion on hte subject. As someone who files suggestions and issues myself, i do not want someone haphazardly closing things with little justification or explanation of the decision. I'd rather try to explain the concern fully versus leaving questions out there.
Now, it's clear, you don't necessarily agree with me on my assessment about the potential costs of this approach. i.e. " I'm personally satisfied that there isn't much of an impact on intellisense, and have no more to say." That's totally fine. But to me, having investigated this space from tons of different directions, i'm definitely not in that state. I have deep concerns and reservations about this. And that's ok :)
but jumping around reactively is painful.
I think you are bringing you own preconceived notions here. This is my job, and i would be remiss if i did not put my full attention and care into assessing the potential downsides of these suggestions. I'm trying to condense out everything we've learned over decades of doing this into a few paragraphs to help provide insight on the scenario. Invariably that sort of communication is going to be problematic. There is so much information to convey, and it's not going to be suitable to spend days crafting an entire response that covers every aspect in depth. For now, i may condense down years of learning, into a few sentences. If you want to know more about it, i'm happy to expand. But you're going to get what i think is an acceptable response as it best pertains to what has been stated so far :)
The title says it all...