Open Charlieface opened 3 months ago
Tagging subscribers to this area: @dotnet/area-system-numerics See info in area-owners.md if you want to be subscribed.
I believe that this was considered and rejected at some point, cc @tannergooding
Note that bitwise operators on bool
does not short-circuit, unlike the logical operators. Sometimes they are used intentionally. So operator &(bool left, bool right) => left && right
is incorrect and confusing.
@huoyaoyuan
Note that bitwise operators on
bool
does not short-circuit, unlike the logical operators. Sometimes they are used intentionally. Sooperator &(bool left, bool right) => left && right
is incorrect and confusing.
The fact that &&
is used internally in the function seems immaterial to me, it will still be non-short-circuiting (and therefore side-affecting) in the case of functions. Only for variables and fields being passed in then the JIT and/or CPU could still short-circuit it, as it is not side-affecting.
The instances when &
and |
are used on purpose are small enough that you would want to know exactly what the CPU is doing anyway (eg cryptography side-channel problems).
I believe that this was considered and rejected at some point, cc @tannergooding
It wasn't outright rejected, it was just considered out of scope for the initial Generic Math
feature work since it wasn't directly related to numeric types. The same was true for implementing IEqualityOperators
on other types.
I'd in general be fine with taking this back to API review and think it would fit in well with some of the Tensor<T>
work that's happening as it would allow Tensor<bool>
to work with the helpers like Tensor.And
.
CC. @stephentoub for secondary input.
What's the most derived numeric abstraction we might consider for bool? IBitwiseOperators fits the mould, technically speaking we could go as far as having it implement IBinaryInteger (in the sense of boolean operations being the same as modular arithmetic mod 2)
Should this be using explicit interface implementations?
I don't think we'd want IBinaryInteger
, unlike char
which explicitly has support for the full set of numeric operators in C# and which often warrants using those; bool
really only supports the bitwise and equality operators.
There's also a lot of subtle quirks that come into play with regards to how IL vs C# view bool
. Where IL says 0
is false
and any other bit pattern
is true
, while C# says 0
is false
, 1
is true
, and any other bit pattern
is undefined
.
This can lead to quirks in some cases since 2
is a valid bool
in IL and so from that perspective, x + y
should be equivalent to operating over byte
, but that breaks for various downstream operations and expectations when encountered.
So, I think we'd only want to implement the underlying interfaces that make sense and then only the APIs which are appropriately constrained could be invoked (which we already try to fit the minimum interface set needed).
@eiriktsarpalis (also @huoyaoyuan relevant to what we mentioned):
What's the most derived numeric abstraction we might consider for bool? IBitwiseOperators fits the mould, technically speaking we could go as far as having it implement IBinaryInteger (in the sense of boolean operations being the same as modular arithmetic mod 2)
I think there are a couple of completely missing interfaces here as well, because &&
and ||
short-circuiting are implemented in C# via true
and false
operators.
public interface ITruthOperators<TSelf>
where TSelf : ITruthOperators<TSelf>
{
static abstract bool operator true(TSelf val);
static abstract bool operator false(TSelf val);
static abstract TSelf operator !(TSelf val);
}
public interface ITruthIdentity<TSelf>
where TSelf : ITruthIdentity<TSelf>
{
static TSelf TruthyIdentity { get; }
static TSelf FalseyIdentity { get; }
}
And then bool
would have:
public static bool operator true(bool val) => val;
public static bool operator false(bool val) => !val;
public static bool operator !(bool val) => !val;
public static bool TruthyIdentity => true;
public static bool FalseyIdentity => false;
This would allow short-circuiting &&
and ||
operators to work on anything implementing both ITruthOperators
and IBitwiseOperators
.
Having said that, I tried it, see this fiddle, and for unclear reasons I got CS0217: In order to be applicable as a short circuit operator a user-defined logical operator ('IBitwiseOperators<T, T, T>.operator &(T, T)') must have the same return type and parameter types
Because of the potential alignment with Tensor<bool>
here, I'm putting this into the 10.0.0 milestone so we make sure to consider it for the release. If that alignment with Tensor doesn't materialize though, we might move this back to Future.
Background and motivation
System.Numerics
and the interface operators feature has allowed us to generic-ize over arbitrary numerical types, to use operators and functions such as+
*
Zero
. One of those interfaces isIBitwiseOperators
, which allows the operators&
|
^
and~
. This is implemented by all the integer types.I propose that
Boolean
should implement this as well, even though it doesn't implement any otherNumerics
interfaces. It does have all those operators, and this would allow it to be used anywhere that you specifyIBitwiseOperators
.The motivation is writing generic code that can do arbitrary bitwise operations. For example, I'd like the following LINQ extension, which mimics the
bit_and
aggregate function in some SQL implementations.But this won't work with
bool
and requires a separate implementation just for it.API Proposal
API Usage
As mentioned, bit aggregation operations are the most useful usage.
ReadOnlySpan
would be good to implement for alsoMerge adjacent runs using
&
An
IMP
function (logical implication)Alternative Designs
No response
Risks
What happens to the C#/Roslyn compiler in the presence of such functions, where
bool
is used directly? Does it use those functions, or is it hard-coded to use IL bytecode instead? This would be a similar problem to any other of the numeric interfaces, if it is actually an issue.