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: not keyword for if and while #157

Closed
lachbaer opened this issue Feb 21, 2017 · 65 comments
Closed

Proposal: not keyword for if and while #157

lachbaer opened this issue Feb 21, 2017 · 65 comments

Comments

@lachbaer
Copy link
Contributor

This proposal is about readibility. Many things of C# go in that direction, like LINQ and the new match expressions.

Many conditions must be negative. But instead of using the slender and easy to overlook ! the negation should go with the main expression

void MyFunction(int b) {
    if not (b>0)
        throw new ArgumentOutOfRangeException();
    
    bool finished = false;
    while not (finished) {
        // have your own interpretation here ;-)
    }
}

As you can see not as a keyword stands after if and while and not as part of the expression.

In my eyes it is much more readable! Could be a light extension with a big use.

@pebezo
Copy link

pebezo commented Feb 21, 2017

I have made bugs where I didn't notice the ! quite a few times. One workaround is to use (!(expression)) but that doesn't look pretty (visually busy). I like your suggestion better; much more readable.

@Thaina
Copy link

Thaina commented Feb 21, 2017

I was once propose if! dotnet/roslyn#7927

There are some argument in there

@SamPruden
Copy link

I don't know that this suggested syntax really feels right for C#, but I agree it would be nice to have a clearer way to express the common pattern of negating a whole expression. The ! can be easy to miss at a glance.

@lachbaer
Copy link
Contributor Author

lachbaer commented Feb 22, 2017

I read dotnet/roslyn#7927 an there are some valid arguments in there.

Actually for beginners it might be a good idea, with respect to all C syntax derived languages, to stick with !, so they get familiar with it.

With using static one could easily write their own not operator, that looks nicer than !.

namespace MyTools
{
    internal static class BinaryLogic
    {
        public static bool not(bool expression) => !expression;
    }
}

and use it like

using static MyTools.BinaryLogic;

class AClass
{
    void ProcessValue(int value)
    {
        if ( not( value >= 0 )) { ... }
    }
}

I just put it in some places of existing code of mine. Looks good enough for me 😄

@DavidArno
Copy link

@lachbaer,

I think you just implemented your own proposal. I'd never thought of just adding a not function before. Stealing you code for my own use 😁

@lachbaer
Copy link
Contributor Author

@DavidArno You're welcome 😁

@SamPruden
Copy link

@lachbaer I like the way you're thinking with the static function, I'll probably steal that in a few places. So simple and nice.

I'm probably getting out of the scope of what really matters here, but it is worth noting that there's one (perhaps minor) disadvantage to this in that automated refactoring tools (think Resharper offering !a && !b -> !(a || b)) would struggle to recognise this pattern. Would it be worth putting static bool not(bool expression) => !expression somewhere in the core libraries so that tools can recognise it? That way code formatters and things could enforce style choices on it too. The compiler could also compile it away to a standard !, though I don't know if this would give any performance advantage over existing JIT inlining in this case.

I'm probably over engineering this one.

@jveselka
Copy link

I usually go with

public static class BoolExtensions
{
    public static bool Not(this bool b) => !b;
}

or simply condition == false because what really helps readability is having the most important parts of condition at the end especially when condition is more complex than simple variable access, e.g.

if (getDataServiceCallResult.MessageHeader.IsSuccess.Not()) { ... }

@scottdorman
Copy link
Contributor

So the not keyword would have to be a new contextual keyword and would just be syntax sugar for !, correct. That means

void MyFunction(int b) {
    if not (b>0)
        throw new ArgumentOutOfRangeException();
    
    bool finished = false;
    while not (finished) {
        // have your own interpretation here ;-)
    }
}

would simply compile to

void MyFunction(int b) {
    if (!b>0)
        throw new ArgumentOutOfRangeException();
    
    bool finished = false;
    while (!finished) {
        // have your own interpretation here ;-)
    }
}

I think my only change would be in the placement of where the not keyword appears and have it require parenthesis, so instead of if not (b > 0) it would be if (not(b > 0)) as I think that would allow better readability when chaining together multiple conditions:

// The not applies to the entire expression
if (not(b > 0 && c < 10)) { }

// The not only applies to the b > 0 expression
if (not(b > 0) && c < 10) { }

(That's effectively the same syntax as the extension method @lachbaer showed, just that it's part of the language syntax rather than an extension which means refactoring and other tooling would understand it.)

@MovGP0
Copy link

MovGP0 commented Feb 24, 2017

I guess this one should stay in VB rather than included in C#. You can always use local functions (#56) to do this:

void MyFunction(int b) 
{
    Func<bool, bool> not = val => !val;

    if (not (b>0))
        throw new ArgumentOutOfRangeException();
    
    bool finished = false;
    while (not (finished)) {
        // have your own interpretation here ;-)
    }
}

@Aurequi
Copy link

Aurequi commented Feb 26, 2017

What about adding the keywords 'unless' and 'until'?

void MyFunction(int b) 
{
    unless (b > 0)
        throw new ArgumentOutOfRangeException();
    
    bool finished = false;
    until (finished) {
        // do  stuff
    }
}

@HaloFour
Copy link
Contributor

I don't like the idea of throwing new keywords around that do nothing more than what existing operators do today. If I were to affect the syntax in any way to make this "more readable*", I might relax the syntax of condition expressions to allow for ! to appear outside of the parenthesis:

void MyFunction(int b) {
    if !(b>0)
        throw new ArgumentOutOfRangeException();
    
    bool finished = false;
    while !(finished) {
        // have your own interpretation here ;-)
    }
}

As whitespace is effectively optional here it would be the same as adding if! or while! keywords to the language.

* A painfully dubious term on which you're likely to find very little consensus.

@lachbaer
Copy link
Contributor Author

@Aurequi Me, too, don't like the idea of introducing too much new keywords.

@HaloFour The main idea was to make not expressions more verbose, so that they can be read more easiliy. As already confirmed by others in this thread, the slender ! can easily be overseen. But that syntax could be added additionally, because it would be logical.

@svick
Copy link
Contributor

svick commented Feb 26, 2017

@lachbaer I think the ultimate goal isn't to make negation more verbose, it's to make it stand out. Verbosity is one way to achieve that, but I think putting the ! outside parentheses works too.

@MovGP0
Copy link

MovGP0 commented Feb 26, 2017

@Aurequi logical statements should be positively formulated in order to make them more easily understandable. this reduces the error rate.

By this locic, I'd disagree with the unless keyword, but would agree with until (…) {…} and do {…} until (…) clauses.

@MovGP0
Copy link

MovGP0 commented Feb 26, 2017

besides, you should probably avoid to use while and do while clauses anyway and prefer techniques like LINQ or async/await to get a better understandable code.

@orthoxerox
Copy link

What if VS added a separate highlighting rule for ! to make it stand out? That would solve the legibility issue.

@MovGP0
Copy link

MovGP0 commented Feb 27, 2017

Usually the problem with hard to read not's in the code goes away with refactoring:

if (!(value >= 0))

becomes

if (value < 0)

and

if (!(b > 0) && c < 10)

becomes

if (b <= 0 && c < 10)

becomes

if (IsSomeNamedConditionMet(b, c))

@Thaina
Copy link

Thaina commented Feb 27, 2017

Problem is if (value >= 0) is not equal to if (value < 0) when value is nullable

@DavidArno
Copy link

@Thaina, I'd suggest that if you do if (value >= 0) without first testing if it has a value, then you are setting yourself up for a fall...

@Thaina
Copy link

Thaina commented Feb 27, 2017

@DavidArno Well sure I just point out that it not always the same

And sometimes it just subtle like

if(!(obj.child?[key]?.transform.position.y >= 0))
{
}

@lachbaer
Copy link
Contributor Author

@MovGP0

logical statements should be positively formulated in order to make them more easily understandable. this reduces the error rate.

Sorry, but I strongly disagree. Not always is the positive expression the one that is best understandable. Otherwise we wouldn't have the negation in our natural speaking.

You'll always say "When you don't do your homework immediately you'll be grounded" instead of "You might either go out or stay home after your homework is done". 😁 Or in code:

if (! homeworkIsDone)
    groundYourKid();
// instead of
if (homeworkIsDone)
    allowKidToEitherGoOutOrDoWhateverSHeLikes();

The main reason we write negative statements is that we think it that way first. And mostly even when reading code I'd go with the thinking the code shows to me.

@MovGP0
Copy link

MovGP0 commented Feb 27, 2017

@Thaina then don't do it!

Instead, refactor and name things properly. I use your example:

// find the 4 bugs in this code... 
if (objectTree.Child?[key].Transform(matrix).Position.Y >= 0)
{
    // ...
}

and refactor to

var elementOrNull = objectTreeOrNull?.Child?[key];
var transformedElementOrNull = elementOrNull?.Transform(matrix);
var positionOrNull = transformedElementOrNull?.Position;
var yPositionOrNull = positionOrNull?.Y;

// this now should be a no-brainer 
if (yPositionOrNull != null && yPositionOrNull >= 0)
{
    // ...
}

and then refactor to

var positionOrNull = GetPositionForTransformedElementOrNull(key, matrix);
var yPositionOrNull = positionOrNull?.Y;

if (IsPositive(yPositionOrNull))
{
    // ...
}

@lachbaer
Copy link
Contributor Author

About refactoring:
Sure it should be done somewhere during the developement process, but let's get rooted a bit, please.
While (until not 😉 ) you are writing your code you will normally not do refactoring on the go. Therefore I think it is really important to keep the code readable and understandable in that state, so that debugging before refactoring is already not misleading at a certain point.

Especially in that state the code looks a bit 'ugly' and the more opportunities there are to already formatting it understandable on the fly the better.

Not to mention, that often code once written stays that way. C# isn't only used in big programming companies 😉

By the way if (yPositionOrNull != null is a thorne in my flesh since the beginning of C#. Should be done something about it...

@DavidArno
Copy link

@lachbaer,

While (until not 😉 ) you are writing your code you will normally not do refactoring on the go...

Red -> green -> refactor.

If you aren't refactoring as you go, then that suggests you aren't using TDD. Why not? 😝

@lachbaer
Copy link
Contributor Author

@DavidArno Indeed I most often don't as I don't work on major projects for a living. And sure I am not the only one out there using C#...

@MovGP0
Copy link

MovGP0 commented Feb 27, 2017

@lachbaer "you are writing your code you will normally not do refactoring on the go"

This is a false statement. In a matter of fact, I do refactorings like this all the time. One of my mottos is "refactoring is thinking".

The above refactorings not only improve readability, but also decrease the cyclomatic complexity. So it also improves understandability and reduces the amount of testing I need to do.

@lachbaer
Copy link
Contributor Author

@MovGP0 Don't take 'you' personally (from Oxford: "Used to refer to any person in general"), in Germany we would say "man" meaning that there are a mentionable amout of people to who it applies.

@Thaina
Copy link

Thaina commented Feb 28, 2017

@jnm2 First, I compare it to !found. And !found is more understandable than count == 0

Second. Both false and == is keyword and operator need to understand. ! is only one

isFound is just a name so isFound equal false or not isFound is the same

And even said it out loud is Found equals false is very confusing with two verbs. not is found is just like wrong grammar sentence

@lachbaer
Copy link
Contributor Author

I reviewed this discussion. A big part is about how things could or should be written instead of changing the language specification. This seems a bit too starry-eyed to me! Sorry...

I won't say that many of the suggestions aren't really recommendable. But in the real world - that counts all programmers - a lot will write code a different way.

As we all might have experienced already on one or two days, the ! can easily be overseen. Others will most probably have the same issue now or then. The main question to me is, can the language make the appearance of this not expression stand out more?

I tried some code samples in the editor and must say, that just putting the ! in front of the (opening) parentheses already makes a big difference compared to putting it behind them.

if ( !isFoobar) { }
// vs. //
if !( isFoobar ) { }

That is, 1. because after if one expects the ( character, but this time it is !(. And 2. the opening curve of the paranthese goes away from the exclamation mark, opening the overall appearance.

Also other boolean operators (e.g. the proposed ?, see #196) could be put in that prior-position.

For the same reason I will vote for a until expression. Of course it could be written with while (! ...) (or while !(...)), but it wouldn't hurt and just make the expressions more human readable.
And remeber, actually we wouldn't need while anyhow, because it could be written like for (; condition ;), nevertheless we have it, we live with it, and - bets of all - we use it 😉

@DavidArno
Copy link

@lachbaer

I tried some code samples in the editor and must say, that just putting the ! in front of the (opening) parentheses already makes a big difference compared to putting it behind them.

It makes a big difference ... to you. To me, for example, the thing that stands out in both is your spaces inside the ( ). I always mentally stumble when I encounter a space after an opening parenthesis. Other swear by them. When I read if !( isFoobar ) { }, I'm then reading redundant () and want to "correct" it to if !isFoobar {}, which isn't valid syntax.

The point is, there is no correct form of a syntax that everyone finds the most readable. You, and others may well find if not (expression) or if !(expression) easier to read. Others, including myself, find if (!expression) easier to read. If I were in a minority of one here, there'd be benefits to it. But assuming I'm not, then a proposal to improve readability for some, at the expense of many others, doesn't have merit in my view.

@lachbaer
Copy link
Contributor Author

@DavidArno Actually ( and ) are also redundant in an if clause, from a parser's point of view. So, why not giving others - and I am not the only one, I can assure you! - the ability to make it easier for them? The difference between !( and (! for third readers isn't world changing...

@SamPruden
Copy link

@lachbaer

The more different ways there are to represent the same thing, especially when they're quite similar as these are, the more likely people are to misread them, in my opinion. Somebody used to !(condition) is probably more likely to miss (!condition) than people are already, and vice versa.

@lachbaer
Copy link
Contributor Author

lachbaer commented Apr 1, 2017

if and while only respect the operator true or an implicit boolean conversion.

If somebody implements operator true, he must also implement operator false, no matter if he ever actually wants to call &&.

A statement like if (!objWithOperatorTrueAndFalse) does not compile, as long as there is no implicit boolean conversion (e.g. by operator override).

Two more new keywords like ifnot and until could support an explicit call of operator false.

I will soon write more about the reason, why this could be of more importance than it looks on first sight. The reason lies in one of my favorite topics human factors.

@lachbaer
Copy link
Contributor Author

lachbaer commented Apr 2, 2017

Before I dive into the reasoning for respecting the human factors on this topic I want to tell that Roslyn is already partly prepared for supporting until loops. There already is an IWhileUntilLoopStatement that is used and an IsWhile property, that by now only returns true. But there is evidence that even the compiler team has thought about it.

Now about the human factors

When you review this, and other, discussion you might notice that there - objectively spoken - doesn't seem to be a right and wrong on the ways different programmers think. You can argue that there exist donts and dos on certain programming techiques, based on real world and code experience.

But when talking about the thinking of an expression flow, then we must respect the way our brains sort and coordinate those expressions. And as enlightend people we now that there is not only one way.

E.g. above there was a discussion whether conditions should mainly expressed and rewritten as being true. But some disagreed, because a negative expression seems more expressive to them in certain cases.

Why can it be so? One reason may lie in the cultural and language basis.
Different languages respect "true" and "false", or "positive" and "negative" expressions differently.

For example in Turkish a sentence can have multiple negative statements and the sentence still stays negative. In English like in German a double negative can make the whole expression a positive outcome, like in mathematics. But it ain't not necessarily so 😉

Social influences must also be accounted for. For some people the glass is still half full whereas for others the glass is already half empty.

So, long story short, I want to encourage you to respect these human factors more! Though @TheOtherSamP wrote in another context

The more different ways there are to represent the same thing, especially when they're quite similar as these are, the more likely people are to misread them, in my opinion.

  • and he is possibly right - well, that is how life is on this globalized world with all the different cultures and social aspects. Sometimes we cannot expect to only have one expression that satisfies all. Sometimes we musst twist our thoughts a bit as well, because the original auther had a different - for her the only logical - point of view in mind when writing the expression.

The reason for supporting ifnot instead of unless is, that there is not always a corresponding direct translation to different languages. In German it would be "solange" what has more a touch of a loop instead of a single evaluation.

Also the appearance of if... is so burned into the eyes that everyone will recognize it at once. Also the distinction between the short if and the longer ifnot is IMO big enough to see at one glance that this is the negating expression.

The support for until with respect to this explanation should be obvious. Also until is an old keyword in many other programming languages and I don't see any point that really counts against it.

I'm keen to hear your comments on this! 😃

@DavidArno
Copy link

@lachbaer,

The only comment I feel worth making at this stage is this: this proposal (at the time of writing) has 16 👎's and no 👍's, so you are flogging a dead horse here continuing to argue for it. Sometimes, we just have to accept in life that we lost the argument and move on...

@lachbaer
Copy link
Contributor Author

lachbaer commented Apr 2, 2017

@DavidArno
You see, that is the problem! You are only looking at the persons engaging in this discussion and you do not care about the common folks who "do not sit in the parliament".

@DavidArno
Copy link

@lachbaer,

If they do not join in the discussion, how can we know their views? Also, as there is a huge range of opinion amongst the folk who do come here, then I'd suggest it is extremely unlikely that - despite all that varying opinion being united in its opposition to this idea - that there are lots of folk out there amongst the silent, who agree with your proposal.

@lachbaer
Copy link
Contributor Author

lachbaer commented Apr 2, 2017

I think I'm going to implement these two instructions (as optional feature), as it will probably be not too much effort. Then people can see for themselfs how they feel in use.

@lachbaer
Copy link
Contributor Author

If have posted in another discussion something that makes use of ifnot and until

#453 (comment)

do
    ifnot ((line = Console.ReadLine()).StartsWith("#"))
        lines.Add(line);
until (line.StartsWith("$"));

@MovGP0
Copy link

MovGP0 commented Apr 19, 2017

@lachbaer this code is hard to understand. LINQ would make much more sense for this problem:

declare a helper:

private IEnumerable<string> ReadLinesFromConsole() {
    yield return Console.ReadLine();
}

declare some extensions:

public static IEnumerable<string> WithoutComments(this IEnumerable<string> lines) {
  return lines.Where(line => !line.StartsWith("#"));
}

public static IEnumerable<string> UntilEndOfScript(this IEnumerable<string> lines) {
    return lines.TakeWhile((line, index) => !line.StartsWith("$"));
}

Now you can write your intention in a clean and easily understandable way:

return ReadLinesFromConsole().WithoutComments().UntilEndOfScript();

@lachbaer
Copy link
Contributor Author

@MovGP0 That is an elegant solution with very litte letters and expressions for this very problem 🎃
I intended to point out that often something is better understandable (to one have of the people) when expressed a different way.

@MovGP0
Copy link

MovGP0 commented Apr 19, 2017

@lachbaer LINQ has the additional benefit, that you can implement some additional extensions to remove the !'s:

public static IEnumerable<T> Except<T>(this IEnumerable<T> enumeralbe, Func<T, bool> predicate) {
    return enumeralbe.Where(item => !predicate(item));
}

public static IEnumerable<T> TakeUntil(IEnumerable<T> enumeralbe, Func<T, int, bool> predicate) {
    return enumerable.TakeWhile((item, index) => !predicate(item, index));
}

@lachbaer
Copy link
Contributor Author

Lately I came to my senses 😉

C# is a C syntax derived language, as many other languages are. There is no until construct, also not in those other languages, and there may never be one.

If there is a negative "not" condition and you don't like its "lookings" then either

  • rephrase it to being positive (e.g. !(a < 0) to (a >= 0))
  • make an expressive boolean expression, while ((a < 0) == false)
  • use custom tool functions, if (not(a<0))
  • if you still have the urge for If Not and Until try other languages, like VB.NET for example

I'll keep this proposal for comments for some days before closing it.

@jjvanzon
Copy link

I like the proposed until keyword. I'd use it.

@lachbaer
Copy link
Contributor Author

I'd actually use it, too, and am not done with it. Recently I had evidence that it can prevent semantic errors, because of that human factors I have mentioned several times already. I will open another proposal that solely concerns until with some (hopefully) educated reasoning.

Can we start a poll on any web platform somehow and ask whether "'using' would be used if it was existing in C#"? (Not if one likes it, simply if one would use it or stick with 'while')

@jjvanzon
Copy link

Here's an argument for 'until': it can prevent some double negatives, which can introduce bugs due to poor readability. I don't have a good code example ready right now...

@lachbaer
Copy link
Contributor Author

lachbaer commented Apr 23, 2017

'until': it can prevent some double negatives

That depends on how one sees things. Assume a badly designed 3rd party with unfortunate property names.

while (!person.HasNoJob) person.GotoWork();

This is a double negation. You imply that because of the "negation operator" !, the name of it says it all: negation. For a better understanding you can instantaneously read it to your mind as or like "until obj.IsNotFull".

Or you can use a more verbose form, that eliminates the "negation operator" and lets the "IsNotFull" stay as the only negative statement:

while (false == person.HasNoJob) person.GotoWork();

Just because it has a longer and more verbose beginning while (false == it should be easier to convert it in mind to being "until". With while (! the word 'while' is more dominant in the depiction, giving your mind on first sight the impression of really meaning a non negating 'while', whereas 'whilefalse' already looks a bit like an indepentent keyword that is a synonym of 'until'.

We can argue whether 'while (false ==' can be seen in meaning as a double negation also or not. Because there will certainly be different opinions on that - and I guess by my guts that when asking programmers of different levels it will roughly be 50/50 - there can be no definite one and only answer on whether using 'while (!' or 'while (false =='.

Finally there is the possibility to create a simple tool function bool asUntil(bool value) => !value when you prefer a more verbose approach.

while (asUntil(person.HasNoJob)) person.GotoWork();

@lachbaer
Copy link
Contributor Author

lachbaer commented Apr 23, 2017

Ups, the whole twisting of negating here and there made me make a nonsense example in the post above 😁 I'm going to update it to something serious. Hold on...
Update: exchanged the sample code above.

@MovGP0
Copy link

MovGP0 commented Apr 23, 2017

the until can avoid some negatives even with properly designed APIs, so I'm all up for it:

until(person.HasJob) 
{
   // search and apply for a job
}

@lachbaer
Copy link
Contributor Author

@MovGP0 I was looking for double negations, one being the '!' and the other a property with a verbal negative identifier

@lachbaer
Copy link
Contributor Author

lachbaer commented May 8, 2017

A not keyword has no broad support and will not add any overwhelming benefits to the language, according to the participants.

However, an additional using keyword seems to be of interest to some. But that's another issue.

@arekbal
Copy link

arekbal commented Oct 20, 2018

C# is a C syntax derived language, as many other languages are. There is no until construct, also not in those other languages, and there may never be one.

https://en.cppreference.com/w/cpp/language/operator_alternative

Python has this alternatives as well - they are good.
As a sidenote... in JavaScript and TypeScript I tended to use !!! instead of single ! just so people woudn't miss it. I guess it could be optimized away by compilers but I assume they wouldn't bother.

Among new languages there is also a loop {} gaining a popularity as a replacement for while(true) {}.
funny how with C/CPP macros you can (but maybe shouldn't) define loop, until, and all these alternative operators.

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