Skip to content
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

Force a generic type to support numeric operations #3391

Closed
ghost opened this issue Jun 9, 2015 · 23 comments
Closed

Force a generic type to support numeric operations #3391

ghost opened this issue Jun 9, 2015 · 23 comments

Comments

@ghost
Copy link

ghost commented Jun 9, 2015

Sometimes we have to write a generic Sort way or some special functions with a generic type that must be of a numeric one. However we cannot say something like this:

public XXX<T> where T:ValueType

Though I know that int, long ,double……these basic elements come from ValueType. However I cannot do that because ValueType doesn't directly support operator comparation.

And If I make "T" forced to IComparer, IComparable……, here "T" doesn't ONLY a numeric one, any struct or class implemented this interface, it will have the "capability" to be pushed in.

And what's more——If my generic method/class have "+","-","*","/", only numeric type can do that.

So, it would be better if a generic type can be forced to a numeric one, and supports all kinds of numeric type, the numeric type should also include complicated struct such as "BigInteger".

It would be better if Microsoft can leave some special interfaces, virtual methods for us to create "customized data formation" and it will also be reguarded as "numeric type" (Maybe "Complex Number" is the future)

@whoisj
Copy link

whoisj commented Jun 9, 2015

Well there is where T : struct but it doesn't give you everything you asked for.

@ghost
Copy link
Author

ghost commented Jun 9, 2015

@whoisj :Yes that's just my problem——this only limits "T" is type of struct, however we cannot control whether the struct is of a numeric type that supports Sort, +,-,*,/……operators.....

@ghost
Copy link
Author

ghost commented Jun 9, 2015

And this is very similar to Issue (#3255), however mine is the extension for him——NOT only for simple elements, but customized elements (such as Complex Number, BigInteger……)——These structures are also numeric type, as well.

@AdamSpeight2008
Copy link
Contributor

I'd prefer to have the more generalised traits (#129). As it would allow to specify the operator the type must have implemented. Also @MaleDong be aware that the "comparison operator" are overloadable those don't have to the meaning of comparison. The contextual meaning of them is down to the type that is implementing them to decide.

@ghost
Copy link
Author

ghost commented Jun 10, 2015

In fact, I suddenly have a thought——To decide whether a number not, Maybe Microsoft can check:

Whether a customer has implemented "operator override" such as "+","-","*","/". —— If yes, this can be reguarded as a normal "Number" (fits the generic type's limitation).

So Let's just say:

    public class GeneralOperation<T> where T: Some Microsoft's Number Type Limitation Key Word
   { ……}  //"T" must be a struct type and has implemented "+","-","*" or "/".

And if the type to support Sort, just let us make it implement IComparable/ICompare ;)

@AdamSpeight2008
Copy link
Contributor

@MaleDong What is it about a type that makes that type a number?

Overriding - + * / isn't enough. Or it being with being a struct

For example Quaternion which is non-communitive eg A + B != B + A or A * B != B * A

Also maintain a list of types that are considered "numeric" in the compiler, means that if say Microsoft implement new numeric type in a library, you have to also update the compiler to include it in the list.

Even adding an attribute or interface to the existing "numeric" types is a breaking change.

So what is it about a type that makes it numeric?

@whoisj
Copy link

whoisj commented Jun 10, 2015

I suppose we could look for an intrinsic keyword to signify byte, sbyte, short, int, long, ushort, ulong, float, double, and decimal. Could be a rather useful thing when developing math libraries. Instead of always assuming everything will need to be double we have fun things like Vector<T> where T : intrinsic, Matrix<T> : intrinsic, etc. I suppose primitive, the problem is that "intrinsic" already includes char and "primitive" includes string and bool.

@ghost
Copy link
Author

ghost commented Jun 11, 2015

@whoisj:Your way is quite nice for numeric type.
However, what about if you can create new key words (something like "numberic" that will includes all of the basic elements that belong to number type like "int","double","decimal").

And I'm not sure what you mean by:

Instead of always assuming everything will need to be double we have fun things like Vector where T : intrinsic, Matrix : intrinsic, etc.

In fact, "T" is a generic type and if you use "int", the result and the intellisense is always "int".

@AdamSpeight2008
Copy link
Contributor

@MaleDong wrote:
In fact, "T" is a generic type and if you use "int", the result and the intellisense is always "int
Not true. If the T is constrained T : IComparable<T> inside that method I'll only see the methods available on the interface + those from the base type object

@dsaf
Copy link

dsaf commented Jun 11, 2015

This might have been half-implemented:

http://stackoverflow.com/questions/10951392/implementing-arithmetic-in-generics

Unfortunately, the type constraint does not allow you to require that the type supports arithmethic operators. What I find interesting is that in the BCL source code for e.g. Int32 you will find a IArithmetic interface in the inheritance list that is commented out. This is pure speculation on my part but if Microsoft enabled that interface in the BCL then you could perhaps specify IArithmetic as a constraint to allow you to write your own generic classes with arithmetic.

https://github.com/Microsoft/referencesource/blob/master/mscorlib/system/double.cs#L369

https://twitter.com/migueldeicaza/status/563452383993548801

It seems @davkean knows the details.

Suggested title change: "Please finish and release the IArithmetic interface" :)

@HaloFour
Copy link

I think that the best way to handle this is to have generic type constraints expanded to support specific static (as well as instance?) methods/members of specified signatures. That would cover overloaded operators, including those that affect heterogeneous types. Unfortunately as of now the primitive types don't actually specify those operator methods. They don't have to as the compilers know how to emit proper IL to perform direct addition of the primitive types. But to be usable generically in this fashion they would need the overloaded operator methods otherwise they wouldn't satisfy the constraint. The compiler would then be emitting IL to perform the static method call, which is far from ideal, but the JIT compiler is already smart enough to optimize those calls away.

I can imagine that the syntax for such constraints would be absolutely nasty, though.

public static T3 GenericAdd<T1, T2, T3>(T1 x, T2 y)
    where T1 : struct,
    where T2 : struct,
    where T3 : struct,
    where T1.op_Addition(T1, T2) -> T3
{
    return x + y;
}

Anything else just doesn't seem like it would fit in with the design of generics, specifically that a single member is emitted with one specific body that can be applied to any set of generic type arguments. Compile-time templates would be more flexible and could solve this problem but would pose other problems.

@whoisj
Copy link

whoisj commented Jun 11, 2015

In fact, "T" is a generic type and if you use "int", the result and the intellisense is always "int".

I was referring to how developers are forced, today, to develop math libraries against a know type (usually double) instead of being able to use a generic type due the lack of where clause keywords. I was providing a use-case for your request.

@HaloFour I really like that, though I'd prefer something more elegant. Maybe...

public static T3 GenericAdd<T1, T2, T3>(T1 x, T2 y)
    where T1 : struct,
    where T2 : struct,
    where T3 : struct,
    where operator +(T1, T2) => T3
    where operator +(T2, T1) => T3
{
    return x + y;
}

@AdamSpeight2008
Copy link
Contributor

Can you show an example function from this maths library (that some mention).

They tend to implemented as separate function as each type as it own subtleties, especially at the bit level.

@ghost ghost changed the title Force a generic type to be a numeric one. Force a generic type to support numeric operations Jun 12, 2015
@ghost
Copy link
Author

ghost commented Jun 13, 2015

Well……Thanks for your advices!

In order to simpify this problem:

  1. Define an interface or more interfaces to declare the numberic operation:
public interface INumericOperator<T> where T:struct
   {
    /// <summary>
    /// Add operator
    /// </summary>
    T Add(T t);
    /// <summary>
    /// Minus operator
    /// </summary>
    T Minus(T t);
    /// <summary>
    /// Multiplication operator
    /// </summary>
    T MultipledBy(T t);
    /// <summary>
    /// Division operator
    /// </summary>
    T DividedBy(T t);
  }
  1. Write a generic type wrapper class to call its inner Add (example here) and other implemented functions by offering static overrload operators.
     public class GenericOperationWrapper<T>
    where T:struct
{
    /// <summary>
    /// The actual value that is stored in the current wrapper class.
    /// </summary>
    protected T ActualValue { get; set; }
    /// <summary>
    /// The operation number
    /// </summary>
    public GenericOperationWrapper(T value)
    {
        ActualValue = value;
    }
    /// <summary>
    /// Do "Add" operation.
    /// Notice we should check whether "T" is a basic element such as int, double, decimal...
    /// </summary>
    public static GenericOperationWrapper<T> operator +(GenericOperationWrapper<T> t1, GenericOperationWrapper<T> t2)
    {
        //For some very basic thing, we can do like this following.
        //However maybe we can simpify this by some tricks by CLR and VS's intellisenses:
        if (typeof(T) == typeof(int))
        {
            int value1 = Convert.ToInt32(t1.ActualValue);
            int value2 = Convert.ToInt32(t2.ActualValue);
            object wrapper = new GenericOperationWrapper<int>(value1 + value2);
            return (GenericOperationWrapper<T>)wrapper;
        }
        //For other type, we must check whether it has implemented the interface or not
        //And call its inner implemented function.
        var iop = (INumericOperator<T>)t1.ActualValue;
        return new GenericOperationWrapper<T>(iop.Add(t2.ActualValue));
    }

    public override string ToString()
    {
        return ActualValue.ToString();
    }
}
  1. Implement your own customized struct like this:
    public struct ComplexNumber : INumericOperator
    {
    private double _real;
    private double _complex;
    public double Real
    {
    get
    {
    return _real;
    }
    }
    public double Complex
    {
    get
    {
    return _complex;
    }
    }
public ComplexNumber(double real, double complex)
{
    _real = real;
    _complex = complex;
}
public override string ToString()
{
    return Real + "+" + Complex + "i";
}

public ComplexNumber Add(ComplexNumber t)
{
    return new ComplexNumber(Real + t.Real, Complex + t.Complex);
}

public ComplexNumber Minus(ComplexNumber t)
{
    throw new System.NotImplementedException();
}

public ComplexNumber MultipledBy(ComplexNumber t)
{
    throw new System.NotImplementedException();
}

public ComplexNumber DividedBy(ComplexNumber t)
{
    throw new System.NotImplementedException();
}

}

  1. And now you can use something like:
    GenericOperationWrapper num1 = new GenericOperationWrapper(new ComplexNumber(1, 2));
    GenericOperationWrapper num2 = new GenericOperationWrapper(new ComplexNumber(3, 2));
    Console.WriteLine(num1+num2);

  2. Notice that my customized struct must implement the interface INumericOperator, and it will be reguarded to support "Numeric Operations". So what I want Microsoft do is (Maybe):
    5.1) Check if the generic struct type has implemented the interface INumericOperator
    5.2) If yes, when he/she is using +,-,,/, The CLR or VS's intellisense should also translate these operators to the actual methods and call them (something like step 4, the customized struct must be wrapped by GenericOperationWrapper when writing "num1+num2").
    5.3) For the very basic elements (such as int, float, double, decimal……), do as usual. It has nothing to do with the interface.
    5.4) For other situations, if two structs of the same type don't implement the assigned interface, and he/she has used "+","-","
    ","/", the compiler should intellisense him/her that you should either implement the static operators or just implement the interface.

@whoisj
Copy link

whoisj commented Jun 16, 2015

@MaleDong I do not think you can expect interfaces to support operators. It provides horrible performance. Operators are static methods where as interface methods are instance methods, requiring this and all kinds of other setup to be done.

I think the original discussion about supporting improved where clause capabilities is the right direction to go if this is to be supported.

@ghost
Copy link
Author

ghost commented Jun 17, 2015

@whoisj:The reason why I use interface is:

  1. We can customized our own "Numeric Type".
  2. Make a general operator class with all kinds of operations (such as +,-,*,/) .

In fact I think:

  1. When two struct instances are linked with "+".
  2. Check whether the 1st struct instance has the static operator function "+".
  3. If not, check if the 1st has implemented the interface and the two struct values of the same type or not. If either of the conditions isn't satisfied, VS intellisense will notify the user about the error.
  4. If yes, just wrap the two struct values with something like GenericOperationWrapper and do "+".

I know the reason why don't like that because of the conversion of some very basic elements such as "int","double","decimal"……. I think this is the core thing that may be recoded to avoid the "horrible performance".

And as what you said, could you please offer us an example (in persudo code) to explain how to use that in details about your thoughts of "where"? Thanks anyway!

@whoisj
Copy link

whoisj commented Jun 17, 2015

See my proposition for how generic constraints could work. I believe they'd produce optimal code as well given the current compiler logic. Notice that I do not actually care that the operators are performing math of any kind. Instead, I only ask the compiler to constrain the type to one which has defined the + operator as necessary.

public static T3 GenericAdd<T1, T2, T3>(T1 x, T2 y)
    where operator +(T1, T2) => T3
    where operator +(T2, T1) => T3
{
    return x + y;
}

In this case, the GenericAdd method takes two types as parameters, requires that they implement the + operator for each other, and result in a known third type which is unrestricted.

I could image this for any of the existing operators. Sure would make classes like vectors easier to write as well as compile time checked (instead of runtime).

public struct Vector3<T1>
    where operator +(T1, T1) => T1
    where operator -(T1, T1) => T1
    where operator *(T1, T1) => T1
    where operator /(T1, T1) => T1
    where operator +=(T1, T1) => T1
    where operator -=(T1, T1) => T1
    where operator ==(T1, Int32)
    where operator ==(T1, Double)
{
    // implementation ...
}

@gafter
Copy link
Member

gafter commented Nov 20, 2015

Related to #2147 and #2204.

@msedi
Copy link

msedi commented Mar 19, 2016

You are always talking about scalar values. But what happens when we have large arrays and we want to add them? Currently having math operation on two very long arrays works only well with unsafe code. Every other implementation I have tested failed completely.

Here I also need to have a pointer on a generic type. It would work well if the numeric type would also support pointers.

@whoisj
Copy link

whoisj commented Mar 21, 2016

But what happens when we have large arrays and we want to add them?

You're assuming that the types can be addressed by unsafe pointers. If the types are references types or contain references, they cannot be. How would you address that?

I've proposed a keyword, which could be added to structs to insure that they are unsafe friendly (and the compiler errors if they contain illegal types).

@ddevault
Copy link
Contributor

ddevault commented Dec 1, 2016

Registering interest for this feature

@whoisj
Copy link

whoisj commented Dec 1, 2016

Registering interest for this feature

@SirCmpwn there's a "Subscribe" button on the side, which should allow you avoid needlessly posted to get update notifications.

@ddevault
Copy link
Contributor

ddevault commented Dec 1, 2016

I am familiar with this button. It doesn't register interest, it subscribes to emails. Sending a notification to the participants revitalizes the discussion and draws renewed attention to the issue for planning.

@jnm2
Copy link
Contributor

jnm2 commented Dec 5, 2016

Registering interest for this feature

This leaves me feeling revitalized for sure! :D

@gafter
Copy link
Member

gafter commented Mar 24, 2017

We are now taking language feature discussion in other repositories:

Features that are under active design or development, or which are "championed" by someone on the language design team, have already been moved either as issues or as checked-in design documents. For example, the proposal in this repo "Proposal: Partial interface implementation a.k.a. Traits" (issue 16139 and a few other issues that request the same thing) are now tracked by the language team at issue 52 in https://github.com/dotnet/csharplang/issues, and there is a draft spec at https://github.com/dotnet/csharplang/blob/master/proposals/default-interface-methods.md and further discussion at issue 288 in https://github.com/dotnet/csharplang/issues. Prototyping of the compiler portion of language features is still tracked here; see, for example, https://github.com/dotnet/roslyn/tree/features/DefaultInterfaceImplementation and issue 17952.

In order to facilitate that transition, we have started closing language design discussions from the roslyn repo with a note briefly explaining why. When we are aware of an existing discussion for the feature already in the new repo, we are adding a link to that. But we're not adding new issues to the new repos for existing discussions in this repo that the language design team does not currently envision taking on. Our intent is to eventually close the language design issues in the Roslyn repo and encourage discussion in one of the new repos instead.

Our intent is not to shut down discussion on language design - you can still continue discussion on the closed issues if you want - but rather we would like to encourage people to move discussion to where we are more likely to be paying attention (the new repo), or to abandon discussions that are no longer of interest to you.

If you happen to notice that one of the closed issues has a relevant issue in the new repo, and we have not added a link to the new issue, we would appreciate you providing a link from the old to the new discussion. That way people who are still interested in the discussion can start paying attention to the new issue.

Also, we'd welcome any ideas you might have on how we could better manage the transition. Comments and discussion about closing and/or moving issues should be directed to #18002. Comments and discussion about this issue can take place here or on an issue in the relevant repo.


Type classes would support this feature request, and are under consideration at dotnet/csharplang#110

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants