Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

C# Design Notes for Jul 13, 2016 #13015

Closed
MadsTorgersen opened this issue Aug 8, 2016 · 66 comments
Closed

C# Design Notes for Jul 13, 2016 #13015

MadsTorgersen opened this issue Aug 8, 2016 · 66 comments

Comments

@MadsTorgersen
Copy link
Contributor

C# Language Design Notes for Jul 13, 2016

Agenda

We resolved a number of questions related to tuples and deconstruction, and one around equality of floating point values in pattern matching.

Handling conflicting element names across type declarations

For tuple element names occurring in partial type declarations, we will require the names to be the same.

partial class C : IEnumerable<(string name, int age)> { ... }
partial class C : IEnumerable<(string fullname, int)> { ... } // error: names must be specified and the same

For tuple element names in overridden signatures, and when identity convertible interfaces conflict, there are two camps:

  1. Strict: the clashes are disallowed, on the grounds that they probably represent programmer mistakes
    • when overriding or implementing a method, tuple element names in parameter and return types must be preserved
    • it is an error for the same generic interface to be inherited/implemented twice with identity convertible type arguments that have conflicting tuple element names
  2. Loose: the clashes are resolved, on the grounds that we shouldn't (and don't otherwise) dictate such things (e.g. with parameter names)
    • when overriding or implementing a method, different tuple element names can be used, and on usage the ones from the most derived statically known type win, similar to parameter names
    • when interfaces with different tuple element names coincide, the conflicting names are elided, similar to best common type.
interface I1 : IEnumerable<(int a, int b)> {}
interface I2 : IEnumerable<(int c, int d)> {}
interface I3 : I1, I2 {} // what comes out when you enumerate?
class C : I1 { public IEnumerator<(int e, int f)> GetEnumerator() {} } // what comes out when you enumerate?

We'll go with the strict approach, barring any challenges we find with it. We think helping folks stay on the straight and narrow here is the most helpful. If we discover that this is prohibitive for important scenarios we haven't though of, it will be possible to loosen the rules in later releases.

Deconstruction of tuple literals

Should it be possible to deconstruct tuple literals directly, even if they don't have a "natural" type?

(string x, byte y, var z) = (null, 1, 2);
(string x, byte y) t = (null, 1);

Intuitively the former should work just like the latter, with the added ability to handle point-wise var inference.

It should also work for deconstructing assignments:

string x;
byte y;

(x, y) = (null, 1);
(x, y) = (y, x); // swap!

It should all work. Even though there never observably is a physical tuple in existence (it can be thought of as a series of point-wise assignments), semantics should correspond to introducing a fake tuple type, then imposing it on the RHS.

This means that the evaluation order is "breadth first":

  1. Evaluate the LHS. That means evaluate each of the expressions inside of it one by one, left to right, to yield side effects and establish a storage location for each.
  2. Evaluate the RHS. That means evaluate each of the expressions inside of it one by one, left to right to yield side effects
  3. Convert each of the RHS expressions to the LHS types expected, one by one, left to right
  4. Assign each of the conversion results from 3 to the storage locations found in 1.

This approach ensures that you can use the feature for swapping variables (x, y) = (y, x);!

var in tuple types?

(var x, var y) = GetTuple(); // works
(var x, var y) t = GetTuple(): // should it work?

No. We will keep var as a thing to introduce local variables only, not members, elements or otherwise. For now at least.

Void as result of deconstructing assignment?

We decided that deconstructing assignment should still be an expression. As a stop gap we said that its type could be void. This still grammatically allows code like this:

for (... ;; (current, next) = (next, next.Next)) { ... }

We'd like the result of such a deconstructing assignment to be a tuple, not void. This feels like a compatible change we can make later, and we are open to it not making it into C# 7.0, but longer term we think that the result of a deconstructing assignment should be a tuple. Of course a compiler should feel free to not actually construct that tuple in the overwhelming majority of cases where the result of the assignment expression is not used.

The normal semantics of assignment is that the result is the value of the LHS after assignment. With this in mind we will interpret the deconstruction in the LHS as a tuple: it will have the values and types of each of the variables in the LHS. It will not have element names. (If that is important, we could add a syntax for that later, but we don't think it is).

Deconstruction as conversion and vice versa

Deconstruction and conversion are similar in some ways - deconstruction feels a bit like a conversion to a tuple. Should those be unified somehow?

We think no. the existence of a Deconstruct method should not imply conversion: implicit conversion should always be explicitly specified, because it comes with so many implications.

We could consider letting user defined implicit conversion imply Deconstruct. It leads to some convenience, but makes for a less clean correspondence with consumption code.

Let's keep it separate. If you want a type to be both deconstructible and convertible to tuple, you need to specify both.

Anonymous types

Should they implement Deconstruct and ITuple, and be convertible to tuples?

No. There are no really valuable scenarios for moving them forward. Wherever that may seem desirable, it seems tuples themselves would be a better solution.

Wildcards in deconstruction

We should allow deconstruction to feature wildcards, so you don't need to specify dummy variables.

The syntax for a wildcard is *. This is an independent feature, and we realize it may be bumped to post 7.0.

compound assignment with distributive semantics

pair += (1, 2);

No.

Switch on double

What equality should we use when switching on floats and doubles?

  • We could use == - then case NaN wouldn't match anything.
  • We could use .Equals, which is similar except treating NaNs as equal.

The former struggles with "at what static type"? The latter is defined independently of that. The former would equate 1 and 1.0, as well as byte 1 and int 1 (if applied to non-floating types as well). The latter won't.

With the latter we'd feel free to optimize the boxing and call of Equals away with knowledge of the semantics.

Let's do .Equals.

@HaloFour
Copy link

HaloFour commented Aug 8, 2016

🎉 Design notes!

@paulomorgado
Copy link

Will this be allowed?

partial class C : IEnumerable<(string, int)> { ... }
partial class C : IEnumerable<(string, int)> { ... }

@MadsTorgersen
Copy link
Contributor Author

@paulomorgado : Yes. This is just about what to do when parts disagree on tuple element names.

@svick
Copy link
Contributor

svick commented Aug 9, 2016

Anonymous types

Should they implement Deconstruct and ITuple, and be convertible to tuples?

No. There are no really valuable scenarios for moving them forward. Wherever that may seem desirable, it seems tuples themselves would be a better solution.

What about LINQ query syntax? That will presumably keep using anonymous types (to maintain compatibility) and some LINQ provider might appreciate ITuple.

@MgSam
Copy link

MgSam commented Aug 9, 2016

Agree with @svick. LINQ uses anonymous types, seems like there's a lot of backwards compatibility to think of here.

I also don't think Tuples are always the hands down better solution either; the BCL team made Tuple a reference type to optimize for cases when it has lots of properties. Seems like reference types (a la anonymous types) still have a place.

@HaloFour
Copy link

HaloFour commented Aug 9, 2016

@MgSam @svick

They could emit tuples for LINQ to objects but continue to emit anonymous types for queryable expression trees. That would solve compatibility issues but still provide better performance for in-memory operations.

This would only apply to those query clauses that automatically involve a projection, like let. Obviously manual use of anonymous types for projections would be unaffected.

@paulomorgado
Copy link

Queriables might rely on the property names.

Although, other than the generated query, I wonder if that would make any difference.

@timgoodman
Copy link

What's the reasoning behind not allowing compound assignment with distributive semantics?

@gafter
Copy link
Member

gafter commented Aug 11, 2016

@timgoodman We don't need a reason for each of the billions of things we do not do. Rather we choose to do things on the basis of having a good reason to do them, and it being of sufficiently high customer value for the effort.

@timgoodman
Copy link

@gafter That's fair. I just figured that since it's part of the meeting notes, that there may have been some discussion beyond just "No".

@psylenced
Copy link

Just repeating my comment on the C# 7.0 blog post regarding wildcards:

Wild cards using a * look a little off to me.

Is there any reason why null is not used instead?

p.GetCoordinates(out int x, out int y); // Don’t ignore
p.GetCoordinates(out int x, null); // Ignore return
p.GetCoordinates(out int x, out null); // Alternative ignore return

And in the cases where there are overloads:

p.GetCoordinates(out int x, (int) null); // Ignore return for overloads
p.GetCoordinates(out int x, out (int) null); // Alternative ignore return for overloads

Similarly:

(var myX, *) = GetPoint();
(var myX, null) = GetPoint(); // Ignore

@jcansdale
Copy link

Couldn't we simply omit the name?

p.GetCoordinates(out int x, out); // No overloads
p.GetCoordinates(out int x, out int); // For overloads

I'm sure I'm missing something. 😉

@jnm2
Copy link
Contributor

jnm2 commented Aug 25, 2016

Fwiw I dislike the star and any keyword would make me happier. * sounds more like saying "anything" rather than saying "nothing" which is the gist of what it's supposed to be indicating.

@HaloFour
Copy link

Note that wildcards would extend beyond out parameters and tuple deconstruction. This is entering the realm of pattern matching. Something is required to denote that the consumer doesn't care what the result is. Given that the typical function programming language wildcard _ is already a valid identifier in C# then * is as good a placeholder as any. null is already a pattern, and it wouldn't make sense for an omission of a pattern to be a wildcard pattern.

@jnm2
Copy link
Contributor

jnm2 commented Aug 25, 2016

@HaloFour so when I see * the gist is "don't care." I can live with that. * signifies irrelevance.

null or void could also be seen to have an element of shifting the focus away from marking data as irrelevant in a declarative sense to thinking about what is being done with the variable holding the data in a more implementation-detail sense. That might be a bit of a reach.

@akrantz
Copy link

akrantz commented Aug 25, 2016

To reiterate what @jnm2 said above, * seems to me to be inclusive of everything (as in wildcard matching) and does not mean that to ignore something. I would prefer something else be used. Could ... or another symbol be a possibility? Even something more obscure like ~ would seem better to me.

@HaloFour
Copy link

@akrantz

To reiterate what @jnm2 said above, * seems to me to be inclusive of everything (as in wildcard matching) and does not mean that to ignore something.

That is exactly how the wildcard pattern works in functional languages. It can be used where you don't care about the result, for example:

let (a, _) = tuple // don't care about the second tuple element

These changes around tuples, deconstruction, type-switch and out parameters are all leading towards a much larger adoption of functional paradigms in the C# language. Function arguments are often compared to tuples so the application of deconstruction concepts and syntax is pretty consistent, in my opinion.

@jnm2
Copy link
Contributor

jnm2 commented Aug 26, 2016

@HaloFour Not to switch viewpoints or anything, but we do have the existing paradigm of optional parameters where the placeholders are simply omitted. Why not consider out parameters as optional and follow the same style? It would also apply to the tuple example since tuples are the moral equivalent of parameter lists.

@HaloFour
Copy link

@jnm2 That we do, but optional nature of the parameters are opt-in by the callee, not the caller. To have C# automatically start treating all out parameters as optional would very likely be a breaking change.

@psylenced
Copy link

@jnm2 that doesn't handle the case where the "don't care" or optional parameter is in the start or middle and not the end.

Also I'm agreeing with others above, that's why I prefer null as it means "nothing" rather than * which means "all" or "everything".

@HaloFour
Copy link

@Chris-GH

* doesn't mean "all" or "everything", it means "anything", up to and including "nothing".

null is already a pattern that will ship in C# 7.0. It very explicitly means "nothing" and nothing else.

@jnm2
Copy link
Contributor

jnm2 commented Aug 26, 2016

@Chris-GH Actually what I'm saying is that it does handle the start and middle cases via the comma, just like argument lists: let (, a) = tuple and int.TryParse(str, ). Not that I think this is better than *.

Also Chris, I understand what HaloFour is saying. Don't think of * as indicating wildcard/any. Think of it as indicating irrelevant.

@alrz
Copy link
Contributor

alrz commented Aug 26, 2016

now pattern matching is getting irrelevant.

@jstun
Copy link

jstun commented Aug 30, 2016

I think using a keyword/identifier instead of * for wildcard will work better. For example,

Instead of
(var myX, *) = GetPoint();

would be
(var myX, ignore) = GetPoint();

in this case the keyword/identifier ignore is used to indicate that the developer is not interested in the myX parameter.

Edit: I used the term keyword initially but as orthoxerox mentioned this can be an identifier. I'm fine with however we want to classify the term 'ignore'. My point is using it makes it clearer that the developer is not interested in the parameter value.

@orthoxerox
Copy link
Contributor

@jstun What makes ignore a keyword? It's a regular identifier. See https://msdn.microsoft.com/en-us/library/x53a06bb.aspx

@dcmeglio
Copy link

dcmeglio commented Sep 2, 2016

@jstun the point @orthoxerox was making wasn't about the term you used, it was an explanation of why your idea won't work. ignore is perfectly valid as an identifier, as in bool ignore = true; You can't suddenly tell the world "you can't use ignore as a variable name anymore" without there being ramifications. I am sure most of us have seen a line of code like bool ignore = true; in large applications (I just ran a search in one large application I already have open in VS and found 8 such variables). Your suggestion would prevent them from compiling anymore.

It wasn't a matter of "classification", but rather explaining why it can't (or rather shouldn't) be done. On the other hand, you cannot have an identifier named * which is why that is safe.

@jstun
Copy link

jstun commented Sep 3, 2016

@dman2306 having * as a means for indicating you want to ignore a parameter is nonsense. Every developer is accustom to the * meaning any value, for example when searching someValue, means find me all that have someValue in the name. Now we're suggesting that * should be used to indicate ignore does not make sense to me and any other developer who pays attention to the code they write.

Please look at the meaning of contextual keywords. You can use @orthoxerox link as well.

ignore can be a contextual keyword in the example I provided.

(var myX, ignore) = GetPoint();

Notice how I'm using ignore as the only value for the second parameter. I didn't specify an identifier. The compiler can easily infer that the developer is requesting that the parameter be ignored, see how clear that is.

This will also satisfy those that would like to use ignore as an identifier.

bool ignore = true;

@HaloFour
Copy link

HaloFour commented Sep 3, 2016

@jstun

Deconstruction also uses the following syntax:

var (myX, ignore) = GetPoint();

or:

int myX, ignore;
(myX, ignore) = GetPoint();

or:

int ignore;
(var myX, ignore) = GetPoint();

It is completely impossible for the C# compiler to know if ignore is an identifier or some special keyword in these contexts. To reuse an identifier here is simply a bad idea.

@timgoodman
Copy link

In addition to the very reasonable objection that ignore is already a valid identifier, consider that much of the benefit of having a special syntax is to deemphasize to the reader the parts of the code which aren't important. A single character is more suitable for this purpose, and * is probably the best available character.

Some people might find it counterintuitive the first time they see it, but is anyone seriously going to be confused about it by the fifth time they see it? If not, this concern seems like much ado about (almost) nothing.

@stepanbenes
Copy link

Nobody mentioned * as already existing dereference operator in unsafe context. Is there really no potential ambiguity or confusion in this context?

@jstun
Copy link

jstun commented Sep 3, 2016

@HaloFour if deconstruction is using ignore as some sort of keyword then how can the compiler tell the difference when I declare a variable named ignore?

@timgoodman if there's a single character to emphasize ignore wouldn't a more suitable character be the bang (!) symbol.

(myX, !) = GetPoint();

@timgoodman
Copy link

timgoodman commented Sep 4, 2016

That said, regarding _ (which is really the only option I'd prefer to *, if it were available), I realize this would be parsed as an identifier, but how hard would it be to add a rule that an undeclared identifier used like a local variable assignment, or a local variable which is assigned to but never accessed, and whose name is _, is treated as "ignore this value"?

This seems like it'd be a non-breaking change, since the code either doesn't compile or essentially does nothing today. And it has the (small) advantage of matching the syntax that F# and other languages use for the ignored value.

I imagine that the compiler already optimizes away unaccessed local variables, so I guess the only change there is to supress the "_ is assigned but never accessed" warning on cases like
var (x, _) = GetTuple();

Of course you'd also need to prevent the error on cases like

(var x, _) = GetTuple();

But if that's hard to do or is a breaking change for reasons I'm not seeing, I personally don't mind *.

@timgoodman
Copy link

@stepanbenes I don't think the dereferencing operator could ever appear as the only token in the middle of either (, or ,, or ,) could it?

@HaloFour
Copy link

HaloFour commented Sep 4, 2016

@stepanbenes

In both cases where * can be used in an unsafe context you'd need to supply an identifier in order for that syntax to be valid. As a dereference operator the syntax would have to be * identifier and as a pointer type the syntax would have to be type * identifier. As such I believe that the compiler can correctly discern the context.

@jstun

It couldn't, which is why ignore doesn't make sense as a contextual keyword in deconstruction.

@timgoodman

It's been suggested but it suffers the same problem as ignore does in that the compiler could never really be able to tell if you meant it as an identifier or as a wildcard. Worse, it could lead to some error prone situations where a deconstruction inadvertently overwrites an existing identifier unexpectedly:

public class Foo {
    private int _;

    public void Bar() {
        int x = 123;
        // later
        (x, _) = GetPoint(); // oops, overwrote the field _
    }
}

@jnm2
Copy link
Contributor

jnm2 commented Sep 4, 2016

@HaloFour Mutable _ in a wider scope is almost never the right thing to do. One solution to that would be to force _ in deconstruction to always mean 'ignore' and if any identifier named _ is in range of the deconstruction you get a compiler error. I would actually really like that.

The experience is not that different from when you use _ for a short lambda and later come back and add a nested lambda and try to use _ again. You realize, oops, the outer _ is no longer an okay name because the scope isn't tiny anymore.

@AdamSpeight2008
Copy link
Contributor

AdamSpeight2008 commented Sep 4, 2016

@jnm _ isn't a valid identifier. (in VB)

Would it not be better to have something that can never be an identifier? eg Double Tilde ~~
which looks similar to a strikethrough identifier

(x,~~) = GetPoint();

If it consider different to an identifier, can also be highlighted differently in IDE. (eg greyed out)

@timgoodman
Copy link

timgoodman commented Sep 4, 2016

@HaloFour, well the compiler can tell if you never read the value from a variable, because it generates an "assigned to but never accessed" warning. So assuming it's a wildcard in that case seems harmless, even if for some odd reason your actual intent was to store the value in a variable and then never read it. I'm imagining this means initially parsing it as an identifier and only later saying "nope, it's a wildcard", and I really don't know if that would introduce some horrible complications.

If there are existing optimizations to remove unaccessed variables, then maybe its enough to treat an undeclared _ as var _, suppress the "was never accessed" warning, and then trust the compiler to get rid of it if you don't access it. That is, does the compiler even need to know there's such a thing as a wildcard? Or can it just say "It's an unacccessed local variable, so there's no point in ever putting it on the stack."

One downside of that is you'd no longer produce an error when someone meant to type an existing identifier and somehow wrote _ by mistake, but I'm not sure that's all that much more likely than typing * by mistake. Errors in a one-character name are pretty easy to spot. :)

And yes, as you point out, people could screw up if they forgot they had a variable named _ that was in scope and happened to have the same type as the tuple element. But doing a slightly worse job protecting the small subset of people who have variables named _ from introducing future errors seems a fairly minor thing. On the other hand, the benefit of _ over * also strikes me as fairly minor.

If this could be made to work for _ then it could be made to work for ignore, but I think a single character that at least doesn't look too much like a typical identifier is better here, and also I think a lot more people will have existing code such as bool ignore. I much prefer either * or _ to a longer keyword.

@jnm2
Copy link
Contributor

jnm2 commented Sep 4, 2016

@AdamSpeight2008

_ isn't a valid identifier. (in VB)

Even better. The compiler error preventing _ being used as an identifier when deconstruction is in scope would only have to be implemented for C#.

@HaloFour
Copy link

HaloFour commented Sep 4, 2016

@jnm2

Even better. The compiler error preventing _ being used as an identifier when deconstruction is in scope would only have to be implemented for C#.

The wildcard syntax/pattern is being considered for a number of other scenarios aside deconstruction, including currently legal syntax where _ would be a valid identifier. One of these scenarios is ignoring a lambda argument. It's currently not uncommon to see _ used where a lambda argument is to be ignored:

stuff.Select((_, index) => { ... });

or where the lambda is intended to represent a simple property accessor and the lambda argument itself is just noise:

stuff.OrderBy(_=>_.FirstName);

Using any legal identifier as the wildcard would immediately prevent supporting these scenarios with wildcards.

@jnm2
Copy link
Contributor

jnm2 commented Sep 4, 2016

@HaloFour That fits with what I'm saying. stuff.OrderBy(_=>_.FirstName); would be legal, stuff.OrderBy(_=>int.TryParse("3", _)); would be a compile error because the ignore symbol is used within the scope of an identifier named _.

That experience is similar to what happens with even more frequency today, where I'll have something like stuff.Where(_=>_.FirstName == x); and later come back and add stuff.Where(_=>_.FirstName == x && _.Items.Any(_=>_.IsActive));. I end up with a compiler warning because I tried to reuse the symbol _ for a second identifier. It's helpful too because I see that _ is no longer an okay name for the outer lambda parameter, since its scope is wider than it used to be and it needs an actual name for clarity.

I'd say the same experience would be nice for stuff.OrderBy(_=>int.TryParse(x, _));. Once you try to destructure ignore in the scope of an identifier named _, you're doing it wrong and you should give the identifier an actual name for clarity.

One of the benefits here is that it's completely backwards compatible with existing code.

*(Yes, I know out wildcards aren't destructuring, but it's the same type of scenario.)

@svick
Copy link
Contributor

svick commented Sep 4, 2016

@jnm2 So, with your proposal, there is no way to use wildcard as a lambda parameter and also inside the lambda? That doesn't sound like great design to me.

_ today is not a language feature, so it makes sense that it does not always works like a language feature that you're emulating using it would. But you're suggesting to make it actual language feature, that doesn't always work.

@HaloFour
Copy link

HaloFour commented Sep 4, 2016

@jnm2

I don't think you understood me. I'm talking specifically about scenarios where wildcards are being considered that don't involve deconstruction. One of those scenarios is ignoring lambda arguments:

// what does this mean?  Are any of those identifiers in any scope?
stuff.Select((_, _) => { ... });

// should this be a compiler error for trying to shadow an existing variable, or a wildcard?
int _ = 123;
stuff.Select((_, index) => { ... });

// unambiguous
stuff.Select((*, *) => { ... });

If C# were to decide on _ as a wildcard they should go the Java route and make _ no longer a legal identifier. But given the teams hesitation on breaking existing code I doubt that would happen.

@jnm2
Copy link
Contributor

jnm2 commented Sep 5, 2016

@svick @HaloFour Gotcha, I'm on the same page now. I'm satisfied that * is the best option.

@Sharplifter
Copy link

Sharplifter commented Dec 26, 2016

From my point of view, in all but one case presented in this discussion the void keyword expresses better what is happening than the asterisk option *.
And even in this one case I think, people who are not aware of it would be able to figure out what this actually means:

Point pt = window.Location;
if (pt is (0, void)) {
    Console.WriteLine("The window is somewhere along the left side!");
}

in contrast to having to scratch your head seeing all the other examples like:

var (x, *) = window.Location;
if (x == 0) {
    Console.WriteLine("The window is somewhere along the left side!");
}

This looks way more intelligible to me:

var (x, void) = window.Location;

This would simply lead to expanding the meaning of void from doesn't return a value to

  • no value is returned
  • value isn't taken anywhere (at least not to a named a variable)
  • value is not compared / matched at all

I think this will arrive much easier in the head of the average C# developer than having yet a third possible meaning for * and would increase readability in general.

@falahati
Copy link

falahati commented Feb 11, 2017

void has other purposes, I don't like * either, but I understand the decision that resulted in using it. Also, it does actually mean anything. So it makes sense.

@jnm2
Copy link
Contributor

jnm2 commented Feb 11, 2017

@falahati This is out of date. Further design discussions resulted in the adoption of _. They are now called discards rather than wildcards. Here's the new documentation on that: https://github.com/dotnet/roslyn/blob/post-dev15/docs/features/discards.md

@snboisen
Copy link

Oh wow, so _ was possible after all. So happy C# ends up following the same notation used in F# and many other functional languages! 🎉 🎉 🎉
Fantastic job working through the difficulties!

@HaloFour
Copy link

@snboisen

Just note that the compiler is forced to treat _ as an identifier first and a discard/wildcard second, at least in every scenario where using _ is legal syntax today. For example, the following would be a compiler error:

string _ = MethodThatReturnsString();
if (int.TryParse(s, out _)) { ... }  // compiler error, _ is a variable of type string here

And I hope you never name your fields _ otherwise you could silently overwrite them.

In short, the compiler didn't solve the problems of using _ as a discard. It forces the developer to understand and navigate the ambiguities.

@jnm2
Copy link
Contributor

jnm2 commented Feb 14, 2017

@HaloFour is still saying "over his dead body" :D

@HaloFour
Copy link

@jnm2 Not my place to say that. I expressed my opinion and the team decided to go that route anyway. No biggie. But now there is the liability and onus to educate everyone on the behavior of this feature.

@AndreasHassing
Copy link

Coming from F# it's nice that the adopted version of discard turned out to be _, but reading through @HaloFour's arguments made me think that maybe * would've been a better choice for C#. Oh well 😄.

@jcouv
Copy link
Member

jcouv commented Jul 29, 2017

LDM notes for July 13 2016 are available at https://github.com/dotnet/csharplang/blob/master/meetings/2016/LDM-2016-07-13.md
I'll close the present issue. Thanks

@jcouv jcouv closed this as completed Jul 29, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests