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

Proposal: Type Parameter Pack (Variadic Generics) #5058

Closed
alrz opened this issue Sep 7, 2015 · 43 comments
Closed

Proposal: Type Parameter Pack (Variadic Generics) #5058

alrz opened this issue Sep 7, 2015 · 43 comments

Comments

@alrz
Copy link
Member

alrz commented Sep 7, 2015

Variadic Genericity via Tuples

Definition: A type parameter pack is a simple identifier followed by .. that denotes a placeholder for one or more type arguments supplied to create a constructed type. A type parameter pack is a formal placeholder for multiple types that will be supplied later. By contrast, type arguments (§4.4.1) are the actual types that are substituted for the type parameter pack when a constructed type is created.

type-parameter:
identifier
identifier ..

class C<T..> {}

Scope: Each type parameter pack in a type declaration defines a name in the declaration space (§3.3) of that type. Thus, it cannot have the same name as another type parameter or a member declared in that type. A type parameter pack cannot have the same name as the type itself. Since variadic generic types can be constructed with one or more type arguments, you cannot declare any other generic types with the same name within the same scope.

class C<T..>  {}
class C<T>    {} // error: CS0102
class C<T, U> {} // error: CS0102
class C       {} // ok

Type: Type parameter packs like T.. considered as an atomic kind of type parameter, i.e. you cannot use an identifier to refer to a type parameter pack and the trailing dots are required.

Type F<T..>() {
  return typeof(T);   // error
  return typeof(T..); // ok
}

Translation: Since a variadic generic type can be instantiated with multiple different actual type arguments, type parameter packs will be bound to aValueTyple<> type parameters, so they are implicitly a value type. The code above is roughly equivalent to the following with the type parameters substituted.

// assuming
Type F<T..>() {
  return typeof(T..);
}

// then
var type = F<int>();

// loads the method F as follow
Type F<int>() {
  return typeof(ValueTuple<int>);
}

Similarly, when you use type parameter packs with classes an augmented ValueTuple<> is emitted in place of type parameter pack.

class C<T..> {
  private T.. items;
  public C(T.. items) {
    this.items = items;
  }
}

// translates to

class C<T..> {
  private ValueTuple<T..> items;
  public C(ValueTuple<T..> items) {
    this.items = items;
  }
}

Method Parameters: Since type parameter packs are translated to tuples, compiler would create a tuple with supplied arguments in runtime and pass it to the method. Though, I think this should be translated in a way that use real method parameters.

Constraints: Generic type constraints will be applied to all type arguments but not the type parameter pack itself.

void F<T..>(T.. args) where T.. : struct  {}

F(1, 1.0) // infers F<int, double>(ValueTuple<int, double> args)
F(1, "")  // error

Since you don't have access to individual types in a variadic generic type, defining new constraint on them doesn't make sense, hence it's not allowed.

Variance: Variance modifiers also will be applied to the all types.

public delegate TReslut Func<in T.., out TResult>(T..);

In runtime appropriate delegate type will be created. For delegate declarations we don't use tuple translation because all we care is just the method signature.

Oneples: To be able to use a single type in place of a type parameter pack we need oneples. The trailing comma in the tuple-expression is meant to be used to disambiguate oneple from parenthesised-expression.

tuple-type:
( tuple-member-list )

tuple-member-list:
tuple-member
tuple-member-list , tuple-member

tuple-member:
type identifieropt

tuple-expression:
( tuple-element-list ,opt )

tuple-element-list:
tuple-element
tuple-element-list , tuple-element

tuple-element:
tuple-element-nameopt expression

tuple-element-name:
identifier :

interface I<T..> {
  T.. Get();
}

class C1 : I<int,int> {
  (int, int) I<int,int>.Get() {
    return (1,2);
  }
}

class C1 : I<int> {
  (int) I<>.Get() {
    return (1,);
  }
}

We can make this a special case so one can use regular types e.g. int instead of (int).

Record Types: To facilitate variadic generics we have to support them in record types with a different behavior: each type will create a field and will be used for positional deconstruction.

public struct ValueTuple<T..>(T..); 

Overloading: It is possible to overload a variadic generic method with regular type parameters and similar to params arguments, best overload can be selected based on number of arguments;

void F<T..>(T.. args);
void F<T>(T arg);       

F(1, 2); // first method
F(1);    // second method

This needs more work to precisely define rules for overloading resolution and interaction with pipe forward operator and tuple proposals.

@gafter gafter added this to the Unknown milestone Sep 8, 2015
@dsaf
Copy link

dsaf commented Sep 8, 2015

Does this effectively mean adding templates to language in addition to generics? In that case it's probably a wider meta-programming feature.

@alrz
Copy link
Member Author

alrz commented Sep 8, 2015

@dsaf No, the title might be confusing. Type parameter packs are just a special kind of generic type parameters.

[...] a type parameters is a placeholder for a specific type that a client specifies when they instantiate a variable of the generic type.

so are parameter packs, but for multiple types.

Take this as an example (from here, first 500 lines):

public virtual Func<T..., IObservable<TResult>> FromAsyncPattern<T..., TResult>(
    Func<T..., AsyncCallback, object, IAsyncResult> begin,
    Func<IAsyncResult, TResult> end) => args =>
{
    var subject = ...;
    try
    {
        begin(args, iar =>
        {
            ...
        }, null);
    }
    catch (Exception) { ... }
    return subject.AsObservable();
};

I don't think this would be considered as a template.

@alrz alrz changed the title Proposal: Type Parameter Pack (Variadic Type Template) Proposal: Type Parameter Pack (Variadic Generics) Sep 8, 2015
@dsaf
Copy link

dsaf commented Sep 8, 2015

The compiler might generate these types eventually (for example, if tuples have unlimited items) but in this way it would share the template, type-safely with the developer.

It seems that the only languages supporting/planning this sort of feature that I know (C++, D, Rust) are all using templates rather than generics.

PS: regardless of how it's done, it's an awesome feature.

@alrz
Copy link
Member Author

alrz commented Sep 8, 2015

@dsaf By template I meant the internal templates that compiler is using to translate constructs like anonymous types, asynchronous methods, iterators, closures, etc to valid flat IL. I said, in case of unlimited tuples — meaning that if it's allowed, then compiler should generate those types — which I don't think it would and similar to Action and Func generic overloads (to support lambda expressions with varying number of parameters), we will optimistically face:

public struct ValueTuple<T1,T2>(T1 item1 : Item1, T2 item2: Item2);
public struct ValueTuple<T1,T2,T3>(T1 item1 : Item1, T2 item2: Item2, T3 item3 : Item3);
public struct ValueTuple<T1,T2,T3,T4>(T1 item1 : Item1, T2 item2 : Item2, T3 item3 : Item3, T4 item4 : Item4);
// to the end of the world ...

to support tuples with varying number of items.

I'm just saying that clr generic implementation has the potential and it's not far from possibility.

@alrz
Copy link
Member Author

alrz commented Sep 8, 2015

@svick I'm glad you asked but I couldn't find your comment so I quote it here:

public struct ValueTuple<T...>(T... items);

What exactly does this do? Is this syntax part of your proposal? How would you access the items stored in such tuple?

This is a record type that can be used as the underlying type of tuples of any size. I'll explain.

From #206 in the Change Log section, you see that (in the context of record type declaration)

The new behavior is now keyed on the presence of the parameter list.

And since the parameter list will translate to members (Section 1.1.5) we can use the T... type to get N members of different types.

var tuple = new ValueTuple<string,int>("foo", 845); 
var tuple = ("foo",845); // new syntax
(var str, var num) = tuple; // deconstruction

Why should this work? First let's see how generics work in runtime:

When a generic type is first constructed with a type as a parameter, the runtime creates a specialized generic type with the supplied parameter or parameters substituted [...]

So all you need to do is instantiate it with specific types as parameter and let the runtime create it for you.

PS: these are all based on my assumptions, so may not be really possible in this regard.

@svick
Copy link
Contributor

svick commented Sep 8, 2015

@alrz Sorry, I only noticed you mentioned record types after I posted my comment, so I deleted it, intending to ask again after I look at them.

@AdamSpeight2008
Copy link
Contributor

How you get the individual parameters?
Currently if you use params or ParamArray it is an array<T> or potentially an Ienumerable(Of T).
With this proposal, each parameter could be of a different type.

@alrz
Copy link
Member Author

alrz commented Sep 10, 2015

@AdamSpeight2008 params and ParamArray have nothing to do with generics. They're just a syntactic sugar that turns multiple arguments to an array and pass it to the method, hence they have to be instances of a common type.

How you get the individual parameters?

Quick answer is you don't. Currently if you have a generic parameter (e.g. T), unless it satisfied some generic constraints, you can not do anything with it. Same would apply here. If you have a T... then you may pass it to a method that accepts a T.... If you're using a Parameter Pack, you're not interested in the individual parameters (clients do, when the types have been specified) and also if you do, why it has to be generic? Since iterating over them (type-safely) won't make much sense neither.

For an example, see the third comment.

@gafter
Copy link
Member

gafter commented Sep 10, 2015

In the record type ValueTuple<int, string> that you declared, what are the names of the constructor parameters? What are the names of the fields or properties of the type?

@AdamSpeight2008
Copy link
Contributor

@alrz I was thinking of this problem and how to solve without boxing to object.h
Note: the actual syntax used for "variadic type" is made up

<Extension>
Function IsTypeOneOf<Ts, T...>(source As Ts, ParamArray types As T.. )
  For Each ty As T.. in types 
    If TypeOf source Is ty Then Return True
  Next
  Return False
End Function

Dim res = obj.IsTypeOneOf( Integer, Double, Single )
Which would be helpful.

@dpoeschl
Copy link
Contributor

@AdamSpeight2008 Were you trying to tag someone else? 😄

@AdamSpeight2008
Copy link
Contributor

@dpoeschl damn you tab key, damn you to hell.

@alrz
Copy link
Member Author

alrz commented Sep 10, 2015

@gafter This depends on how parameters will translate to fields. I don't know if the name part
(... : Item1) is mandatory. If it is, then the whole thing is not a regular parameter list and this can't be done. If it's not (it uses the parameter name) then it must treat Parameter Packs differently, no way to abstract it away. This is a special case though, currently there is nothing like that in the language.

@AdamSpeight2008
Copy link
Contributor

@alrz One way open today is to use multiple overload, like the definition of func<> and action<>

@alrz
Copy link
Member Author

alrz commented Sep 10, 2015

@AdamSpeight2008 you are using For Each, T... is not an IEnumerable<>, It's a type.

@AdamSpeight2008
Copy link
Contributor

If could be if it is IEnumerable<dynamic>

@AdamSpeight2008
Copy link
Contributor

This requires an instance of each type, not the actual type. eg ,Int ,Double, Single

public static bool IsTypeOneOf<Ts>(this Ts source, params dynamic[] types)
  {
    foreach(dynamic ty in types)
    {
      if( ty is Ts)  return true;
    }
    return false;
  }
}

I would really like to be able to write

bool result = obj.IsTypeOf( single, double, byte );

This function is also kinda hard to write in VB.net, with dynamic being object
So we then could extend functionality of Type ... Is ... and Type ... IsNot ... to multiple types.

dim res0 = TypeOf obj Is {Integer, Double, Single}
dim res1 = TypeOf obj IsNot {Integer, Double, Single}

@AdamSpeight2008
Copy link
Contributor

IL from C# implementation.

IL_0000:  ldarg.1     
IL_0001:  stloc.2     // CS$6$0001
IL_0002:  ldc.i4.0    
IL_0003:  stloc.3     // CS$7$0002
IL_0004:  br.s        IL_001A
IL_0006:  ldloc.2     // CS$6$0001
IL_0007:  ldloc.3     // CS$7$0002
IL_0008:  ldelem.ref  
IL_0009:  stloc.0     // ty
IL_000A:  ldloc.0     // ty
IL_000B:  isinst      01 00 00 1B 
IL_0010:  brfalse.s   IL_0016
IL_0012:  ldc.i4.1    
IL_0013:  stloc.1     // CS$1$0000
IL_0014:  leave.s     IL_0022
IL_0016:  ldloc.3     // CS$7$0002
IL_0017:  ldc.i4.1    
IL_0018:  add         
IL_0019:  stloc.3     // CS$7$0002
IL_001A:  ldloc.3     // CS$7$0002
IL_001B:  ldloc.2     // CS$6$0001
IL_001C:  ldlen       
IL_001D:  conv.i4     
IL_001E:  blt.s       IL_0006
IL_0020:  ldc.i4.0    
IL_0021:  ret         
IL_0022:  ldloc.1     // CS$1$0000
IL_0023:  ret         

IL from VB implementation

IL_0000:  ldarg.2     
IL_0001:  stloc.3     // VB$t_array$L0
IL_0002:  ldc.i4.0    
IL_0003:  stloc.2     // VB$t_i4$L0
IL_0004:  br.s        IL_001D
IL_0006:  ldloc.3     // VB$t_array$L0
IL_0007:  ldloc.2     // VB$t_i4$L0
IL_0008:  ldelem.ref  
IL_0009:  call        System.Runtime.CompilerServices.RuntimeHelpers.GetObjectValue
IL_000E:  stloc.1     // ty
IL_000F:  ldloc.1     // ty
IL_0010:  isinst      04 00 00 1B 
IL_0015:  brfalse.s   IL_0019
IL_0017:  ldc.i4.1    
IL_0018:  ret         
IL_0019:  ldloc.2     // VB$t_i4$L0
IL_001A:  ldc.i4.1    
IL_001B:  add.ovf     
IL_001C:  stloc.2     // VB$t_i4$L0
IL_001D:  ldloc.2     // VB$t_i4$L0
IL_001E:  ldloc.3     // VB$t_array$L0
IL_001F:  ldlen       
IL_0020:  conv.ovf.i4 
IL_0021:  blt.s       IL_0006
IL_0023:  ldc.i4.0    
IL_0024:  ret         

@alrz
Copy link
Member Author

alrz commented Sep 10, 2015

@AdamSpeight2008 using params and dynamic doesn't make sense at all. For this problem, I suggest that you use pattern matching.

But for you to be able to write this

bool result = obj.IsTypeOf( single, double, byte );

I wasn't sure if it was C# or VB. I think you meant to write:

Dim result = obj.IsTypeOf(Of Single, Double, Byte)()

Anyway, we should see how the type T... behaves as a pattern. I think it shouldn't be allowed.

@AdamSpeight2008
Copy link
Contributor

@alrz Pattern Matching is overkill for some thing that should be simple to express.
Is my object's type one of the ones listed?

Dim result = obj.IsTypeOf(Of Single, Double, Byte)()

will produce the error 'Exts.IsTypeOneOf<Ts,T0,T1>(Ts)' requires 4 type arguments

@alrz
Copy link
Member Author

alrz commented Nov 16, 2015

@gafter

In the record type ValueTuple<int, string> that you declared, what are the names of the constructor parameters? What are the names of the fields or properties of the type?

Elaborating on your question; just like #6115 where you would not specify names for record members

class Foo(Bar,Baz);

constructor parameters and fields names would be generated by the compiler but not properties, hence they are not accessible through them. One would be able to use patterns for positional deconstruction.

For tuples (since the items would be declared in the usage and "erased" at compile-time) default names like Item1, Item2 wouldn't make much sense. That said, the record type declaration,

public struct ValueTulple<T...>(T...);

could be used as the underlying type of tuples of any size.

@gafter
Copy link
Member

gafter commented Nov 28, 2015

I suggest documenting what CLR changes would be required (eg constraint handling, delegate and record instantiation)

@gafter
Copy link
Member

gafter commented Nov 28, 2015

Also CLR support for delegate variance. Also document the compat issues with, e.g. Func

@leppie
Copy link
Contributor

leppie commented Nov 29, 2015

@gafter From what I can see (but not tested), calli should be able to handle this if a tuple (or some other form) can be deconstructed on the IL stack. I am also not sure what is required for verification. The real problem is the deconstruction bit. Currently it can be easily done, but inefficiently from user code. Something like push int[5] or push type might be nice if the CLR can support it. Not sure about the gains though. Might be a fruitless exercise.

@gafter
Copy link
Member

gafter commented Nov 29, 2015

If a type parameter "pack" is encoded as a tuple, it would be incompatible to replace the existing Func and Action types, as those already use explicit type parameters.

@alrz
Copy link
Member Author

alrz commented Nov 29, 2015

@gafter It doesn't essentially always encode as a tuple. In the following contexts, a type parameter pack does not encode as a tuple:

  • Primary constructors (expands to class fields)
  • Method parameters (expands to method parameters)
  • Constructor parameters (same as above)

However, in the following contexts, they do:

  • Generic method parameters
  • Return types
  • Fields and properties

So, a delegate with the declaration,

delegate void Action<T..>(T..);

Would translate into:

sealed class Action<T..> : MulticastDelegate {
  public void Invoke(T..) { ... }
}

The Invoke method is not a generic method, rather, it's a member of a generic type. So when type parameters are specified, it expands to:

var f = new Action<int, int>( ... );
// type args would be substituted 
sealed class Action<T1, T2> : MulticastDelegate {
  public void Invoke(T1, T2) { ... }
}

The thing is, that type parameter packs add an additional layer to genericity of the types, therefore even an open type reference would cause to create a new type object:

// all valid
typeof(Action<>);
typeof(Action<,>);
typeof(Action<,,>);

As a side note, since non-generic types and generic types are clearly distinguishable in CLR we cannot specify zero or more types in place of a type parameter pack, it would cause ambiguity between an open type C<> or an actual type with zero type arguments new C<> and even worse, we have to be able to declare and create a tuple with no elements e.g. (). So for Action we actually need to declare two delegates:

public delegate void Action();
public delegate void Action<T..>(T..);

which as discussed above, it wouldn't be ambiguous or incompatible.

As for records, this expansion is more expensive, because GetHashCode etc should be generated on demand, for example:

// assuming
public struct ValueTuple<T..>(T..);

// then
var t = (1,1.0);

// causes the following type to be loaded
public struct ValueTuple<int,double> {
  public readonly int $i1;
  public readonly double $i2;
  public ValueTuple(int $i1, double $i2) {
    this.$i1 = $i1;
    this.$i2 = $i2;
  }
  public override int GetHashCode() { ... }
  // etc
}

ValueTuple is an exception, because all the members are generated this way, and if you want to name individual elements you can write (Item1: 1, Item2: 1.0) which according to #347 all the names will be replaced with the actual member names (in this case, $i1, $i2). While there is a way to access individual items in a tuple, this is not the case with other variadic generic records, and one must use positional deconstruction to get access to them.

@gafter
Copy link
Member

gafter commented Nov 29, 2015

Given

Action<int> a = ...

This would no longer compile under your proposal:

a(obj: 3);

@alrz
Copy link
Member Author

alrz commented Nov 29, 2015

@gafter Shoot. I'd say completely ignore arg names for variadic delegates because they are all compiler generated. I can see that Action<> is not even consistent over its generic overloads; one used obj (really?) and another arg#. We can either: (1) keep Action<T> and define some rules for compiler generated names e.g. Action<T..>(T.. arg) would generate arg1, arg2 etc. But then there will be some scoping problems which I believe is not that problematic and compiler can "infer" appropriate type based on number of type args, just like what it does for params. (2) use a different type like Fn<T..>.

But seriously, is this the real problem of this proposal? I can tell no one used that very expressive arg name for heaven's sake. But If they did, they deserve a compiler error next time. kiddin.

@gafter
Copy link
Member

gafter commented Nov 29, 2015

This is a pretty expensive/complicated mechanism to add to the CLR and languages. Does it solve a correspondingly large problem, or address a common enough use case with a significant improvement?

@alrz
Copy link
Member Author

alrz commented Nov 29, 2015

@gafter Actually yes, as I mentioned before. This is just an example that shows how this can be really useful (Rx is not the only one, right?). In these kind of situations you will need to use code generators at best. But there is a pattern here, before Func and Action, now ValueTuple and so on. My concern is that if tuples wouldn't be implemented this way, maybe later would be too late. I also wanted to suggest the same syntax for splatting like TwoArgsFunc(tuple..); I'm saying that it just makes sense to implement tuples in this way, but as you say, "it seems unlikely".

@gafter
Copy link
Member

gafter commented Nov 29, 2015

@alrz So it solves the problem of some (rare) libraries having to write more source code than would otherwise be necessary. There are very, very few authors who write things like Func, Action, ValueTuple, and FromAsyncPattern, and those four are already "solved" because they have already been written. It feels like the bang for the buck is very low (the benefit is rarely enjoyed and the engineering resources to do it would be quite large).

I don't doubt that things would be better with something like this rather than without, but overall I think it would be a loss for all the things we could instead be doing with our engineering resources.

@TheOpenDevProject
Copy link

@gafter any progress on this, I still think this is something C# really needs.
I do have one question,
Would this proposal effect Interfaces. For example would this now become legal similar to C++

Interface IExample<T...>
{

}

@alrz
Copy link
Member Author

alrz commented Apr 8, 2016

@TheOpenDevProject It depends on the usage. As long as CLR can represent variadic generics, expansion can follow the rules listed in #5058 (comment). However, this is known to be an extensive change for both language and the CLR, therefore it's unlikely to see variadic generics or higher kinded generics anytime soon.

@bradphelan
Copy link

bradphelan commented Apr 18, 2016

I was one of the contributors to this T4 template which generates the the following type of code in the ReactiveUI for up to some N of possible type arguments.

   /// <summary>
    /// WhenAny allows you to observe whenever one or more properties on an
    /// object have changed, providing an initial value when the Observable
    /// is set up, unlike ObservableForProperty(). Use this method in
    /// constructors to set up bindings between properties that also need an
    /// initial setup.
    /// </summary>
    public static IObservable<TRet> WhenAny<TSender, TRet, T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11>(this TSender This, 
                        Expression<Func<TSender, T1>> property1, 
                        Expression<Func<TSender, T2>> property2, 
                        Expression<Func<TSender, T3>> property3, 
                        Expression<Func<TSender, T4>> property4, 
                        Expression<Func<TSender, T5>> property5, 
                        Expression<Func<TSender, T6>> property6, 
                        Expression<Func<TSender, T7>> property7, 
                        Expression<Func<TSender, T8>> property8, 
                        Expression<Func<TSender, T9>> property9, 
                        Expression<Func<TSender, T10>> property10, 
                        Expression<Func<TSender, T11>> property11, 
                        Func<IObservedChange<TSender, T1>, IObservedChange<TSender, T2>, IObservedChange<TSender, T3>, IObservedChange<TSender, T4>, IObservedChange<TSender, T5>, IObservedChange<TSender, T6>, IObservedChange<TSender, T7>, IObservedChange<TSender, T8>, IObservedChange<TSender, T9>, IObservedChange<TSender, T10>, IObservedChange<TSender, T11>, TRet> selector)
    {
                    return Observable.CombineLatest(
                                This.ObservableForProperty(property1, false, false), 
                                This.ObservableForProperty(property2, false, false), 
                                This.ObservableForProperty(property3, false, false), 
                                This.ObservableForProperty(property4, false, false), 
                                This.ObservableForProperty(property5, false, false), 
                                This.ObservableForProperty(property6, false, false), 
                                This.ObservableForProperty(property7, false, false), 
                                This.ObservableForProperty(property8, false, false), 
                                This.ObservableForProperty(property9, false, false), 
                                This.ObservableForProperty(property10, false, false), 
                                This.ObservableForProperty(property11, false, false), 
                            selector
        );
                }

It was quite a headache to construct. I've also authored a number of other similar templates in closed source projects I think variadics would be very nice to have. I agree it's not the realm of your average coder but for making libraries it is nice.

@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.

I am not moving this particular issue because I don't have confidence that the LDM would likely consider doing this as proposed. I think we might consider doing something along these lines along with higher-kinded types if we do that; see dotnet/csharplang#339.

@vodevil
Copy link

vodevil commented Sep 20, 2018

We need a turing complete variadric template implementation in C# like C++ does

@Salgat
Copy link

Salgat commented Jun 16, 2020

Would have loved this feature for something implemented recently, where we allow for returning a generic tuple of values ranging from one value to multiple. Having to add explicit cases for each count (1 generic type, 2 generic types, etc) creates a lot of redundant code.

@CyrusNajmabadi
Copy link
Member

@Salgat This is a closed issue :) If you're interested in voting on a language issue, i would recommend finding and voting on one of the ones in dotnet/csharplang

@Salgat
Copy link

Salgat commented Jun 16, 2020

@CyrusNajmabadi I'm aware, however gafter clarified that we can still continue discussion here even though the issue is closed, and since many relevant issues still link to this. I didn't intend on pursuing this lacking feature beyond just leaving a quick comment here.

@TheOpenDevProject
Copy link

I mentioned this in 2016 that it was required, its still required, not sure how this can take 5 years to finalize the discussion on.

@CyrusNajmabadi
Copy link
Member

@TheOpenDevProject Language design is not done at dotnet/roslyn. It happens at dotnet/csharplang.

not sure how this can take 5 years to finalize the discussion on.

Length of time is not relevant. If we don't see the appropriate value in doing a feature, it won't happen regardless of how much time has passed.

@thygrrr
Copy link

thygrrr commented Jan 31, 2024

Length of time is not relevant. If we don't see the appropriate value in doing a feature, it won't happen regardless of how much time has passed.

Agree :) Adding some value here; looking for a way to make this less ugly:

//Imagine about 20 variations of these with different additional generic and non-generic parameters... at least until user asks for 6 component and 7 component variants
public delegate void QueryAction_ECCCCC<C0, C1, C2, C3, C4>(in Entity e, ref C0 c0, ref C1 c1, ref C2 c2, ref C3 c3, ref C4 c4);

And I understand this proposal as suggesting:

//Exactly one Type and no Overloads
public delegate void QueryAction_EC<C..>(in Entity e, ref C.. c);

FWIW, this is a very attractive use case in a ECS framework I'm currently developing, and the current state leads to a lot of bloat especially regarding unit tests for the many, many overloads to the same functions I need to provide to the user.

However, not even considering there may be a much smarter way to do this; it's a use case definitely adjacent to source generation, because how would the program call the delegate (which can have any number of values) in an efficient way that doesn't involve assembling the parameter array in memory for each call.

The language lacks a feature there, but it feels a bit out of scope regardless of my personal design goal of avoiding a source generator for this purpose.

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