-
Notifications
You must be signed in to change notification settings - Fork 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: Add invokeable?(...)
as short hand for invokeable?.Invoke(...)
and add support for any "invoke-able" type
#3257
Comments
Overlaps or duplicates #95 |
Searched and searched but didn't search for "functor" 🤦♂️😅 |
I have a feeling the parser isn't going to like this, since while the statement is still incomplete, it'll have to guess between "is this a null-safe invoke of an invokable?" and "is this an unfinished conditional expression ( |
We can also go with a slightly different syntax like |
THe former is already legal. i.e. |
@CyrusNajmabadi True, maybe something similar, dunno. |
Well doesn't duplicate 100% so keeping this open, since I think both null-coalescing operator and extending the usage ties together. Which brings me to null-coalescing operator issues.
In what cases would this be ambiguous? The type of the One interesting case would be what happens in the face of implicit conversions. E.g. given a type: public struct FooBoolean
{
public bool Value => false;
public int Invoke() => 42;
public static implicit operator bool(FooBoolean foo) => foo.Value;
} this does not work currently if public int? N(FooBoolean? foo) => foo ? (42) : 16; it fails with the following error: It works fine when public int? N(FooBoolean foo) => foo ? (42) : 16; But in neither case is there any intellisense help or suggestions when writing Hence, overall I don't see an issue here. :) |
No, types are not known in the parser. Parsing determines the syntactic structure of the expression before its types can be determined. |
Does that mean that you think parsing |
At the syntax level, |
Yes I can see that it is definitely not easy at parser only level :) Alright, feel free to close this PR if null-coalescing invocation operator is deemed impossible. The other part of this proposal seems to be already covered. |
@gafter isn't that just an operator precedence issue? we don't complain about many other issues where the syntax has alternate interpretations depending on the implied brackets: |
I don't think this is true. Precedence tells me, once I've parsed the operators, where do I put the brackets. Here we don't even know how to parse the operators. |
i don't see that it is impossible to parse? Given
then to parse the following expression: |
This is valid today and doesn't fit those rules:
|
When it comes to the parser, it will definitely increase parsing time when using the ternary operator and the My suggestion is to have this opened again, as it will definitely come in handy, especially in the cases of invoking events or event-like delegate memberes, which are quite common in certain applications. Especially given the fact that multicast delegates are |
|
This is indeed a case that I had not thought of, however I believe it could be specially handled from the compiler. This will always pass the parsing process and the resulting syntax tree will contain one of the two possible scenarios. Then, if the compiler detects this syntax tree, it may yield an error stating the ambiguity of the expression, and asking that the extra parentheses be removed or have more be added to indicate order. |
I think the question would be whether or not it'd be worth that effort given that there already is a way to invoke these methods. With autocomplete the amount of manual typing to call the However, if invocation operators were to become a language feature then maybe it might be worth reconsidering, especially if that operator didn't map to an easily referenced method name. |
Theoretically, the |
@jnm2 Re:
After further consideration and exploration on the compiler's end, even this expression is not fully ambiguous. It surely is ambiguous at a parsing level, but the binder can resolve the ambiguity easily. The reason is that any experssion preceding the For this to happen, a new type of node would have to be supported, declaring syntax ambiguity resolvable during binding. And in this specific scenario, that node would reflect ambiguity between Ideally, the syntax tree should not be updated, exposing the pre-binding ambiguous nodes, allowing warnings to be reported, suggesting explicit clarification of the ordering. |
C# today does not have ambiguous syntax, and we are not interested in adding anything to the language that would change this. |
EDIT: the comment below was naively posted right before considering the catastrophic case I've given this a bit of thought and discovered a way to discover ambiguities during parsing, without relying on the binder. Ambiguities in this case are only caused in scenarios where ternary operators and nullable invocation operators are mixed before getting to the false expression part of the ternary operation. In other words, As to how the parser recognizes this, unfortunately the only solution is looking ahead up until the end of the last
This algorithm will only have to be executed upon coming across a QP token sequence, and not for every expression. Tokens before the starting sequence are ignored. Below are examples of this algorithm for the aforementioned ambiguous example, and its unambiguous variants. Execution only begins from the points of discovering the QP token sequence.
|
@alfasgd the syntax is simply too ambiguous. But necessarily in the sense that a computer could not figure it out, but in the sense that it's too confusing for humans to read/write. I look at the examples and I have no idea what they mean without staring intently for a long time running the parsing algorithm in my head. That's not desirable for us. |
@CyrusNajmabadi However, that's not an argument against doing something that's otherwise valuable. You can always take valuable features and design something horrific like |
I disagree, and I don't think the two scenarios are comparable. The |
Ah, gotcha. Even when the entire statement in view is: |
Wait until you hear about the more complex case that I'm still wrapping my head over when it comes to parsing.
This proposal has been in the heads of the entire community for a while. Its use cases are everywhere, especially in business logic. The feature's syntax cannot be anything other than The way I see it is, either support this feature, with all the awkward parsing issues, or never bring a small piece of consistency into the language due to those complications.
All expressions can get complicated pretty easily. Even this simple expression: It's in our hands to not abuse features. The proposal intends to help in simple scenarios where a null-checked invocation could be simplified down to something shorter. And I repeat, nobody should write code that includes monstrous expressions like the above. Even mixing ternaries with null-conditional invocation sounds atrocious. The best advice is to properly parenthesize and space out expressions and symbols. And this applies to everything, especially a feature that introduces potential ambiguous syntax, which will be safely discarded with a compiler error. |
Achieving consistency is not a goal of the language, especially at the expense of clarity. |
Isn't |
JavaScript recently introduced the optional invocation syntax |
IMO, no. You always have to scan ahead to see what the rest of the expression contains, and if the expression is anything more than a trivial invocation that will be challenging. Even if rules can be applied to always disambiguate in the parser that will never help with the human parser. I just don't find having to include |
I would be potentially ok with this. |
I disagree with this. The syntax does not need to be
Nope! :) And that's the core concern. Up through C# 10 i could always read that and know what it meant by checking teh single character after the Furthermore, it means that when code is incorrect (say i mistype something, or put a |
I agree with this too. It reads fine, and is easy to write, review, and maintain. If we truly got enough of a belief that this was worth investing into (which the current vote count doesn't seem to imply) then i'd still want a syntax that was easy to parse without my own brain having to do lookahead. So that would mean something like |
I'm seeing the issues One alternative option (though ugly) I see is |
we're pretty opposed to implicit-lookup of identifiers. in all our discussion so far, we've wanted a sigil to indicate that's happening. like |
Proposal: Add
invokeable?(...)
as short hand forinvokeable?.Invoke(...)
and add support for any "invoke-able" typeIt is proposed to extend and generalize the
delegate(...)
invocation operator to reduce the amount of repetitive code and support new scenarios for succinct invocation of types following a specific pattern. Such a type is referred to as an "invoke-able" type in this proposal and is any type having any method calledInvoke
or types made invoke-able via extension methods. Hence, C# should use structural matching for theinvokeable(...)
andinvokeable?(...)
invocation operator and not match on whether a type is a delegate.There are two parts to the proposal:
action?()
()
to structurally match any type with one or moreInvoke
methods available incl. generic methods and extension methods, perhaps even static types.See Member access operators for existing operators.
In this proposal examples are in C# 8 with nullable enabled.
Motivation
A common and easy pattern to use for logging is to simply use/inject a delegate
Action<string>
. Allowing this to benull
with the meaning that logging is disabled. This is simple to implement and has low coupling. But is also a bit tedious currently in C#:compared to how this is if this couldn't be null:
Proposed Solution
Hence, the first part proposes changing this to allow the null-propagation operator for invocation. It already exists for methods
?.
and indexers?[]
and it seems natural to extend that so the following is allowed and is simply a short hand for?.Invoke(...)
. With null-propagation being exactly the same.delegate()
just emitsdelegate.Invoke()
in IL so this does not change generated code.Other examples:
Similar Patterns
Below are examples of existing null-propagation for comparison.
Extended Solution
Since, the invocation operator
()
for delegates is simply a short hand for.Invoke()
, it is also proposed to extend the invocation operator()
for any scenario where a type or instance has any method calledInvoke
available. Whether it be a member method or an extension method. This helps a lot in patterns that define first class invoke-able types. Hence, the invocation operator should be available as soon as the compiler can find a structural match. Examples are given in the two sub-sections.In fact, this could also be extended to static classes with static methods called
Invoke
. One could simply invoke this directly. This is up for discussion and not part of the main proposal here. Example can be seen below.what benefit would there be to this... don't have any good examples now which is also why this is not part of main proposal.
Member method example
the above example is a pattern I have used a lot for numerical libraries where one need to apply a "functor" to each element in an "array". By defining a value type
TFunc
one can get method inlining which is cruzial for performance. The desire is code generated specifically for thatTFunc
. Trading code size for performance. Often this involves unrolling loops and hence a lot of.Invoke(
in the code. This is just an example and the main point is this gets more succinct with the invocation operator being available for the types involved. Performance will not change in any way for this proposal. IL is the same.Extension method example
Above shows how this would also work for extension methods.
Future
In the future C# might get static delegates, traits and hopefully many other things. By extending the invocation operator and adding null-propagation invocation operator these will make it natural to invoke any type or instance that is invoke-able. Hence,
Action<>
andFunc<>
like types get easy to use invocation operator support.There already is a proposal to extend
using
to be based on structural matching, which makes a lot of sense in the face ofref
-types. cc: @gafter#1623
Generally, I think it would be preferable to direct C# towards being based more on "patterns" rather than exact type matching, since that creates a direct coupling between runtime types and language. When possible this would make C# more "free" and open to other use cases. Letting new code patterns could emerge.
The text was updated successfully, but these errors were encountered: