-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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: Poisonous Null-checking #5961
Comments
For something that can completely change what a line of code does, I think this syntax is too subtle. If I see |
I agree. All of the other null-coalescing behavior starts from a root expression and collapses from there. This wants to do so from a child expression. But given how a function may accept any number of nullable arguments and any combination of them may be a reason to avoid the function call I don't know of an obvious shorthand syntax. What about the case of nested function calls? |
@svick That's actually the same subtle thing that cause a line of code fails to do what it supposed to do. so you can just be sure this way that it won't. Although, I just proposed a syntax consistent with other null-checking operators so they can be used together as you can see in my examples. If you use a keyword, for this, that would not look like a null-checked statement anymore. @HaloFour That's the point. A single F(arg1?, arg2?, arg3?, arg4?);
if( arg1 != null && arg2 != null && arg3 != null && arg4 != null )
F(arg1, arg2, arg3, arg4); By the way, in my last example, properties will evaluate just one time. Since you haven't args right where you need them like the code above. |
@alrz Honestly, neither. The |
@HaloFour Honestly, that wouldn't happen in real code but in some cases, for one or two variable, null checks will cause to lose focus on doing the actual work and make the code mostly irrelevant. Specifically if variables have long names for readability. This syntax follows the same motivations as |
I don't see why |
@alrz Because that is the purpose of the null propagation operator. It doesn't make sense to apply to non-null operations. If you want the end result to be something other than null that's why the null coalescing operator |
At best I could see some kind of syntax like this working if #5445 gets off of the ground (oh dear IPU I hope not). Then it at least would make sense to have a second forwarding operator like bool result = Foo.Bar?.Baz ?> F ?? false; |
@HaloFour returning a btw, I liked that |
@alrz The Forward operators provide absolutely no functionality or even shorthand compared to what we have today. They're only really useful for very specific chains of functions. Once you have multiple parameters or |
@orthoxerox what is this |
@HaloFour Re "Returning |
@alrz If I remember correctly, that's the "cast to non-nullable or throw" operator from the nullability tracking proposal. |
@orthoxerox I don't see such a thing in #5032. Anyway, this won't throw any exception, because if |
@orthoxerox No kidding. Scala is about as fun to read as an ASCII sneeze, and don't get me started on Java interoperability. But function piping avoids no more objects than nested function calls and C-family languages have been fine with that for decades. Or we have extension methods which make rewriting that as fluent quite easy. Your assumption is exactly why I find this proposal confusing. You don't decorate the method call, but it affects the method call. bool result = Bar(foo?);
// equivalent to
bool result = (foo != null ? Bar(foo) : default(bool)); |
@HaloFour With #5444 you can do more with forward pipe, for example void F(Foo foo) => foo :> Foo::InstanceMethod :> Console.WriteLine(format: "Result: {0}"); It's not limited to nested, static or single parameter functions. It's all about order of execution and code style. Whatever you prefer. But about confusion you referred, yes I assumed that the operator bool result = checked(Bar(foo?)); Obviously, some other keyword. But you've got the idea. |
@alrz It is exactly the same as nested function calls, except that sourcing multiple parameters from expressions is messier. And your proposal for supporting multiple parameters separately from using tuples by using named arguments is only more confusing since it blends the two forms. There are dozens of languages targeting the CLR. If my preference is for COBOL-like syntax, I don't expect C# syntax to become COBOL. But anywho, that horse is beaten and I really don't have constructive comments regarding forwarding, so I'll leave it to the language design team to consider whether it achieves the 200+ points necessary for consideration. |
assuming some sort of null checked operator (
should be the same as
aside from the idea that the former is thread safe and the latter is not (foo could potentially be changed between the conditional evaluation and the method call). This feels like postfix conditions in perl except they are potentially even more difficult to identify. 👎
If the forward pipe operator comes in (#5445) I would prefer also having a conditional forward pipe operator and the syntax
(you would need to do semantic analysis to determine what the syntax tree for that statement is; this is even worse with extension operators: #4945) |
@bbarry With the proposed syntax you can do that: |
The operator // null-checked member access
var result = obj ?. Member;
// null-checked expression
var result = obj? .Member; Actually what is proposed here is a generalized version of null-conditional operators. So there are two options left:
Introducing |
@gafter I wonder why the team didn't consider this at first place and specialized it just for member access and indexers? |
That makes no sense. Null propagation doesn't work without member access of some sort. It's how that works in the variety of languages that already support it like Swift and Oxygene. What is The |
@HaloFour Yes that doesn't make sense. Because you actually do null-checks when you want to use a value. whether in a member access or function call or whatever. Returning |
Except that null propagation propagates |
#3718 is just a form of declaration expressions with a more complicated syntax. It doesn't make sense in that context either as |
Since let-in syntax followed by an expression, if
Exactly, why null propagation makes it a special case for value types while it doesn't make sense to continue the propagation after yielding a |
@alrz Which means that you're amending the purpose of that proposal. Null propagation propagates nulls. The null of a value type is |
I think wanting a |
Being common isn't relevant, providing the choice to the developer is, and that is only possible if the expression returns This is the exact same behavior as with Apple Swift. Optional chaining always results in an
|
@HaloFour unnecessary cast I mean.
This is exactly what is recommended to avoid in any production design. It should make sense not just providing a choice for the user. The point is, you can live without |
Casts are cheap, as is the overhead of unwrapping a
Wait, are you seriously arguing that a "production design" programming language should eliminate choices for the developer?
Which is exactly why |
Yes they are, but the team still prefer to not use it for a reason, right?
Yes, check out the closed issues. Anyway, so you are against the first bullet too? Regarding backward compatibility. |
You'd have to ask them. My guess would be that C# 6.0 features are very underrepresented in the Roslyn codebase, primarily because the majority of that code was written before those features existed and they didn't bother rewriting it.
There is a big difference between deciding to not adopt unnecessary features or syntax which does not fit the language vs. designing those features with a specific locked down and inflexible behavior.
Your first bullet involves removing an existing feature. Considering any new feature starts with a score of -100 I'd think that a feature predicated on changing the nature of an existing feature would immediately incur quite the penalty. You'd have to demonstrate an extraordinary amount of value in this proposal in order to even break even. |
@HaloFour It doesn't change the nature of the existing operators, it just generalize them by segregating them into one new operator and the existing one. Actually, if that happens it would be a smart design with backward compatibility in mind. That's why introducing |
@gafter you got me there 😄. but I think this is still possible without changing the existing behavior. |
@gafter "won't fix" as it's not possible or it's not worth the effort? I was really looking forward for this, it would simplify a geat deal of repetitive code and save some property/method calls along the way (my third example). |
@alrz I'd rather crush your hopes now than leave you hanging forever 😉 I have a lot of concerns about this - how far up the expression does this operator infect things; how does it affect the type sysem; how does it affect readability; what syntactic issues arise from a postfix I can't imagine anyone on the language design team championing this proposal, and that is what it would take for this (or any other proposal) to happen. |
The way to do this now is to wrap it in a try/catch and throw using the ref?.property ?? throw syntax. |
With a lot of null-checking features introduced in C# language up to this version (and more on the way), there is some cases that still need for null-checking (assuming that it is a valid value so it's not addressed by #5032).
For example
value
inF(value);
can benull
butF
can't be called with anull
(in case of a non-nullable reference type), or shouldn't (throwing exceptions). So you need to check fornull
before calling it likeIn cases like this, there should be a feature in which you check for a
null
on an expression and if it's anull
make it a poison value for the rest of the expression. e.g.so
F
wouldn't be called ifvalue
isnull
.If
F
is not avoid
-returning function, then the whole expression would benull
.You can see how this would be useful.
The text was updated successfully, but these errors were encountered: