-
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]: Expand the use of 'not' as a generalized inversion operator in C# #5976
Comments
Assigning to @333fred who expressed the most interest in seeing this happen. I believe we have the time in the current release to get started on this and have prototypes out soon for consumption. If necessary, work on current features should likely be postponed to front load these options instead. If we cannot get these in now though, i suggest waiting a year and restarting work on them at that point. |
I don't make scheduling decisions, that's an @jaredpar question. I just write the notes. |
Adding @jcouv who has expressed feelings of dissatisfaction with the current working set, and who has been itching to write more proposals for features that will be more beneficial to the language. Also, a few more |
We might need a followup |
It depends on the representation. I think we could have an imaginary64, which is built out of two floats (for the real and imaginary components). This would allow modern 64bit architectures to efficiently execute these instructions. @tannergooding to weigh in on the viability of this (as well as the above proposal and how it might help numerics). Given that numerics are entirely built out of pairs of complimentary operations (like |
I dub thee the "Borat" operator and look forward to seeing it haphazardly rushed into C# 11. I also think that this should be a postfix operator: if (this.Suit is Black not) ; |
Is there an update on the status of this feature? It's been over 20 minutes since it was posted and ages since anyone from LDM has even acknowledged it. Other languages have had this for years now. Does Microsoft even care about C# anymore???? |
I am escalating this up the chain of command as fast as i can. It's a friday, so most people are not even working. Will try to post updates as i learn more. |
Can confirm, I'm going to be setting up an FRC event this afternoon. |
Linking to relevant TypeScript proposal which we may want to consider alongside this. Though there is a misspelling in their title. It should be " |
I really like the direction with this proposal. I think my only concern is that you need to keep the syntax space composable. For example, you listed a few examples between dual operators like Dual OperatorsIf you wanted to move closer to So using your examples above, the following void Yadda()
{
continue
{
_myComponent3.Attach("...");
_myComponent2.Start("...");
_myComponent1.Acquire("...");
}
} would desugar to public Yadda()
{
_myComponent1.Acquire("...");
_myComponent2.Start("...");
_myComponent3.Attach("...");
} Stacking OperatorsAlternatively, if you want to be consistent with void Yadda()
{
not not
{
_myComponent3.Attach("...");
_myComponent2.Start("...");
_myComponent1.Acquire("...");
}
} would desugar to public Yadda()
{
_myComponent1.Acquire("...");
_myComponent2.Start("...");
_myComponent3.Attach("...");
} |
I'm not so sure this is a good idea, but I'm not not for thinking about it more. I'm sure the set of examples that @CyrusNajmabadi gave is not not not exhaustive, and I'm not not not not sure the community will think of other even more motivating examples and I can't wait to see those. I'm liking the syntax here, since it's not not not not not like other recent proposals we've had where using ! too many times can get confusing. It also does not not not not not not allow repeated use of a token over and over again, which @jaredpar always appreciates. |
Hrmm. I hadn't considered that @jasonmalinowski. That definitely makes me wary as my brain is pretty terrible at understanding double negatives |
Glad to see first-class support for |
I am not looking forward to implementing this syntax in the C# textmate grammar. |
Kind of a given, nobody likes implementing any syntax in textmate grammars. |
Thanks for assigning me to have a look @CyrusNajmabadi. While I think you've covered the statement case well have we considered that we may want the ability to |
Can Roslyn provide elision regions in the editor to combine |
April fools is over. Closing out. |
Expand the use of 'not' as a generalized inversion operator in C#
Summary
Expand 'not' operator to be viable in sensible places in the language beyond just patterns.
Note:
not
doesn't have to be the operator we pick. If we feel like!
is more suitable we could go with that. However, based on the reaction to!!
i am somewhat wary about overloading the meaning of!
anymore. So I think usingnot
will be the most palatable and will read particularly well.Motivation
A programming language in general (and C# in specific) often has the need to relate entities in their domain to sets (for example, the set of booleans, the set of types, the set of contraints etc.). In these cases the language provides very suitable constructs for testing set containment. However, while less common, it is also just as reasonable to want to relate entities to the inverted form of that set. After all, such an inverted form is still just as reasonable a set on its own as the other set, so supporting that case would make certain problem spaces more pleasant. In some cases in the language today it is trivial to do this manually. Small finite sets, for example, can be inverted just by listing out all the members of the inversion of the set manually.
Programming languages recognize the common need for this with expressions. For example, the need to test if something is not in the set
0 or 1
, leading to trivial operators like!(x == 0 || x == 1)
. However, where this gets painful though is either when the inversion is extremely large. It also becomes impossible if the inversion is infinite as there is no way to enumerate all the case cases in the language constructs today. Patterns need this routinely, which led to the addition of thenot
pattern a generalized mechanism to do set/matching inversion. I propose expanding on the use of this operator to all set-based contexts in our language today in a clean, clear and composable fashion.Realistically, we will likely not be able to get such support in one version for all the cases where it is possible. However, we should consider them all and front load the ones that make the most sense, while also thinking about where we might want to expand on this in the future. We can drive particular cases now and in teh future based on community feedback and how important/applicable the use cases are to partners and the ecosystem as a whole.
In practice, my belief is that the
type
space is the most fruitful one to start with for set inversion. We technically already support type-inversion today in patterns through things likeif (x is not Y)
. However, types are replete in our language in other locations, and I believe those other locations would benefit greatly from having this construct. The first location for this would be in parameters. For example:Logically, we want one method to be the catch all that is used when the caller truly has no idea what they have (for example, if they're using reflection/dynamic/other-slow-things). However, if they do have a strong type, we want to follow another path where we can assert that if htey were working with a strong type that it must follow certain sensible rules that indicate they know what they're doing. For example, in XLinq this occurs as the set of non-object supported types is fixed, but annoying to always spell out. While union types could solve this, the user would have to spell out often a large set of types, which would be extremely unpalatable). However, this does raise an important question about how
not
is to be interpreted. There are two viable perspectives, which are complimentary, but subtly different. The first is thatnot
gives you the inverted set (e.g. everything other than what was specified). The second is thatnot
disallows a particular entity (or set of entities). These may appear the same, but can mean very different things. As an example, consider the following:For this to be sensible at all, the first line likely cannot mean "Pull in every namespace that is not
Newtonsoft.Json.Linq
". However, we should consider supporting just that even though it might seems initially absurd (and likely impossible to even pull off). Perhaps there are some problem spaces where it makes sense to be pull in everything (especially where people own the entire domain under their control). For example, one might doimport not System;
to get every symbol they own, and nothing from the BCL. However, even if we don't go that route it might be reasonable to imagine projects that want to prevent accidental usage of a particular namespace with similar concepts in certain parts of the codebase. Here, for example, we want to ensure that only the platform Json processing components, while not calling out into a different library (which may have subtly different semantics). While analyzers could suffice for this purpose, i believe there has been enough feedback from customers asking for a lightweight way to disallow usage of particular Apis to warrant a very simple construct to enable that like the above.Of course, as we consider this space further, the possibilities for how we can use this operator become more and more interesting and exciting. In the BCL, Roslyn, and Language space, we've constantly run into issues into how to prevent certain APIs from being used (e.g. referenced in source), while also ensuring that programs do not break wrt to their ABI. This ensures that as people recompile they move off of these 'banned' APIs, while ensuring consumers that cannot recompile continue to work. In the past we've attempted to accomplish this with varying levels of success (honestly, total failure) through features like
EditorBrowsable
,Obsolete(error: true)
and modreqs. The problem with this is that all of these required some amount of cooperation between components and languages in the ecosystem. We have cases where all of the above are just flat out ignored (including in varying versions of Roslyn compiler itself) leading to painful migration and transition stories for components. In line with recent discussions around thefile
scope, we would now allownot
to be used as a scope like so:Obviously, this would only be viable if it solves the needs of both needing to preserve ABI compat while also breaking all source recompiles. To that end, this makes use of a extension of type-forwarders that the runtime is working on called member-forwarders. The implementation strategy here for Roslyn would be that the above would now be generated with a completely mangled name in an entirely different namespace/type. All these namespaces/types/member-names would be entirely mangled (similar to what we do today when we emit
<PrivateImplementationDetails>
). We will also emit aMemberForwardedFrom
attribute (similar to TypeForwardedFrom) to inform the runtime where this member was originally. At dll-load-time/member-resolution-time, the jit/runtime would synthesize the appropriate method on the type ensuring that binary compat was maintained. This would be an airtight way to ensure source would have to be updated as the only way any other tool could possibly use this was if their compiler actively sought out to subvert these mechanisms (as opposed to things like modreqs where compilers can easily accidentally forget to check, and end up thinking something is allowed when it is not).Lastly, the final place i think we could find the most value in a
not
feature and should consider front loading into an upcoming release is a new pattern around designing APIs with common 'inverted' operations. Just as operators often come in pairs (+
vs-
,*
vs/
,checked
vsunchecked
), there are often lots of APIs that have pairs of useful operations that are compliments of each other. For example:Read
andWrite
are virtual inverses of each other. Similarly,Parse
andFormat
.Serialize
/Deserialize
.Save
/Load
.Attach
/Detach
,Expand
/Collapse
,Insert
/Delete
,Enable
/Disable
,Start
/Stop
,Acquire
/Release
,Cos
/Sin
,Math.Pi
/Math.E
. etc. etc. etc. These patterns are so common that we do not give a second thought to the common redundancy in naming and code organization here. To that end, i propose an extremely powerful new mechanism both for defining these pairs of constructs, as well as then using them from code:Similar to the auto-prop
field
proposal, this enables the pairing of similar functionality (like with property accessors) while also giving those paired members the ability to share state that other members of the type cannot see. For example, the above code may have abool attached
local they can use and assert on, without having to worry about other members of the type examining (or worse yet mutating) that field. This is effectively the property-pattern expanded to methods, but not restricted to simple value get/set. Note: if the signatures of theinverse
operation need to be different that will be possible through things likenot (string information, int otherData)
. Calling these members is of course necessary. With properties, it is clear contextually if a read, write or both is happening. With normal methods though, that cannot be determined syntactically. To that end, in line with the declaration syntax, the invocation syntax can be written as:! While this might look a little strange at first, it's reall no different from using
not
or!
for any other sort of negation to pick another choice. Users will soon pick this up and understand this well, and i think the virtue of not having to come up and use antonymous names for these pairs will be embraced wholeheartedly. Users hate repetition (the source of the DRY principle). So now instead of needing to have the repetition in things like Serialize andDe*serialize*
(which also have odd inconsistencies in casing), you now simply haveSerialize
andnot Serialize
. All pairs of inverse concepts now can always have a single name with a single consistent pattern in terms of how to define them together and easily call them. Indeed, if we imagine common compose/dispose sequences, the inverse sequence is simply thenot
form of of the compose sequence in reverse. To that we end, we might even consider one final form to tie this all together:Alternatives
The above cases just scratch the surface of where I think
not
will be viable in the language. In the interest of targeting use cases that the community finds most useful though, we should seek out as many additional cases as possible. So, if you are reading this and can think of places you'd like us to broaden the language with, please list them below. All ideas are welcome as i think this will be a big boon to c# and will certainly not (har har) be a detriment to the language. Thanks!The text was updated successfully, but these errors were encountered: