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

Method level constraints in generic type parameter for contra and covariant returns #3702

Closed
juliusfriedman opened this issue Jul 17, 2020 · 22 comments

Comments

@juliusfriedman
Copy link

juliusfriedman commented Jul 17, 2020

Consider this valid interface declaration:

public interface Test<out T>{ T Value {get;}}

The motivation of this proposal is to simplify programming by not having to define trivial interfaces just to use in or out constraint for generic methods which should be equally easy to implement:

For example:

public static ToArray<out TElement>(params TElement[] elements){
       return elements;
   }

Usage

ToArray(1, 2.0, 3.0d, 4.0m)

Additional way of implementing or in addition to with different meaning

I think that covariant and contravariant returns should be signified at the method level just as they are allowed in the interface definition for example:

Most basically:

public static class SomeExtensions{
public static M<in T>{
       return elements;
   }
public static M<out T>{
       return elements;
   }
}

Also

public in static M<T>{
       return elements;
   }
public out static M<T>{
       return elements;
   }

Or more crazy

public in static M<out T>{
       return elements;
   }

Should compile? (probably not..)

public out static M<in T>{
       return elements;
   }

Prior similar art (Math related) but I am not a math a mathematician... sorry in advance

Number

Consider

public static TRadix Add <TRadix>(TNumber number, TRadix radix) where TRadix : Radix{
...
}

What about if I have a class:

public class Radix<in T>{}

This class is not valid (CS1960)

I can't even define static extension classes for it that have these semantic and for no good reason IMHO.

using System;
public class C {
   
   public class Number{ 
       public Radix Radix {get;} 
       public static implicit operator Radix (Number n){return n.Radix;}
       //public static implicit operator Radix<T> (Number n){return n.Radix;}
                      }
    
   public class Radix{ Number Number {get;} public static implicit operator Number (Radix r){return r.Number;}}
   //1960
   //public class Radix<in T> : Radix{}
   
    //0311
    public class Radix<T> where T : Radix<Number> {}

//cs0406
    public class Radix<T> where T : Number, Radix {}

// just to compile
public class Radix<T> where T : Number {}
}

//It would be extremely useful to allow in/out semantics on SomeOperation especially if it's generic
public static class RadixExtensions
{
    public static C.Number SomeOperation(C.Radix<C.Number> a, C.Radix<C.Number> b){
        return new C.Number();
    }
   public static C.Number SomeOperation<T>(C.Radix<C.Number> a, C.Radix<C.Number> b)
        where T : C.Radix<C.Number>
    {
        return new C.Number();
    }
}

The logic routine is already in the compiler, I would be able to implement this by myself in probably less than 6 months including spec modifications with some help on just the spec writing part and maybe a point or two to some code.

@svick
Copy link
Contributor

svick commented Jul 17, 2020

I don't understand what you're trying to achieve or what the proposal is.

Some of the things that confuse me:

  • Variance is useful if you have a type like I<T>, but you want to treat it as I<SomeOtherT>. You don't seem to be doing anything like that.
  • Variance doesn't work on value types, but your usage example seems to be doing something with those. (Though it's not clear to me what.)

@juliusfriedman

This comment has been minimized.

@dstarkowski
Copy link

I'm not sure I understand this either.

What's the use case? In your example you take an array and return exact same array. What does that achieve? What would you expect the return type of ToArray method to be?

@svick described use for variance. I don't see how does that relate to your proposal. Could you maybe provide better example?

What is C# Java? Do you want people to have to implement an interface with a virtual call to support inheritance? Then lets have anonymous objects which can auto implement interfaces and I will shut up.

Is this tone really necessary?

@Unknown6656
Copy link
Contributor

What is C# Java? Do you want people to have to implement an interface with a virtual call to support inheritance? Then lets have anonymous objects which can auto implement interfaces and I will shut up.

@juliusfriedman I do not understand what you meant by that.

@333fred
Copy link
Member

333fred commented Jul 17, 2020

@juliusfriedman I've hidden your comment as it's not relevant to this issue, and is approaching the edge of the .NET Code of Conduct.

Your ideas are getting a lot of pushback. I understand that that can be frustrating, especially since they make sense to you. Perhaps a better solution would be to join either the gitter or the csharp discord and talk through your proposals with someone. Realistically, 99% of the proposals on this repo are never going to be implemented, so anything that you can do to make your proposals more attractive and understandable to LDT members is only going to benefit them.

As to this proposal, I'll join the others commenting in that I don't really understand what co/contravariance on a method type parameter would get you. There's no inheritance here, and variance only comes into play when we're talking about inheritance hierarchies.

@juliusfriedman
Copy link
Author

I don't understand what you're trying to achieve or what the proposal is.

Some of the things that confuse me:

  • Variance is useful if you have a type like I<T>, but you want to treat it as I<SomeOtherT>. You don't seem to be doing anything like that.
  • Variance doesn't work on value types, but your usage example seems to be doing something with those. (Though it's not clear to me what.)

I don't want people to have to implement an interface with a virtual call just to run a method with already available semantics just at a method level instead of the interface level.

Even if we have anonymous objects which can auto implement interfaces I would still likely want a way to control the variance of the type and I don't want to define an interface just to define the method semantics I want when I can use that already valid syntax at the method level and have it to the same thing, thus supporting multiple different return type scenarios depending on the method and type you pass rather than having an interface PER method you need.

@juliusfriedman
Copy link
Author

@juliusfriedman I've hidden your comment as it's not relevant to this issue, and is approaching the edge of the .NET Code of Conduct.

Your ideas are getting a lot of pushback. I understand that that can be frustrating, especially since they make sense to you. Perhaps a better solution would be to join either the gitter or the csharp discord and talk through your proposals with someone. Realistically, 99% of the proposals on this repo are never going to be implemented, so anything that you can do to make your proposals more attractive and understandable to LDT members is only going to benefit them.

As to this proposal, I'll join the others commenting in that I don't really understand what co/contravariance on a method type parameter would get you. There's no inheritance here, and variance only comes into play when we're talking about inheritance hierarchies.

At this point I don't know why you spent years implementing covariant returns when it's already widely supported in generics but that doesn't seem to matter anymore as the feature is done and 📦

@juliusfriedman
Copy link
Author

juliusfriedman commented Jul 18, 2020

What is C# Java? Do you want people to have to implement an interface with a virtual call to support inheritance? Then lets have anonymous objects which can auto implement interfaces and I will shut up.

@juliusfriedman I do not understand what you meant by that.

  • Do you want anonymous interfaces like java has, e.g. IDisposable x = new IDisposable { void Dispose() { ... } } ?

Sure I could use them along with devirtualization and have a good time.

  • Do you request duck-typing generic constraints like F# has?

Eh, no probably not

Yes but not the way it was done, generics supported this for a long time.

  • Do you want Contravariant Returns? Because this does literally make no sense and I do not see any use cases....

Yes we already have this, return object.

@CyrusNajmabadi
Copy link
Member

At this point I don't know why you spent years implementing covariant returns when it's already widely supported in generics but that doesn't seem to matter anymore as the feature is done

Variance is only supported today with interfaces your parameters and delegates. We added support for covariant return types so you can also use them in classes and normal interfaces on their members.

It expands the locations where this is supported (honestly to the place people have been asking about the most and for the longest).

@svick
Copy link
Contributor

svick commented Jul 18, 2020

@juliusfriedman

I don't want people to have to implement an interface with a virtual call just to run a method with already available semantics just at a method level instead of the interface level.

Can you share an example where that is necessary? Because I don't see it.

Consider a trivial method with a contravariant parameter and covariant return type:

interface IMap<in TInput, out TOutput>
{
    TOutput Map(TInput x);
}

class Identity<T> : IMap<T, T>
{
    public T Map(T x) => x;
}

You can use the variant interface like this:

var map = new Identity<Stream>();
IMap<MemoryStream, object> variantMap = map;
    
var input = new MemoryStream();
    
object o1 = variantMap.Map(input);

If I understand you correctly, you want to treat Map as variant, but without having to go through the IMap interface. Well, you can, the following is perfectly valid code, thanks to implicit conversions:

object o2 = map.Map(input);

What am I missing?

@juliusfriedman
Copy link
Author

@juliusfriedman

I don't want people to have to implement an interface with a virtual call just to run a method with already available semantics just at a method level instead of the interface level.

Can you share an example where that is necessary? Because I don't see it.

The method ToArray in the example above demonstrates it for the class Number because (not show) Number is implicitly converted from or to any primitive type such as int or double or decimal etc.

If you look at the linked code there is the context.

I also provided the definition of what works and what doesn't.

Consider a trivial method with a contravariant parameter and covariant return type:

interface IMap<in TInput, out TOutput>
{
    TOutput Map(TInput x);
}

class Identity<T> : IMap<T, T>
{
    public T Map(T x) => x;
}

Co variant

You can use the variant interface like this:

var map = new Identity<Stream>();
IMap<MemoryStream, object> variantMap = map;
    
var input = new MemoryStream();
    
object o1 = variantMap.Map(input);

You don't it seems, let me re-write pretty much what I have above....my proposal here is to allow the grammar which exists and by virtue of that fact the rules of the spec to work at any generic method level not just the generic interface.

If I understand you correctly, you want to treat Map as variant, but without having to go through the IMap interface. Well, you can, the following is perfectly valid code, thanks to implicit conversions:

object o2 = map.Map(input);

What am I missing?

Everything.

@juliusfriedman
Copy link
Author

juliusfriedman commented Jul 18, 2020

At this point I don't know why you spent years implementing covariant returns when it's already widely supported in generics but that doesn't seem to matter anymore as the feature is done

Variance is only supported today with interfaces your parameters and delegates. We added support for covariant return types so you can also use them in classes and normal interfaces on their members.

It expands the locations where this is supported (honestly to the place people have been asking about the most and for the longest).

That's only because of how you chose to implement it, yes I can see it being useful at the return type I guess but it was already possible via the interface definition and it's methods as @svick has shown above.

All I am proposing here is to allow generic methods (which can already be defined on generic interfaces) to exist without the interface in the first place, so I can therefor have a static class which offers those methods without needing an interface and then to define my extension class on that interface.

I will be a bit arrogant and say your problem I think is that you don't know how to make it work when T isn't defined on the class and maybe a different type for the method TT but the solution is the where constraint IMHO, where as now we rely on inheritance hierarchy... and there could be a default interface which causes lots of trouble in those scenarios... look at the diamond pattern problem.

@svick
Copy link
Contributor

svick commented Jul 19, 2020

@juliusfriedman

The method ToArray in the example above demonstrates it for the class Number because (not show) Number is implicitly converted from or to any primitive type such as int or double or decimal etc.

Except variance is not about user-defined implicit conversions, it's pretty much only about base types and derived types. And even then, it only works on reference types, while your example is all about value types. If you're proposing to change some or all of that, you need to explicit about it.

Also, that example looks like it's more about changing how type inference works (see e.g. #450 for some previous discussion about that), because, with your implicit conversions to Number, the following code compiles:

ToArray<Number>(1, 2.0, 3.0d, 4.0m)

If you look at the linked code there is the context.

I don't think it's reasonable for you to assume we'll read 3000 lines of code, just to attempt to understand what probably could be explained in at most a page of text.

my proposal here is to allow the grammar which exists and by virtue of that fact the rules of the spec to work at any generic method level not just the generic interface

I don't understand what this means.

@Unknown6656
Copy link
Contributor

Unknown6656 commented Jul 19, 2020

I will be a bit arrogant and say your problem I think is that you don't know how to make it work

@juliusfriedman This tone is not helping anyone -- especially not when using it against members of the language design and implementation team.


  • Do you request duck-typing generic constraints like F# has?

Eh, no probably not

Then I still do not quite get your proposal, to be honest. (I am not trying to be an ass or anything .... I literally do not get it, even though I re-read the entire conversation 😕)

  • Do you want Contravariant Returns? Because this does literally make no sense and I do not see any use cases....

Yes we already have this, return object.

No, we dont. Contravariant returns do not exist.


Disclaimer: The following segment is slightly off-topic. (@333fred do please instruct me, whether I am going against the code of conduct with this.)

@juliusfriedman: I have taken the liberty to take a glance at your GitHub profile, and I saw that you have been contributing to dotnet/runtime and other .NET-related repositories (Correct me if I'm wrong, but you even have moderator privileges on said repository).

I therefore do conclude that you do know what you are talking about. I also conclude that you have a somewhat in-depth knowledge of the finer workings of the .NET Runtime and/or compiler. At least you seem to have more knowledge about this subject than I have (I studied computer science at university and wrote a few niche-case compilers .... however, none of them came even close to the enormous project that is Roslyn).

However, from your recent proposals I was unable to gather the core message. It might very well be that I am either not intelligent enough, nor possess enough knowledge about the Roslyn and .NET projects. Maybe, my English qualifications are not good enough (English not being my primary language). The point is that even though I consider myself to have some knowledge of compilers and the C# language in general, I simply do not get what you are requesting.

I am honestly (really!) trying to understand you, but I somehow fail to get it .... which is very frustrating to me, as I want to try to help you with your proposals.

Could you maybe please create a concise reply containing the following?

  • 1..2 sentences for the problem at hand, e.g. "I am trying to represent tree-like data using generic interfaces bla bla bla"
  • A few sentences on what you would like to propose, e.g. "We need multi-inheritance" or "We need contravariant returns" etc.
  • A (detailed) grammar/syntax concept (ideally in Antlr-format -- not a huge block of C# or IL code).
  • The estimated effort to implement your proposal.
  • Possible alternatives to your proposed solution.

This could greatly help me (and probably others) in helping you with your requests!

Thank you very much for reading through this. If you feel offended by anything I wrote in this (or previous) posts, I sincerely want to apologize for that. I do not bear any ill-wishes and I do not want to make ad-hominem arguments in any discussion, however I absolutely wanted to raise the points written above.

@juliusfriedman
Copy link
Author

juliusfriedman commented Jul 19, 2020

I will be a bit arrogant and say your problem I think is that you don't know how to make it work

@juliusfriedman This tone is not helping anyone -- especially not when using it against members of the Language design and implementation team.

  • Do you request duck-typing generic constraints like F# has?

Eh, no probably not

Then I still do not quite get your proposal, to be honest. (I am not trying to be an ass or anything .... I literally do not get it, even though I re-read the entire conversation 😕)

  • Do you want Contravariant Returns? Because this does literally make no sense and I do not see any use cases....

Yes we already have this, return object.

No, we dont. Contravariant returns do not exist.

Disclaimer: The following segment is slightly off-topic. (@333fred do please instruct me, whether I am going against the code of conduct with this.)

@juliusfriedman: I have taken the liberty to take a glance at your GitHub profile, and I saw that you have been contributing to dotnet/runtime and other .NET-related repositories (Correct me if I'm wrong, but you even have moderator privileges on said repository).

I therefore do conclude that you do know, what you are talking about. I also conclude that you have a somewhat in-depth knowledge of the finer workings of the .NET Runtime and/or compiler. At least you seem to have more knowledge about this subject than I have (I studied computer science at university and wrote a few niche-case compiler .... however, none of them came even close to the enormous project that is Roslyn).

However, from your recent proposals I was unable to gather the core message. It might very well be that I am either not intelligent enough, nor possess enough knowledge about the Roslyn and .NET projects. Maybe, my English qualifications are not good enough (English not being my primary language). The point is that even though I consider myself to have some knowledge of compilers and the C# language in general, I simply do not get what you are requesting.

I am honestly (really!) trying to understand you, but I somehow fail to get it .... which is very frustrating to me, as I want to try to help you with your proposals.

Could you maybe please create a concise reply containing the following?

  • 1..2 sentences for the problem at hand, e.g. "I am trying to represent tree-like data using generic interfaces bla bla bla"
  • A few sentences on what you would like to propose, e.g. "We need multi-inheritance" or "We need contravariant returns" etc.
  • A (detailed) grammar/syntax concept (ideally in Antlr-format -- not a huge block of C# or IL code).
  • The estimated effort to implement your proposal.
  • Possible alternatives to your proposed solution.

This could greatly help me (and probably others) in helping you with your requests!

Thank you very much for reading through this. If you feel offended by anything I wrote in this (or previous) posts, I sincerely want to apologize for that. I do not bear any ill-wishes and I do not want to make ad-hominem arguments in any discussion, however I absolutely wanted to raise the points written above.

Don't worry not offended or anything, I tried to improve it.

The 1 - 2 sentences your looking for are:

I think that covariant and contravariant returns should be signified at the method level just as they are allowed in the interface definition.

Thank you for your time.

And P.s. I am not a moderator I don't know where you saw that...

@juliusfriedman
Copy link
Author

@juliusfriedman

The method ToArray in the example above demonstrates it for the class Number because (not show) Number is implicitly converted from or to any primitive type such as int or double or decimal etc.

Except variance is not about user-defined implicit conversions, it's pretty much only about base types and derived types. And even then, it only works on reference types, while your example is all about value types. If you're proposing to change some or all of that, you need to explicit about it.

Also, that example looks like it's more about changing how type inference works (see e.g. #450 for some previous discussion about that), because, with your implicit conversions to Number, the following code compiles:

ToArray<Number>(1, 2.0, 3.0d, 4.0m)

If you look at the linked code there is the context.

I don't think it's reasonable for you to assume we'll read 3000 lines of code, just to attempt to understand what probably could be explained in at most a page of text.

my proposal here is to allow the grammar which exists and by virtue of that fact the rules of the spec to work at any generic method level not just the generic interface

I don't understand what this means.

Please see the example?

@333fred
Copy link
Member

333fred commented Jul 19, 2020

@Unknown6656 you're fine. As long as we're not attacking each other here.

@juliusfriedman
Copy link
Author

@Unknown6656 you're fine. As long as we're not attacking each other here.

May I note that I only registered constructive criticism from his post and also thank you for your concern! You are a really fair and good moderator @333fred !

@Unknown6656
Copy link
Contributor

The 1 - 2 sentences your looking for are:
I think that covariant and contravariant returns should be signified at the method level just as they are allowed in the interface definition.

Correct me, but contravariant returns do not exist on interfaces, either. Only contravariant parameters (https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/in-generic-modifier). Are you proposing that contravariant parameters should also be allowed on class methods? That might be more difficult to implement, as method parameters are part of the method signature -- opposed to the return type (which explains why covariant returns were not too terribly difficult to implement).

And P.s. I am not a moderator I don't know where you saw that...

Ah, I think I saw you attach a label to one of your own issues in the dotnet/runtime repo, and I assumed that you are a moderator, as usually only moderators can attach labels to issues or pull requests.


@Unknown6656 you're fine. As long as we're not attacking each other here.

Thanks, mate!

I know that I can sometimes be a bit difficult to work with due to my trolling nature ... but I really do not mean to offend anyone :)

You are a really fair and good moderator @333fred !

Yeah, I second that. Thanks, @333fred !

@jnm2
Copy link
Contributor

jnm2 commented Jul 19, 2020

@Unknown6656

Ah, I think I saw you attach a label to one of your own issues in the dotnet/runtime repo, and I assumed that you are a moderator, as usually only moderators can attach labels to issues or pull requests.

This has happened to me when I post in some repos that I'm not a member in. I think it's GitHub automation that applies the label and attributes you as the one who triggered it.

@jaredpar
Copy link
Member

jaredpar commented Jul 20, 2020

The user juliusfriedman was banned due to multiple COC violations on the dotnet organization. Their behavior here, and on our related Discord and Gitter communities, demonstrated a repeated pattern of personal attacks, an unwillingness to accept feedback and a constant tone of disrespectfulness to the other members of the community

@Unknown6656
Copy link
Contributor

I guess that this issue can be closed then, @jaredpar ?

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

No branches or pull requests

8 participants