dotnet / csharplang

The official repo for the design of the C# programming language
11.11k stars 1.01k forks source link

Champion "Negated-condition if statement" #882

Open gafter opened 6 years ago

gafter commented 6 years ago

See https://github.com/dotnet/csharplang/issues/568 where this idea was recently introduced.

iam3yal commented 6 years ago

I support the idea but in my opinion not would be much more readable than reusing the negation operator.

Just reusing @HaloFour examples:

if not(foo is Bar bar)
{
    // stuff
}

while not(finished)
{
    // stuff
}

do
{
    // stuff
}
while not(finished);

And maybe as added benefit:

if (foo is not Bar bar)
{
    // stuff
}

This can probably be applied to other constructs in the language..

I'm in favor of unless but see @alrz explanation as to why this is more expensive to add.

alrz commented 6 years ago

Is this a new statement form or we are going to just remove parentheses from all statements like Swift/Rust?

Joe4evr commented 6 years ago

Probably new statement form, Gafter showed in the linked issue that removing the parentheses could cause ambiguity.

alrz commented 6 years ago

I see, thanks.

RogerWaters commented 6 years ago

I would just replace "!" with "not" because it's much more readable. I currently always write "== false" to clearify that condition is negated. Parentheses should be kept, as they mark begin and end of the condition especialy on multi line conditions.

DavidArno commented 6 years ago

My personal opinions:

  1. if (foo is not Bar) - πŸ‘ πŸ‘
  2. if !(expression) - πŸ‘
  3. if not (expression) - πŸ‘Ž

I disagree with @eyalsk and @RogerWaters and actually think ! is more readable than not for a simple boolean expression.

yaakov-h commented 6 years ago

I'll throw my vote in for !(expr) too.

foo is not Bar only works for pattern matching, not other expressions where it makes sense to read it one way but you want the condition negated.

not is an entirely new keyword that I don't think is necessitated here. We already have ! throughout the rest of the language, which is well understood. We don't have - or need - if (foo not == bar) etc, or bool a = ...; bool b = not a, so I don't see the benefit for an if not.

dsaf commented 6 years ago

I hope this will not be implemented in 7.x-8.x:

  1. Why this and not e.g. better LINQ syntax integration? To show that incremental language features can be delivered quickly as part of minor versions?

  2. The not syntax - does it mean interchangeability with !? It's not even in LINQ where it would make the most sense.

  3. The ! syntax - will it cause confusion with the coming not-null assertion? Is it that much more readable even? Why not just extract meaningfully named variables and extensions? E.g. I use if (x.None()) instead of if (!x.Any()).

iam3yal commented 6 years ago

@yaakov-h

foo is not Bar only works for pattern matching, not other expressions where it makes sense to read it one way but you want the condition negated.

Well, that's your opinion which I respect but I disagree, not can definitely work here too, it's a matter of taste.

not is an entirely new keyword that I don't think is necessitated here. We already have ! throughout the rest of the language, which is well understood.

And this is a new statement, we didn't have when before and now we do.

We don't have - or need - if (foo not == bar) etc, or bool a = ...; bool b = not a, so I don't see the benefit for an if not.

I'm not sure I follow, you wouldn't be able to use not inside the if statement, only outside; like I said before we didn't have many things in the language and now we do, in my opinion it's really a weak argument against it, it's more reasonable to say it's not my cup of tea than to say we didn't have it before or try to speak in the name of everyone and say we don't need it, well, again, I prefer not. πŸ˜„

I wonder whether if!(...) works because some people prefer the following style if(...) over if (...).

p.s. Just to make myself crystal clear, I don't mind reusing the negation operator, I just think that not would be more readable but than @DavidArno thinks the opposite, fun. πŸ˜†

HaloFour commented 6 years ago

@eyalsk

I wonder whether if!(...) works because some people prefer the following style if(...) over if (...).

You'd still be able to put a space between if and !(...) here, e.g. if !(foo is Bar) { }.

Personally I think that ! is plenty readable. Languages in the C# family generally prefer punctuation for operators rather than keywords and it would be weird to have both ! and not be unary negation operators. That said I don't think that this proposal obviates the need and use of a not pattern and the above could just as easily be written as if (foo is not Bar). But this proposal is significantly easier to implement and quickly eliminates a niggling aspect of C grammar.

MgSam commented 6 years ago

I'm not enthused about this proposal but it's certainly an improvement over today's mess of parenthesis required to do this, if(!(condition)).

My main problem with it is that it still forces negative is expressions to be inside out. Compute the value of some expression and then negate it, rather than just conceptually checking for the negative expression. This makes it a lot slower to mentally parse what's going on. Thus, I'd still prefer isnot (don't care the exact wording) in addition to, or instead of, this proposal.

MgSam commented 6 years ago

Languages in the C# family generally prefer punctuation for operators rather than keywords...

Maybe that was the case at one point in time but not any more. There are a plethora of examples in C# and other languages of word operators. I think readability is much more important than trying to follow design principles from a 40 year old language.

HaloFour commented 6 years ago

@MgSam

There are a plethora of examples in C# and other languages of word operators.

C# has mostly kept to the same conventions as C/C++. LINQ and await are the big outliers that come to mind and you could argue that await falls into the same category as yield. I'm not opposed to isnot or not but I'd rather view them in a larger context such as pattern matching. If not patterns were closer to being on the table I'd happily prefer that over this proposal. But this proposal is also infinitely smaller and easier to implement and does not preclude the possibility of including those operators in the future.

From the syntax suggested above, not would still require the inside-out evaluation that you dislike about !. How would you suggest supporting that keyword without that being a problem? Or are you just arguing for the addition of an isnot operator which can't be used in general purpose negation scenarios?

jnm2 commented 6 years ago

@DavidArno

My personal opinions:

  1. if (foo is not Bar) - πŸ‘ πŸ‘

What do you mean we never agree on anything? πŸ˜†

(:+1:)

MgSam commented 6 years ago

typeof, nameof, as, is, default, await, yield, the query comprehension form of LINQ, possibly with in the future. Lots of keyword operators. And with is getting a major upgrade as part of pattern matching, the trend is only being reinforced.

To make C# pattern matching as usable as possible, it creates the immediate need for a better way to express negation in this context. That's why I'd prefer isnot be focused on rather than simply relaxing the parens requirement on if, though I'm not opposed to doing both.

My issue isn't about the order of evaluation- it's about how it's mentally parsed when a human being is reading it. I've seen far too many logic errors from people sprinkling ! operators outside of nested expressions and then it being too hard to mentally parse what's going on so they make a mistake somewhere. And that is only like to get even worse with pattern matching only having a positive form of the expression. This proposal, while it does decrease verbosity, does not solve that part of the problem.

It's directly analogous to the != operator. No language needs a != operator. You could easily express any logic using only the unary !. But the huge benefits of being able to directly express a negative condition outweigh the cost of extra syntax.

HaloFour commented 6 years ago

@MgSam

That's why I'd prefer isnot be focused on

Well it's not like the team can't do both.

I'd prefer not combined with the is operator to keep the syntax consistent regardless of how you use the pattern, e.g. if (foo is not Bar) or case not Bar:

If it came down to an either-or proposition I would certainly prefer that the team focus on not.

rather than simply relaxing the parens requirement on if

This proposal doesn't go that far. Actually it's been demonstrated that removing the parens requirement will create syntax ambiguities. All this proposal does is allow an optional ! outside of the parenthesis in order to negate the entire condition.

My main problem with it is that it still forces negative is expressions to be inside out.

My issue isn't about the order of evaluation

Well, which is it? :grin:

alrz commented 6 years ago

All this proposal does is allow an optional ! outside of the parenthesis in order to negate the entire condition.

I'd prefer to see it as an if followed by an optional ! i.e. a modifier on if: if! (e), rather than an operator on (e): if !(e), which doesn't make sense at all. /s

yaakov-h commented 6 years ago

well if if!(e) becomes valid (i.e. no space required after if), you can enforce that with an analyser... πŸ˜„

codefox42 commented 6 years ago

Sometimes I miss the simplicity of unless in Perl:

unless (success) { throw new InvalidOperationException("..."); }
yaakov-h commented 6 years ago

@codefox42 #138 is probably the closest we'd get.

CyrusNajmabadi commented 6 years ago

In a V1 i think i would prefer "not". However, C# has already shipped 7 (soon to be 8) versions with an existing "not" operator !. We should use what we have when it makes sense. We don't need to invent synonyms.

HaloFour commented 6 years ago

@CyrusNajmabadi

Agreed. I'd only consider not within the context of pattern matching where I believe @alrz demonstrated that reusing ! would cause ambiguities. It wouldn't be usable as a replacement to !, e.g. not y == x would not compile. But maybe foo is not Bar could, as well as case not 0 and, potentially, pt is Point(var x, not 0) whenever recursive patterns get off of the ground.

iam3yal commented 6 years ago

@CyrusNajmabadi You and others are probably right about not, I thought it was a good idea but anyway, what do you guys think about unless? is adding a new keyword for it off the table? just curious.

alrz commented 6 years ago

@HaloFour As demonstrated in #277 we should actually consider reusing ! but I think its precedence would be an issue, and tbh it's not as readable as not in a pattern e.g. !0 doesn't make much sense.

@eyalsk I think unless is relatively more expensive since unless(e); is a valid invocation expression and we need to reconstruct it as an unless statement with a semicolon in the body after semantic analysis. As long as if! works I don't think it would worth it, however I agree it's a little strange compared to other proposed keyword modifiers like foreach? and await?.

iam3yal commented 6 years ago

@alrz I understand, thanks. ;)

DavidArno commented 6 years ago

@jnm2,

It's not just you agreeing with me; five six eight people do! Not since the best bits of pattern matching were dropped from C# 7 have I found myself being agreed with so much. πŸ˜€

sirgru commented 6 years ago

With if ! seems more natural, but with is not seems more natural. If there's both then there's the question why there are 2 ways to do the same thing (code style issues, combination issues). So I would guess ! is would have to do. I don't like isnot, it's like elseif and that's just improper English.

Also, I don't like the removing of parens. It makes for sloppy one-liners. It optimizes for typing but not for reading.

HaloFour commented 6 years ago

@sirgru

The issue is that pattern matching extends beyond is. Would we also have !case? What about with recursive patterns where there wouldn't be an additional keyword? point is Point(var x, !0)?

DavidArno commented 6 years ago

@HaloFour,

Is the following proposed for recursive patterns?

point is Point(var x, not 0)

If so, I definitely agree that not makes a lot more sense here than ! does and it's a lot more compact than

point is Point(var x, var y) when y != 0
alrz commented 6 years ago

ok I'm convinced if we're going to do if! syntax we could do it for all statements and require blocks to disambiguate.

that said, the example @gafter gave becomes a compile-time error.

if exprDoNotStartWithOpenParen
        (M)(e1)(e2); // ERROR: expected `{`, found `;`

then we have no changes in the syntax node, just the parser.

Although, you can't do if !(e) return; anymore, instead you should write if !(e) { return; }.

A corner case would be object initializers, if new bool() {} and if new bool() {} {} both may become valid syntax, depending on how we handle the initializer block.

sirgru commented 6 years ago

@alrz So we have to have either parens or braces in one liners. What are we saving here by omitting the parens? It introduces a new "unexpected" rule.

tannergooding commented 6 years ago

I think the biggest reason for using not instead of ! (or allowing both) is that ! can be easy to miss sometimes, especially if it is just at the beginning of a complex expression.

Most operator/punctuation characters are either visible by themselves or tend to have whitespace surrounding them to make them more readily visible (most of them, such as &, +, and - are ~10 pixels wide on my screen, others such as , and ; are ~5px). However, ! is visibly small and is easily swallowed when intermixed with parenthesis and other statements (it is 3px on my monitor, with 2 of those pixels being used for anti-aliasing). -- Note: The pixel sizes are from a 4K screen with 150% scaling (OS scaling, not VS scaling) using the default font in VS.

Allowing if!(expression) suffers the same problem as what we have today, the ! gets swallowed by the surrounding characters and is easy to miss. if !(expression) is a bit better (and more readable than today's if (!(expression))), but it is still not quite as "parsable" (at least by my eyes) as if not (expression) or if ((expression) == false)

alrz commented 6 years ago

@sirgru

So we have to have either parens or braces in one liners

Note that the case for a negated if statement is not conciseness, it's about readability (as mentioned in the linked proposal).

What are we saving here by omitting the parens?

Rust for instance, did this from day one and nobody ever wanted a "negated if statement". I think that pair of parentheses are the reason we're here. Besides, most of codebases out there are using blocks anyways, because it's more readable and have better diff when modified.

It introduces a new "unexpected" rule.

I think consistency is important, even though reusing !(e) seems like a good idea, it also is a valid expression and an embedded-statement right next to it, is not what I'd expect. if we just do this for if it looks like you are ommiting parens from if, but it's actually a new syntax and you don't have it in other statements. that is also unexpected.

alrz commented 6 years ago

@tannergooding

However, ! is visibly small and is easily swallowed when intermixed with parenthesis and other statements

Same would apply to the post-fix "not null" operator. The fact that it's post-fix may cause it to be totally overlooked.. so, I think we're stuck with it?

tannergooding commented 6 years ago

I don't think it will be quite as bad.

The biggest issue with !, today is it tends to not have surrounding whitespace. I think this is why if (!(expression)) is hard to parse but if !(expression) is so much better (even if not "ideal"). a != b is likewise, not hard to parse, both due to the surrounding whitespace and due to the height difference between ! and =.

There will certainly be some types where the not null operator is hard to read, such as roof! (which is also why I probably don't like if! (expression) and if!(expression)), but it should be a bit more readable Ex: string! value, Exception! value, IDisposable! value, object! value, etc.

Thaina commented 6 years ago

Agree for if !(expression)

And I prefer obj not Type than obj is not Type. At least it should be obj isnot Type

jnm2 commented 6 years ago

@Thaina what about case not Type? Then since we already replace case with obj is, that composes as obj is not Type.

Thaina commented 6 years ago

@jnm2 At least case is special one that need it own line and special syntax case SomeThingSomeThing :. is on the other hand tend to stay with many things around it in if clause so SomeCondition && obj is not Type && SomeOtherCondition seem a bit hard to read for me

jnm2 commented 6 years ago

seem a bit hard to read for me

Meh, I used to be pro isnot or isnt but I can see it both ways.

DavidArno commented 6 years ago

I would just replace "!" with "not" because it's much more readable...

[I] think ! is more readable than not...

I just think that not would be more readable...

...it's not as readable as...

...to make them more readily visible...

I think the one conclusion we can reach here is that a lot of us could probably do with an eye test! πŸ˜†

alrz commented 6 years ago

I think both if !() and if not emphasis on the code that would be executed if the condition is not true. But IMO when you want to write if(!(obj is Foo foo)) the emphasis is on the code that comes after the if statement, you just want to make sure that the condition is true on the rest of the block. Maybe we should also consider guard statement or something similar here.

So I think these are available options so far.

TonyValenti commented 6 years ago

My preference is if!(...) Also don’t forget while!(...)

I can go either way with β€œis not” or β€œisnot”. That would depend on which allows the IDE team to provide the best experience.

alrz commented 6 years ago

I accidentally made myself a negated if statement πŸ˜†

if (condition); else 
{
}

This is basically the guard statement,

if (argument1 is StatementSyntax statement1); else throw new ArgumentException();
if (argument2 is StatementSyntax statement2); else throw new ArgumentException();

Sadly, auto formatter doesn't play nice with it and also you get "possibly mistaken semicolon" warning.

alexzzzz commented 6 years ago

My preference is just

if (expression == false)

without any extra syntax.

gafter commented 6 years ago

@alexzzzz that does not invert the definite assignment state of the expression as ! does.

jordao76 commented 6 years ago

I'm in favor of unless and until. Much more straightforward. I'd even go as far as to say that all the other alternatives are awkward and I'd prefer to live with the status quo.

Now, it's already been stated that they might be harder to implement and that, e.g., unless(e); is a valid invocation expression. Although technically correct, I wouldn't rule them out because of these arguably rare occurrences. There might be good solutions to these cases. Also, we should think of the usability of the language overall, regardless of the amount of effort to introduce the feature, unless it becomes prohibitive, which I don't believe to be the case here.

Just my 2 cents...

alexzzzz commented 6 years ago

@gafter

that does not invert the definite assignment state of the expression as ! does.

To me it feels much less of a problem than incorrect semantics of ! most of the time and its poor visibility.

CyrusNajmabadi commented 6 years ago

To me it feels much less of a problem than incorrect semantics of !

What incorrect semantics does ! have?

and its poor visibility.

! is very idiomatic C-style language negation. I don't think visibility of it is a serious problem that needs to be addressed

Neme12 commented 6 years ago

I used to be rather opposed to isnot or even is not had it been just an extension of the is operator but now seeing that not could actually be a pattern that could also be used with case and even recursively, that makes a lot of sense to me and it takes away the concern of having a not keyword that would just be a replacement for !, because not as a pattern would be very different and not interchangable - not 5 and not Bar would be a pattern whereas !Bar makes not sense at all.

HaloFour commented 6 years ago

@Neme12

I do agree, however the language already supports ! as a prefix to a pattern, at least in the very specific case of constant bool patterns:

if (b1 is !false) { ... }

switch (b2) {
    case !true: ...
    case !false: ...
}

It's more an artifact of the compiler being able to evaluate the Boolean expressions at compile-time so that !false is really true, but that might set a precedent. Syntax aside, I think that a not/! pattern would be quite useful.