Champion: "Partial Type Inference" #8967
Replies: 86 comments
-
Pros/cons for 2 vs 3? Both are DRY but I prefer 2... you don't buy much visually with 3. |
Beta Was this translation helpful? Give feedback.
-
1 is okay but verbose. I prefer 2 because I would like this potential progression in levels of needed disambiguation for |
Beta Was this translation helpful? Give feedback.
-
I'm pretty sure there was an issue regarding recursive generic, but I can't find it. Without recursive type parameters, I think 2 is better because is less verbose, since verbosity is part of the issue here. But with recursive type parameters, I think 1 would fit better. |
Beta Was this translation helpful? Give feedback.
-
I think that either 2 or 3 is the best one. 2 is certainly the least verbose (which is nice), but 3 looks more complete and full. I don't think the slight verbosity of 3 would really be an issue because it still requires no thinking of what the type name is, and is only three keystrokes. I slightly prefer 3, but 2 is also perfectly fine. 1 is a bit too verbose. |
Beta Was this translation helpful? Give feedback.
-
I support 1 even with the inclusion of 2 or 3. It helps to document the parameters. |
Beta Was this translation helpful? Give feedback.
-
I believe, F# is using
(So we could consider using it too.) |
Beta Was this translation helpful? Give feedback.
-
Isn't 2 already used in other constructs? typeof(IDictionary<,>) |
Beta Was this translation helpful? Give feedback.
-
@qrli |
Beta Was this translation helpful? Give feedback.
-
@zippec |
Beta Was this translation helpful? Give feedback.
-
@qrli Say what? |
Beta Was this translation helpful? Give feedback.
-
@jnm2 IIRC typeof expressions cannot have partially-open generics but you can construct such Types via reflection. |
Beta Was this translation helpful? Give feedback.
-
@federicoce-moravia I don't believe either is possible. Can you show me? Btw, closing over |
Beta Was this translation helpful? Give feedback.
-
You can do it by grabbing the generic type parameter from the open generic type: Type type = typeof(Dictionary<,>);
Type[] args = type.GetGenericArguments();
Type type2 = type.MakeGenericType(typeof(string), args[1]); |
Beta Was this translation helpful? Give feedback.
-
@HaloFour That's |
Beta Was this translation helpful? Give feedback.
-
It's something that cannot be expressed in C#. It is |
Beta Was this translation helpful? Give feedback.
-
@IanKemp Final warning. If you want to take about this further, email me as I mentioned. Discussions about the code of conduct or moderation need to end here. |
Beta Was this translation helpful? Give feedback.
-
You have a fundamental misunderstanding here. Nothing has held this feature hostage. We already have champions for it. We just need a proposal here. We don't have one yet as the champions are working on other features and the entire team is overloaded with the current amount of work just to ship c#10. |
Beta Was this translation helpful? Give feedback.
-
New features generally ship yearly (or sometimes multiple times a year). However, the team itself decides what it things is important to front load and what isn't. Currently, while this does have champions, no one particularly feels that this particular issue was important enough for c#10. Will that change for c#11? Depends on how we view this over the thousands of other areas we can make language changes that people have been wanting as well. |
Beta Was this translation helpful? Give feedback.
-
Just for transparency on this idea: do not do this. An unrequested pr implementing a language change will be summarily rejected. What you can do though is work to create a viable proposal that, with team permission, could be approved and made into a compiler change. Community members have done this before. However the time commitment and bar is just as high as for anyone directly on the team. So please discuss with team first about taking this path. Thanks! |
Beta Was this translation helpful? Give feedback.
-
As long as the first form exists as a fallback I'll be a very happy person. For most cases simple wildcard usage would be best but, much like named method arguments, the merits of the first syntax don't really show through until you are dealing with a very large number of them. Incidentally, those cases when you have many generic parameters are the cases that benefit the most from expanded inference. Very long real world examplePersonally, I work with reflection.emit very often, and I have a set of helper types and extension methods that use generics to do basic verification that the stack is balanced and the right types are going to the right place at compile time. This usually looks something like this: Return<int> method = new ILHelper(methodBuilder)
.LdcI4(53)
.LdcI4(13)
.Add()
.Ret();
//Signature of the Add method
static S<int, TStack> Add<TStack>(this S<int, S<int, TStack>>) where TStack : IStack {}
The stack state is stored with a generic type, in this case the type after the 2 ldcI4s but before the add is This all works great until you want to emit a call and type inference breaks because you need to supply each part of the function signature individually to the method as generic parameters to keep stack checking, but you also need to distinguish between an Return<int> method = new ILHelper(methodBuilder)
.LdcI4(1)
.LdcI4(2)
.LdcI4(3)
.LdcI4(4)
.Call<S<int, S<int, S<int, S<int, Empty>>>>, int, int, int, int, int, SigReturn<int>>(StaticFuncs.Add4)
.Ret();
//Or
Return method = new ILHelper(methodBuilder)
.LdcI4(1)
.LdcI4(2)
.LdcI4(3)
.LdcI4(4)
.LdcI4(5)
.Call<S<int, S<int, S<int, S<int, S<int, Empty>>>>>, int, int, int, int, int, NoSigReturn>(StaticFuncs.Print5)
.Ret(); With wildcards alone (option 2 and 3 basically) it would improve, but still require that extra generic param that denotes the return or lack thereof. Looking something like this: Return<int> method = new ILHelper(methodBuilder)
.LdcI4(1)
.LdcI4(2)
.LdcI4(3)
.LdcI4(4)
.Call<var, int, int, int, int, int, SigReturn<int>>(StaticFuncs.Add4)
.Ret();
//Or
Return method = new ILHelper(methodBuilder)
.LdcI4(1)
.LdcI4(2)
.LdcI4(3)
.LdcI4(4)
.LdcI4(5)
.Call<var, int, int, int, int, int, NoSigReturn>(StaticFuncs.Print5)
.Ret(); With syntax 1 though, that last arg could be dropped entirely: Return<int> method = new ILHelper(methodBuilder)
.LdcI4(1)
.LdcI4(2)
.LdcI4(3)
.LdcI4(4)
.Call<In1: int, In2: int, In3: int, In4: int, Out: int>(StaticFuncs.Add4)
.Ret();
//Or
Return method = new ILHelper(methodBuilder)
.LdcI4(1)
.LdcI4(2)
.LdcI4(3)
.LdcI4(4)
.LdcI4(5)
.Call<In1: int, In2: int, In3: int, In4: int, In5: int>(StaticFuncs.Print5)
.Ret(); Technically, this is more code to write out than just wildcards still, but only by a small amount and the overall clarity of the code is much much better. Additionally, being able to drop that last argument makes the implementation of the Call method way easier to deal with. Note: Type inference is just one of many headaches when trying to make something like this work. For clarity I've omitted a fair few additional generic arguments that are needed to handle byref and pointer types (Cannot use those normally in generics), and also totally removed all the spooky tuples needed to do branches. Other tangential note: Implementing something like the above also is quite possibly the fastest possible way to bump your head into every single limitation on generics that exists in C#. If that sounds like fun then I highly recommend it... I'm also partial to MyClass<var,int,int,var,int> _ = new();
MyClass<,int,int,,int> _ = new();
MyClass<_,int,int,_,int> _ = new(); As I recall, using |
Beta Was this translation helpful? Give feedback.
-
i think reason of this proposal could be this: static void Run<t, o, i1, i2, i3>(Func<t, o> f, i1 Input1, i2 Input2, i3 Input3)
{ }
Run <MyApp,,,,> (x => x.ToString(), 1, 2, 3); |
Beta Was this translation helpful? Give feedback.
-
I'm not sure this related but, is this issue also involve partial generic for Is this issue also made this below possible? object obj = new Dictionary<string,string>();
if(obj is Dictionary<string,> dict) // allow any type of dictionary's value, just need to have string as a key
{
string key = dict.Keys.FirstOrDefault();
return dict[key]; // default to object ?
} |
Beta Was this translation helpful? Give feedback.
-
No. Partial type inference only means that the compiler fills in part of the generic type arguments automatically, but the types are known at compile time. The runtime doesn't permit use of open generic types in that manner you've described, and it couldn't use variance either in that case. |
Beta Was this translation helpful? Give feedback.
-
I would prefer |
Beta Was this translation helpful? Give feedback.
-
VB.net also seems to support partial type inference, at least in a limited fashion. I don't know the specifics and can't seem to find a reference easily, but I know I am able to do the following with extension methods: Public Module TestExtension
<Extension>
Public Function ConvertOutput(Of InT, OutT)(ByVal source As IEnumerable(Of InT)) As IEnumerable(Of OutT)
Return source.Select(Function(input) DirectCast(Convert.ChangeType(input, GetType(OutT)), OutT))
End Function
End Module
...
Dim strings As IEnumerable(Of String) = {"1", "2", "3"}
Dim ints As IEnumerable(Of Integer) = strings.ConvertOutput(Of Integer)() It is possible this is limited to the types that are provided by the Dim ints As IEnumerable(Of Integer) = strings.ConvertOutput(Of String, Integer)() |
Beta Was this translation helpful? Give feedback.
-
Re-iterating through this proposal, I'm very fond of the idea of inferring obvious type parameters, both for generic methods and type constructors, especially given that generic inference does not apply to constructors. Gotta note that enabling named type parameters with inference for the others, there will have to be support for handling ambiguities for types with multiple arities. That aside, it seems like a very promising QoL improvement, one step closer to reducing some inconveniences with generics. |
Beta Was this translation helpful? Give feedback.
-
One thing that I ran into which annoys me and I think this could perhaps over, is when a Type parameter constraint actually ends up defining Type parameter so to speak. Given: interface ISomeThing<T1, T2> { }
class SomeThingA : ISomeThing<int,string> {}
T Get<T, T1, T2>() where T: ISomeThing<T1, T2> => ...; When I call Get, given any type. T1 and T2 is already given by the first type due to the constraint. Yet I have to repeat that information anyways, which seems very convoluted, I wished I could just call Get as: Get<SomeThing1>(); // (T1 = int, T2 = string, we know this from SomeThing1, yet I have to provide that info)
Get<SomeThing1, int, string>(); // But that is sooo redundant, This example might highlight it better:
Get<ISomeThing<int, string>, int, string>(); Seems like this proposal would not save me from specifying the Type Arguments on the Get method, but at least it would save me from specifying them on the call to get which is where the biggest annoyance is. |
Beta Was this translation helpful? Give feedback.
-
@jeme |
Beta Was this translation helpful? Give feedback.
-
Normally that would mean only one of them would be an implicit implementation which TECHNICALLY means you could argue you could infer it to that, but that would be a little crazy i guess, so I would rather that it gave the classic:
As it's no longer obvious, therefore it cannot be implicitly inferred. The other example however is not so crazy in my opinion, it's not like the compiler is unaware, after all, it knows exactly which types I can use as it gives an error if I don't specify exactly matching types in this simple case. Now Covariance / Contravariance will change this a bit as you can then specify a different type parameter than what is implemented. E.g:
However, I don't really see that as an issue for the inference, if the two second parameters are not provided, it would assume B and B. |
Beta Was this translation helpful? Give feedback.
-
Hi! |
Beta Was this translation helpful? Give feedback.
-
See #1348
Currently, in an invocation of a generic method, you either have to specify all of the type arguments or none of them, in the latter case depending on the compiler to infer them for you. The proposal is to permit the programmer to provide some of the type arguments, and have the compiler infer only those that were not provided.
There are (at least) three possible forms this could take:
M<TElement: int>(args)
M<int, >(args)
var
(Proposal: Allow partial generic specification #1348), e.g.M<int, var>(args)
Design Meetings
https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-07.md#partial-type-inference
Beta Was this translation helpful? Give feedback.
All reactions