Open gafter opened 7 years ago
I wonder if this would still be needed if we did type classes (shapes) See https://github.com/dotnet/csharplang/issues/110 and https://github.com/dotnet/csharplang/issues/164
Related to this is the generic cast. This is currently not possible in C#, but feels like it should be:
public sealed class NullObject
{
private NullObject() {}
public static readonly NullObject Instance = new NullObject();
public static implicit operator List<T>(NullObject o) => new List<T>();
}
Sorry for the dumbness of this example. It's all I could think of right now.
I'm entirely behind this, I've run into plenty of scenarios where this is necessary to reduce bloated and unclear code.
See also #108, #612
Just wanted to bring this up, as shapes and extensions have nothing to do with this feature request, as it talks neither about extending an existing type (extensions), nor about generalizing over static members, which the underlying type system does not support (shapes).
It also seems to work fine in F#, both declaration and consumption, without any special IL generation.
The C# LDM considers it an important feature of the language that every place the language infers a type, you can also explicitly give one. This proposal does not include any way to explicitly provide the type argument at the invocation site. If this proposal were extended to have a way to do that, we don't believe we would like it.
@gafter for the explicit type argument would it be possible to simply allow invoking the method via ClassName.op_Addition<T>(..., ...)
?
Update: Mixed things up badly. Just disregard.
@lostmsu
Just wanted to bring this up, as shapes and extensions have nothing to do with this feature request, as it talks neither about extending an existing type (extensions), nor about generalizing over static members, which the underlying type system does not support (shapes).
It also seems to work fine in F#, both declaration and consumption, without any special IL generation.
At least we'd have a way to abstract over numerical operations (by having an Add
static method for every numeric type that maps to +
). Or am I missing something deeper?
@gafter
This proposal does not include any way to explicitly provide the type argument at the invocation site. If this proposal were extended to have a way to do that, we don't believe we would like it.
Is this meant as strongly as it reads?
Is this meant as strongly as it reads?
The proposal was put in the Likely Never milestone, and moved to the Rejected column in LDM triage. It's meant as strongly as it reads.
@gafter for the explicit type argument would it be possible to simply allow invoking the method via ClassName.op_Addition
(..., ...)?
"Simply"? Um, That would require a breaking change to name lookup. See https://sharplab.io/#v2:C4LglgNgNAJiDUAfAAgJgAQEECwAoA3nusesgMykCMAbFugPYAOA+pjDGMGPQHYAUmdAENKUOkNQBKdAF4AfMMoBuPAF88eNOgBCILHkK4SpCsho6GjAKYAnIcHo308PtsVi3E6fMUrc63E0MMwB2AyISNFQI4kNjYzcAI1l0HgBXCAg/eJJtADomVnZObn5EsUTJJXQAehr0AGcAC3oMmHQAYyFMrAKWNg4uXhj0AICgA== for a program that would be broken.
@gafter I don't understand your example. Can you explain what would be broken?
Here I am declaring op_Addition
in B
the same way I would declare operator+
, and the emitted IL stays the same. Why having it with operator
would be any different?
My understanding from the generated IL is that unless operator+
in B
is generic, the code you provided will correctly pick A.op_Addition<T>
over B.operator+
, because it does so over B.op_Addition
already.
And if you are talking about the case when the new generic operator will hide A.op_Addition<T>
, then it is not a breaking change, because that can't exist in the current code.
If invoking something called op_Addition
would invoke something declared operator +
, then the program at https://sharplab.io/#v2:C4LglgNgNAJiDUAfAAgJgAQEECwAoA3nusesgMykCMAbFugPYAOA+pjDGMGPQHYA8AFQB8ACkzoAhpSh0JqAJToAvEMmUA3HgC+ePGnQAhEFjyFcJUhWQ1DDRgFMAThOD1H8EQbUyvcxSrVNXB1cPQxrAHZTIhI0VBjiMwsLLwAjZXQeAFcICCDkkgMAOiZWdk5ufjAeYFFUmVT5fJIQkKA= would change behavior.
Oh, you are actually referring to this case: https://sharplab.io/#v2:C4LglgNgNAJiDUAfAAgJgAQEECwAoA3nusesgMykCMAbFugPYAOA+pjDGMGPQHYAUmdAENKUOkNQBKdAF4AfMMoBuPAF88eNOgBCILHkK4SpCsho6GjAKYAnIcHo34fbYrGuJ0+YpW51uTQwzAHYDIhI0VHDiQ2NjVwAjWXQeAFcICF84km0AOiZWdk5ufgSxBMkskn9/IA= where treating operator+
as op_Addition
would cause B.operator+
to be called instead of A.op_Addition
without code change.
Maybe C++ style ClassName.operator+(left, right);
?
I just want to mention that generic operators in combination with global using static ...
would allow for some really dumb shennanigans, such as the following "custom range syntax" (which already compiles):
foreach (int i in -6 -to- -4)
{
Console.WriteLine(i);
}
foreach (long l in -2 -to- 2)
{
Console.WriteLine(l);
}
foreach (char c in 'a'-to-'e')
{
Console.WriteLine(c);
}
string[] names = { "Sally", "Bob", "Joe", "Eric", "Patty" };
foreach (string s in names.Where("Allen"-tᴏ-"Kevin"))
{
Console.WriteLine(s);
}
Source code: https://gist.github.com/ZacharyPatten/3b7d7d63bc5188d3f7c863639f14765d
With generic operators the To1<T>
would not need to be generic. It could just be To1
and the operator could be generic instead. Then you would only need one static to
variable to work with any type.
Be careful what you wish for. :P
Another example could be measurement unit syntax. Stuff like:
var speedDecimal = 5m * Meters / Seconds;
var speedFloat = 5f * Meters / Seconds;
var speedInt = 5 * Meters / Seconds;
The new static abstracts in interfaces
feature is enabling the libraries to expose support for generic operators. The API surface and design work for that is here: https://github.com/dotnet/designs/pull/205
@tannergooding from my understanding it does not. That feature is completely orthogonal to this feature request. The new operator interfaces do not cover the scenario in the head post where non-generic class Class
tries to implement operator +
that accepts a generic second argument T
(the exact operator here is not important):
class Class { operator+<T>(Class a, T b) => ... }
The proposed interfaces simply do not work in this case, cause you would have to put specific type in place of T
here:
class Class: IAddition<Class /* TSelf */, T /* TOther */, SomeResultType>
{
...
}
But the problem is that T
is not a single type, and class Class<T>
would have a totally different semantic.
@lostmsu what's the use case such an operator would be solving?
@CyrusNajmabadi frankly, I don't remember by now what use case I originally had for this feature. A simple example I can come up with is implementing lisp-like tuples with +
concatenation operator. E.g.
var sfTuple = new Tuple<string, float>{ Head = "hi", Tail = 0xDAD };
var isfTple = 42 + sfTuple;
struct Tuple<THead, TTail> {
THead Head;
TTail Tail;
public static Tuple<T, Tuple<THead, TTail>>
operator +<T>(T value, Tuple<THead, TTail> tuple)
=> new () { Head = value, Tail = tuple };
}
But in general any construction of objects with different generic parameters than input.
The reason I dug up this issue this time was an attempt to implement the pipe trick:
using System;
using static Pipe;
int f(int v) => v + 10;
int v = pipe(10)
.pipe(x => x * 2)
.pipe(f);
public static class Pipe {
public static Pipe<T> pipe<T>(T value) => new Pipe<T>(value);
}
public struct Pipe<T> {
public T Value{get;}
public Pipe(T value) => Value = value;
public static implicit operator T(Pipe<T> pipe) => pipe.Value;
// the DSL would be much better
// if the method below could be replaced with `operator |<TResult>`
public Pipe<TResult> pipe<TResult>(Func<T, TResult> op) => new (op(Value));
}
Hi, there is a simple workaround using implicit operators
.
It just needs the one implementation of such an operator that works for all comparisons and more, and that's good.
But of course it's not as fast and explicit as with dedicated comparison operators.
I use this technique extensively in many variations of it in my project RationalNumerics. It saves a ton of code - unfortunately for NET 7 we have to write all conversions explicitly to comply with the new coding rules. That relativates all the good new options into it's opposite. What nonsense.
Good examples for the workaround are Float80, Float96, Float128 that all based on a template Float<T> but also more efficient performant types e.g a new BigInteger is not based on templates but uses them. The technique with the implicit operators
allows for example simple interfaces like ISimpleNumber what can easely replace System.Numerics.INumber with all the complexity for the implementation to use and the run-time overhead. Based on templates, this means that type creation is done only when needed and greater flexibility for custom types, e.g. Float64 without a line of code or fast dedicated implemetation like Float128. It works all with this technique and with a dedicated full implementation we get also all the inline effects required for best perfomance.
Simple code snippet for the approach:
struct MyTempl<T>
{
T data;
public static implicit operator MyTempl<T>(T value) => new MyTempl<T> { data = value };
public static implicit operator MyStruct(MyTempl<T> value) => value.data is int x ? x : default; // this for template
}
struct MyStruct
{
int data;
public static implicit operator MyStruct(int value) => new MyStruct { data = value };
public static implicit operator MyStruct((int x, int y) value) => new MyStruct { data = value.x }; // this for tuple
public static bool operator <(MyStruct a, MyStruct b) => a.data < b.data;
public static bool operator >(MyStruct a, MyStruct b) => a.data > b.data;
}
static void test_MyStruct()
{
MyStruct a = 1, b = 2;
if (a < b) { }
if (b > (1, 2)) { }
if (b < (1, 2)) { }
MyTempl<int> c = 3;
if (b < c) { }
}
@lostmsu what's the use case such an operator would be solving?
In my case I want a simple implicit conversion from MyClass<U>
to MyClass<T>
. This can't be done.
class MyClass<T>
{
public static implicit operator<U> MyClass<T>(MyClass<U> x) { ... }
}
using MyClass<int> Foo;
using MyClass<uint> Bar;
Foo x = new Foo();
Bar y = x;
@c-ohle This won't work if MyStruct
is generic as well.
I also want generic operators. I wonder why this isn't possible as it is for methods. The new static abstract interfaces don't change anything about this.
Just because I haven't seen it mentioned, this feature would allow matrix multiplication with named axes:
public static Matrix<Dim1, Dim3> operator *(Matrix<Dim1, Dim2> m1, Matrix<Dim2, Dim3> m2)
Dear all, If this feature is implemented, it paves the way for a beautiful syntax identical to the mathematical formulas one writes on paper or pseudocode. Linear Algebra operations of vectors and matrices and even sparse matrices become finally readable and as efficient as hand-coded expressions using for loops. Take for example:
Matrix<double> A, B;
Vector<double> a, b, c, x;
double alpha, beta, gamma;
x = gamma*c + alpha*A*b - beta*B*b;
Expression generics with generic operators will allow the syntax above avoiding operator overloading which results in temporary object (that consume a lot of memory for large array sizes > 10^7).
C# will open the door for a huge number of scientists and engineers using Fortran at the moment or C++ because such clean and clear syntax is only supported since many decades in Fortran and a few years ago in C++ with expression templates. Furthermore, complex<double>
data types operations will become significantly more efficient due to avoiding temporary objects speeding up any application that involves mathematics with complex Numerics. Guys the number of applications is huge, just to give you an idea I list a few,
these researchers currently use Fortran or C++ or languages that allow syntax closer to the mathematical expressions (Matlab, Julia), due to the complexity of the codes and formulas involved. Why not encouraging them to join our community and benefit from all this great machinery C# provides (extremely fast compilation, ease of coding, static analysis, memory safety, ...)? I would even go a step further and recommend that C# by design adopts natively such operations on its arrays double[], and matrices double[,]. Just so people who work in the areas above have nothing to lose switching to C#.
+1 for this feature.
Generic Math is currently incomplete without a generic operator
I am expecting operator should be generic like => where T: GenericOperator if required different types of operators like arithmetic operator, Comparision operator, so on along with operator overloading for generic operator will be also useful in case of same logic for multiple operators.
+1
Generic Math is currently incomplete without a generic operator
I wouldn't agree it is incomplete nor that having generic operators is a "good design" for many cases.
Not only is there considerable nuance lost when talking about things like casting operators between arbitrary generic types; but there is a high amount of risk in terms of bugs and untracked behavioral issues when talking about cases like TSelf
can be added with any U
given a constraint.
A lot of this boils down to what is API design that goes against the Framework Design Guidelines. Therefore being things where even if they existed, it is highly unlikely that the built-in interfaces that define the core of "generic math" for .NET would ever utilize them. Of course external libraries are free to define their own types and considerations, but the general integration with the rest of .NET is likely to suffer.
For conversions, you have to consider things like:
TSelf
and U
implement a conversion, which should be called
Then for other operators, you have to start considering things like whether an implicit conversion from U
to TSelf
makes sense. Something like operator +<U>(TSelf left, U right) where U : INumber<U>
would be "bad", because if U
was int
and TSelf
was byte
, you get a loss of data. That could be silent, it could be throwing, and it could slip in very subtle bugs and other issues.
Not all convenient things are good. There was a deep consideration of many of these concepts and whether or not they were fundamentally "required" when the Generic math
feature was designed/implemented. The cases where you may want to support doing an operation between two different types are still possible (for example, you can define a Complex<T> * T
operator still), they are just limited in a way that is overall reasonable and ultimately helps keep devs away from nasty pits of failure.
That doesn't strictly mean that adding generic operators shouldn't happen either, just that features like generic math are likely not the golden ticket to making that happen as they aren't a representative use case for the majority of users working with the feature or types implementing the feature.
@dmitriyse commented on Fri Dec 18 2015
Currently c# does not support generic operators definition like that:
This support can be usefull to build advanced libs with expressions (validation expressions in my case).
This is only C# limitation, CLR ready for this feature. We can already define operator in different syntax.
No changes in CLR needed to define generic operator in this syntax:
but later C# does not recognize it.
Please add this proposal to the C# 7.0 wish list.
@tpetrina commented on Fri Dec 18 2015
I would love this feature. Non-generic operators are limiting.
@dsaf commented on Sun Dec 20 2015
Related to #3391 and #2147.
@msedi commented on Sat Mar 19 2016
Yes. In my opinion this is now absolutely necessary. Having only generics that are not fully equivalent to C++ generics (in functional behaviour) are absolutely mandatory. I guess there are many guys out there requesting this feature. Instead a lot of people here are complaining about syntactic sugar.
@orthoxerox commented on Tue Apr 05 2016
Might help solve the problem of verbose generic patterns discussed in #10153. If
is
was generic the pattern could infer its generic type.@grwGeo commented on Thu Jul 21 2016
I have needed this feature trying to model mathematical entities and it is a definite must. It will save tons of repetitive code and make the conceptual model richer. Also type inference would do wonders in this scenario.