-
Notifications
You must be signed in to change notification settings - Fork 1k
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: "Opaque" parameters #4629
Comments
Am I right in thinking this only more performant if the type parameter is a struct? Is there any advantage to allowing you to constrain the type parameter to struct? This sounds quite similar to |
Dargh. Beaten. |
This absolutely cannot be used in return types, since that would require existential types at the runtime level. |
Sounds like we now have an excuse to drive the runtime mad until they give use more type system goodies! |
I'll champion this. |
That's partially me and I would be both excited and hesitant. 😄 |
For reference, this is the same as https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/flexible-types Does this interact with "existential types" (#1328)? |
Yep. This is a limited form of them, valid only in the parameter position. A more full implementation would be required for the return type inference that Yair and Cyrus mentioned above. |
what about void Foo<T>(T foo)
where T : not interface, IEnumerable<int>
{
...
} |
That doesn't seem related. Nothing about this proposal says that the type substituted has to be a concrete implementation, it's purely about making signatures simpler. |
@JustNrik It is similar to: https://en.cppreference.com/w/cpp/language/function_template#Abbreviated_function_template Perhaps we could use int CombineHash(in var f1, in var f2)
{
//just as simple sample
return f1.GetHashCode() ^ f2.GetHashCode();
} (this would be not as useful as in C++ - because it would still allow only the usage of object-member) @agocke: nice proposal |
@bernd5 That doesn't work. You can't use There's no type inference in my proposal except for normal generic type inference on the caller side. |
Why it should not work? It works even today: using System;
class App {
static int CombineHash<T1, T2>(in T1 f1, in T2 f2)
{
//just as simple sample
return f1.GetHashCode() ^ f2.GetHashCode();
}
public static void Main(){
var a = 12;
var b = 2L;
var result = CombineHash(a, b);
Console.WriteLine(result);
}
} The constrained interface would be simply the implicit "object-interface". |
That's not what What would you do when you actually need to write out the interface? void M(var IEnumerable<int> e) { ... } There's no place in the language where a type ever follows |
No, I don't mean What you describe is actually what var does at every other place, too. With the interface you just limit the set of deducable types which could be used for overload resolution, too. |
That description doesn't really tell me anything, it's just saying that |
I think this should work more like an modifier, so that you can use it for nested types: void M(some IEnumerable<some IEnumerable<int>> t)
{
// ....
} would become: void M<T1, T2>(T1 t)
where T1 : IEnumerable<T2>
where T2 : IEnumerable<int>
{
// ....
} |
This is too limited to make auto generic parameter. It can't infer concrete type (or even abstract type if I correctly understand this?) and cannot infer multiple interfaces. I don't think this worthwhile for making a new language feature for when we could just writing generic syntax normally (sure it is tedious but not that much) Unless we already have #399 |
The problem is void M<T1, T2>(T1 t)
where T1 : IEnumerable<T2>
{
...
} This is actually very desirable in a lot of random places but is completely uninferrable by C#. It's not the declaration side, which sucks but you write it once, that's the biggest problem. It's that the caller side has to manually specify the type parameters every time because there's no way to infer the substitutions. |
I tried, couldn't make it work without terrible perf or a compat break. This is my backup solution. 😄 |
Oh I see, then wish your best luck |
LDM considered this on 5/19. We think that there's too many cliffs here: additional constraints, additional arguments of the same type, interactions with other type parameters, and the silent munging with the signature. We may consider something like this when we start looking more at traits and associated types, but this is rejected for now. |
I don't see anyone mention F#, which has what they call flexible types. It looks very similar indeed: https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/flexible-types |
@333fred Any discussion of type inference? This is kind of a blocker for certain high-perf scenarios and I don't have an alternate approach for fixing constrained type inference. |
It was not discussed in any detail, no. |
The huge problem is that IEnumerable or IAsyncEnumerable is missing its enumerator opaque type. |
Opaque parameters
Summary
This proposal would include a new syntax form for parameter declarations: an optional
some
contextual keyword that precedes the parameter type, similar to
ref
orparams
. This wouldlook like:
The parameter type would be required to be an interface. The semantics would be that the
parameter type is not the interface itself, but an unspeakable generic type constrained to the
interface listed as the parameter type, e.g.
On the caller side, the call would look like
List<int> x = ...; M(x)
. From the caller's perspective,there would be no generic parameters to this method. The compiler would be required to infer
the appropriate generic parameter type, which should be the exact type of the argument.
Overloading:
some
parameters should be valid as overloads, similar toref
params, and should be considered a better candidate than a parameter of the same type withoutsome
. This would allow for the following overload scenario:Users could then decide to add
some
overloads that would have improved performance and be preferred when present.Motivation
This addresses a couple problems. First, it's very verbose to declare a signature of the above form,
although it's often very desirable for performance. Second, it hides irrelevant information (the generic
parameter name), which is not useful aside from the performance implications of the definition. Third,
due to the C# type inference algorithm, the verbose equivalent is often very difficult for the compiler
to infer. Because the parameter is not referenceable anywhere else in the parameter list, it should be
easy to infer the correct parameter type here.
Aside: credit to Swift for inspiration here, although the problems aren't the same.
Drawbacks
Extra syntax, more complexity.
Alternatives
TBD
Unresolved questions
Exact lowering and inference. A compiler-recognized attribute on the generated type parameters to remove
them from the list of user-visible type parameters may be necessary, along with a modopt on the
some
parametersto enable overloading.
Design meetings
The text was updated successfully, but these errors were encountered: