Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Proposal/Discussion: Negation of if/while Condition #568

Open
HaloFour opened this issue May 10, 2017 · 108 comments
Open

Proposal/Discussion: Negation of if/while Condition #568

HaloFour opened this issue May 10, 2017 · 108 comments

Comments

@HaloFour
Copy link
Contributor

There have been a couple of proposals exploring how improve the readability of conditional operations that involve negation. Some developers find the nesting of parenthesis to be unattractive at best and easy to miss at worst.

I would like to explore the potential of allowing for a negation prefix operator ! to appear outside of the required parenthesis for if and while conditions so that the condition is effectively reversed:

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

while !(finished)
{
    // stuff
}

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

Certainly minor and the argument can definitely be made that the negation is even less obvious. It also doesn't address concerns with operators that would still require parenthesis, e.g. is.

@lachbaer
Copy link
Contributor

lachbaer commented May 10, 2017

I give it 👍 because I believe that it improves reading and thinking much.

The downside is that it can be easily overlooked compared to if (!( ... )). Also the position of the ! should be fixed, otherwise there are 3 or more possibilities to write the same expression, leading to confusion and mistakes.

if! (foo is Bar bar) ...
if ! (foo is Bar bar) ...
if !(foo is Bar bar) ...
// worst:
if                          !
  ( foo is Bar bar ) ...

foobar! 😁

Of all I prefer the @HaloFour style. (Hey, good name, "the HaloFour condition syntax" 😇 )

@jnm2
Copy link
Contributor

jnm2 commented May 10, 2017

I prefer the readability of while (!isFinished) which is already available, and if we had to choose between them, I'd rather have if (foo isnot Bar bar) than if !(foo is Bar bar) because isnot can be used in more places.

@lachbaer
Copy link
Contributor

A little side note: though they are kind of obsolete for this case, an a priori negated condition could call the operator false. if (! TypeWithOperatorTrue) ... gives a compilation error, unless that type has an implicit convertion to boolean as well.

@bondsbw
Copy link

bondsbw commented May 10, 2017

Also the position of the ! should be fixed

I disagree, that would prevent no spaces:

if!(foo is Bar bar)

which is less common but is used.

@bondsbw
Copy link

bondsbw commented May 10, 2017

@jnm2 agreed with isnot, or perhaps !is or is!. Though it could coexist with this proposal (which can be used for situations other than is).

Both situations would need to consider how pattern variables should work, scoping, etc.

@casperOne
Copy link

casperOne commented May 10, 2017

I'm pretty meh on this. I don't think the readability is any better with the removal of the outer parenthesis (I see it that way, not moving the not operator out).

And if the readability isn't improved, ergo, there's no good reason to do it (IMO).

I think the biggest issue here is that It also opens the door for other things to be included outside the parenthesis (by precedent), which I think will ultimately have a detrimental effect on the language long-term.

@CyrusNajmabadi
Copy link
Member

CyrusNajmabadi commented May 10, 2017

Rather than this, i would just prefer that we relax the grammar for if/while/switch/etc. to not be if ( expr ) but just have it be if expr.

if we did that, then if ! ... just falls out.

Currently the requirement of parens is simply a holdover from C. There is no actual syntactic need/ambiguity it addresses. Which construct this is is already governed by the leading keyword.

--

Another way to think about this is the grammar was always if expr, but there was a normative rule that stated that expr needed to be a parenthesized-expression.

@HaloFour
Copy link
Contributor Author

@CyrusNajmabadi

Indeed, that is another option. I probably wouldn't prefer that syntax in the normal cases, but it does enable the syntax above as well. Personally I like the parenthesis and I apply them generally anywhere that I might have a non-trivial expression, such as with return statements or ternary conditions.

@DavidArno
Copy link

If you championed that, @CyrusNajmabadi, I'd be more than happy to promise to never ever again argue with you or make snide comments about the motives and behaviour of the team.

Deal? 😀

@HaloFour
Copy link
Contributor Author

#itsatrap

@CyrusNajmabadi
Copy link
Member

I would do it... if i believed you :D

@casperOne
Copy link

Rather than this, i would just prefer that we relax the grammar for if/while/switch/etc. to not be if ( expr ) but just have it be if expr.

Does this mean that one liners such as this:

var cond = true;

if cond Console.WriteLine("true");

Would be legal?

When separating on multiple lines, the syntax looks OK, but when on the same line, it starts to get all jumbled together.

At least that's my perception. This becomes a stylistic issue though, I don't see any real drawback to having this.

Note, I don't see any benefit, but like I said, it's stylistic, and may be of benefit to others (and I acknowledge saving people from having to type millions of parenthesis over their lifetime).

@CyrusNajmabadi
Copy link
Member

Would be legal?

Yes

When separating on multiple lines, the syntax looks OK, but when on the same line, it starts to get all jumbled together.

Then don't write it that way. :) It's not like parens would be disallowed. They would just not be required. Similar to how we don't require curlies. It's up to you if you want to mandate them for your own code or not depending on situation.

@casperOne
Copy link

Then don't write it that way. :) It's not like parens would be disallowed. They would just not be required. Similar to how we don't require curlies. It's up to you if you want to mandate them for your own code or not depending on situation.

Someone didn't read the rest of my response =P

@lachbaer
Copy link
Contributor

This becomes a stylistic issue though,

Exactly! Like the placing of curly braces or so many other different things. A common style will establish nevertheless, one that is primarily teached in books. But then you should also allow until when everything becomes so 'verbose'.

@jnm2
Copy link
Contributor

jnm2 commented May 11, 2017

if !foo.Bar() foo.Baz();

Meh.

@jnm2
Copy link
Contributor

jnm2 commented May 11, 2017

I predict pattern matching complications if this is ever legal.

if obj is Foo foo foo();

@jnm2
Copy link
Contributor

jnm2 commented May 11, 2017

Knew it wouldn't be long. Ambiguity:

if obj is Foo foo (x).Bar();

if (obj is Foo) foo (x).Bar(); or if (obj is Foo foo) (x).Bar();?

@lachbaer
Copy link
Contributor

Well, very obviously the use of a statement-block must be forced in case the condition parentheses are dropped. Not a big drawback, because most flow statements I see around have braced statement blocks anyway.

Another advantage to me is, that it makes one-line if-statements, e.g. for early returns, optically better readable.

if obj is null { return; }
// vs.
if (obj is null) { return; }

The second is normally spread over 4 lines in total, I guess that that coding style exists, because two clasped regions in one line is "ugly".

With only one braced block however it plays in the same leage as other used constructs.

public Student(string Name, int Age) : base(string Name) { _age = Age; }
string Name { get; private set; }
if foo is null { bar(); }

(First one has other (...), but nevertheless I've seen this construct many times.)

@CyrusNajmabadi
Copy link
Member

CyrusNajmabadi commented May 11, 2017

(First one has other (...), but nevertheless I've seen this construct many times.)

Every if in C# has always had (...), so you've certainly "seen [that] construct many times" as well :)

@lachbaer
Copy link
Contributor

@CyrusNajmabadi ?

@CyrusNajmabadi
Copy link
Member

Ambiguity:

C# is rife with ambiguities :) The only rules are:

  1. Old code needs to parse as before (unless the benefit so far outweighs that). For example, we accept that we parse Foo(a < b, c > (d) ) differently that before because the number of people hurt by this is so small, and the new parsing is so worthwhile.
  2. With ambiguities, you have to specify the disambiguation rule.

c# 1.0 shipped with this ambiguity:

(a)-b. But we simply defined how it would be parsed and moved on. We'd do the same here. Likely we'd take the maximal approach to sub production parsing. i.e. since we could, locally, parse out a pattern here, we woudl, leading to the interpretation:

if (obj is Foo foo) (x).Bar();

@CyrusNajmabadi
Copy link
Member

@lachbaer You said:

because two clasped regions in one line is "ugly". ... With only one braced block however it plays in the same leage as other used constructs.

But you then referenced a construct that has two clasped regions in one line. You said that that was ok though because you'd seen that construct many times.

But, of course, if it's ok because you've seen it many times, then certainly a multi-clasped 'if' on a single line is ok because you'll have seen (...) with ifs 100% of the time :)

@lachbaer
Copy link
Contributor

@CyrusNajmabadi I thought it was very obvious what I wrote. But especially for you:

  • public Student(string Name, int Age) : base(string Name) { _age = Age; } seen many times.
  • if (obj is null) { return; } seen rather seldom (spread over 4 lines normally).

@lachbaer
Copy link
Contributor

If removing the need for the condition parantheses, what is a big change for a B/C-derived language since their existence, then IMO the enforcement of a braced statement-block for that new syntax should also be introduced.

@CyrusNajmabadi
Copy link
Member

then IMO the enforcement of a braced statement-block for that new syntax should also be introduced.

This seems like one step forward, one step back. :)

@lachbaer
Copy link
Contributor

This seems like one step forward, one step back. :)

I understand what you mean, but sometimes a step back isn't a bad step if you're at the edge of an abyss 😀

I mean, you once wrote that you primarily focus on features for educated programmers. Most advanced code that I have seen use statement-blocks always anyhow (we're talking about control statements here). With this change you had the unique possibility to write history and shift it for the better, for the sake of clarity to all developers.

if obj is Foo foo (x).Bar();

Even if you resolved the amiguities by definition, there is the chance that other developers meant it differently. Why introducing a just possible trap when you can avoid it in the first place? Hasn't C# a leading paradigm of avoiding errors before they occur?

@CyrusNajmabadi
Copy link
Member

that's a fair point. But i don't think it's our place to mandate that sort of thing at the language level. i think it would be much more appropriate to just give users style options in the IDE so they can decide on stylistic things themselves. For example, i have to accept that people that want this, will want to write:

if (!(o is string))
    return;

And will want to convert it to:

if !(o is string)
    return;

If we say "if you want to remove hte parens, now you have to write:

if !(o is string)
{
    return;
}

Then that's going to kill the value of this feature for many users.

@CyrusNajmabadi
Copy link
Member

Note: i'm not dismissing this concern. It would definitely be a deep one. But i would not consider it a killer for this idea. We would have to consider what we would do here, and it might entail carving out a set of allowed/disallowed things. I'm just trying to clarify that it's unlikely that we could introduce a code-reduction feature, and then tack on a required code-increase feature :D

@yaakov-h
Copy link
Member

@lachbaer

then IMO the enforcement of a braced statement-block for that new syntax should also be introduced.

Once again, this is a job for a Roslyn Analyzer. You could make the case to include one with Roslyn/VS/.editorconfig, but other people can have different opinions and switch off the analyzer rule or not include it in the first place.

I think it would be strange to have if expr Thing(); become a valid single line of C#, but after playing around with Swift I'm very much in favour of if expr { block } as regular syntax.

@CyrusNajmabadi Is there any precedent etc. of creating a compiler warning or error from such an ambiguity as the one defined above, rather than quietly parsing it as one or the other?

@ufcpp
Copy link

ufcpp commented Jan 16, 2018

It is unfriendly for non English speakers to increase vocabulary unless/until.

@jnm2
Copy link
Contributor

jnm2 commented Jan 16, 2018

How is that more unfriendly than void or delegate (noun), which are words I almost never hear used in English conversation? We just sort of accept what void means in C# without drawing upon any spoken-English intuition. Like a lot of the keywords. class vs struct. enum. static. namespace. switch. Etc.

@ufcpp
Copy link

ufcpp commented Jan 16, 2018

void and delegate are rather easy to remember as technical terms, not as natural English words. Some Japanese developers don't know how "void" means in English but do know it means "no return value".
Difficulty of unless/until is that different letters (if and unless) are related to each other. As a negated word of if, if not (if !) is much easier than unless.

@jordao76
Copy link

@HaloFour

Also, those keywords also don't solve the problem of trying to negate an is expression as part of a larger Boolean expression without having to break out the parenthesis.

I usually solve that problem with some refactoring.

@HaloFour
Copy link
Contributor Author

@jordao76

I usually solve that problem with some refactoring.

While I largely agree, that is substantially more verbose and does not work well when it comes to analysis for definite assignment and pattern matching. For while and do loops that would also lead to leaky scope which has further ramifications on closures (which is why leaky scope was backpedaled in those cases.)

@jordao76
Copy link

@ufcpp

It is unfriendly for non English speakers to increase vocabulary unless/until.

It is unfriendly for non-English speakers to have an English-based language. But, as programmers, we get used to it. I'm sure you can deal with extra keywords, as might have been the case with, e.g., async and await.

@HaloFour, thanks for explaining that.

@alrz
Copy link
Contributor

alrz commented Jan 16, 2018

It is unfriendly for non-English speakers to have an English-based language

Absolutely not. I cannot imagine programming in Persian. that would probably read like Shahnameh by Ferdowsi.

@jordao76
Copy link

@alrz, I meant that as a very general comment. If you don't know any English, that would be unfriendly at first, but then you'd certainly get used to it. I agree with you, of course, I can't imagine working with a non-English based language.

@yaakov-h
Copy link
Member

I find that in spoken language, until usually comes after the action. For example, "wait here until I get back" is more natural than "until I get back, wait here".

This would make it more suitable for a do...while sort of control structure, where the condition goes at the end.

Unfortunately, this makes for rather horrible readability IMO - for example, Ruby has statements such as:

puts "This string will not be printed" unless true

... which trips me up every time.

@ufcpp
Copy link

ufcpp commented Jan 16, 2018

@jordao76
Yes, async/await pair is difficult. Some one misunderstands await as "asynchronouslv wait". It's difficult but there is no word more appropriate than await. On the other hand, until can be replaced with simpler and already existing words if!.

@alexzzzz
Copy link

alexzzzz commented Jan 18, 2018

@gafter

Also the first time I’ve heard the suggestion to add “is true” when reading code. Would if (e is true) ... be read “if e is true is true”?

if (e is true) is just redundant when e is a bool.

if (control.Validate()) { } // WinForms API
if (int.TryParse(s, out var n)) { }

If TryParse… what? If it returns true. If its result is true.

if (!(enabled && visible)) { }

"If not enabled and visible" is not technically correct, "if not enabled or not visible" is not what is written. Correct reading is "If enabled and visible are not both true". Maybe native speakers can come up with a better option without be true/not be true/be not true, but I can't.

@jnm2
Copy link
Contributor

jnm2 commented Jan 18, 2018

Or "if not both enabled and visible."

‘Either...or’ works with any number. ‘Both...and’ only works with exactly two. I've tried to think of what you'd say for if (!(enabled && visible && focused)) { }– ‘both’ is perfectly clear but an obvious stretch.

Speech doesn't lend itself to parenthesizing, in general.

@bondsbw
Copy link

bondsbw commented Jan 18, 2018

I don't try to convert to unparenthesized English.

@DavidArno
Copy link

You can sort of express such logical expressions in English:

if !(a && b && c) // "if not a and b and c"
if (!a && b && c) // if not a, and b and c"

With the "," being vocalised as a slight pause. But such an approach quickly breaks down for non-trivial examples and is highly error prone.

@Joe4evr
Copy link
Contributor

Joe4evr commented Jan 18, 2018

if (e is true) is just redundant when e is a bool.

But it's not when e is a bool?. And that's relatively easy to get these days by having a ?. in the expression.

@orthoxerox
Copy link

@DavidArno That's a spoken equivalent of a whitespace-sensitive language!

@DavidArno
Copy link

@orthoxerox,

Ah, English, don't you just love it? 🤣

@alexzzzz
Copy link

@Joe4evr

But it's not when e is a bool?. And that's relatively easy to get these days by having a ?. in the expression.

If it's not a bool, I couldn't read it at all. I would get stuck for a while trying to realize what does it mean, what was the intention and what is the difference between e is true, e == true and (bool)e.

@weitzhandler
Copy link

weitzhandler commented Oct 15, 2018

I prefer an isnot operator instead, just as a simple syntactic sugar, or !is (ugly but compact).

@cryolithic
Copy link

I personally quite like the idea of negation of the statement. I tend to work with a lot of juniors, and something like this would make many simple boolean errors go away.

@Saleca
Copy link

Saleca commented Nov 17, 2022

There is no actual syntactic need/ambiguity it addresses.
Another way to think about this is the grammar was always if expr, but there was a normative rule that stated that expr needed to be a parenthesized-expression.

Just wanna mention that if you remove parenthesis then you need to specify when the expression ends somehow. Either make block compulsory { }, or like, python you use : since in c# you can do:

if isLogged && isActive GiveAccess(); In my opinion gets a bit confusing

You could if isLogged && isActive {GiveAccess();} but then why not keep if (isLogged && isActive) GiveAccess();

@jkalamarz
Copy link

jkalamarz commented Feb 7, 2023

I'd also love if negation operator ! could be move to the very last element of the nested members notation:
if (isDone && !foo.VeryLongMethodName().MyElement.GetList().Any())
could be written
if (isDone && foo.VeryLongMethodName().MyElement.GetList().!Any())

@Saleca
Copy link

Saleca commented Feb 9, 2023

I see where you are getting at but doesn't look great imo
if it matters "very long variable name" appearance you can assign it to a variable:
var anyElement = foo.VeryLongMethodName().MyElement.GetList().Any()
then will look better than either of the above options (imo)
if (isDone && !anyElement)
I personally never had any issue negating on long nested member, and usually go for the simplest and most direct syntax if I will only need the value once. but anyway maybe this is not the thread to discuss this topic since its different suggestion altogether.

@Thaina
Copy link

Thaina commented Feb 10, 2023

Or if (isDone && foo.VeryLongMethodName().MyElement.GetList().NotAny()) (make another extension method)

@bernd5
Copy link
Contributor

bernd5 commented Feb 10, 2023

Or a bool extension method like:

if (isDone && foo.VeryLongMethodName().MyElement.GetList().Any().Not())
{
  //do stuff
}

@Thaina
Copy link

Thaina commented Feb 10, 2023

Actually I think NotAll and NotAny is common enough to included in LINQ

And it's not always give the exact same result with negation. !Any((b) => b) is not behave the same as All((b) => !b) such as when it was empty

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests