-
Notifications
You must be signed in to change notification settings - Fork 1k
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
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] Functional Interfaces #3452
Comments
It appears that Shapes subsumes this: #164 |
Related: #2517 |
@CyrusNajmabadi , AFAIK shapes don't explain the translation of lambda expression and closures. Did I miss something? |
Hey @CyrusNajmabadi - I'm not sure I understand your comment here, could you elaborate on that? For instance, consider how an API like // Just an example, I know there is string(char, int):
char c = '$';
// Super awkward syntax and function signature to enable delegate caching
string text = string.Create(10, c, (span, state) => span.Fill(state)); Whereas with this proposal you could have the following: // API definition
string Create<TFunc>(int length, ref TFunc func)
where TFunc : struct, ISpanAction<char>;
// Usage:
char c = '$';
string text = string.Create(10, ref span => span.Fill(c)); That // Display type
struct <Test2>DisplayStruct_1 : ISpanAction<char>
{
public char c;
public void Invoke(Span<char> span)
{
span.Fill(c);
}
}
// And transform the code as:
char c = '$';
<Main>DisplayStruct_1 state = default;
state.c = c;
string text = string.Create(10, ref state); This would make both the API surface and the actual usage much less verbose and easy to use. I feel like this would enable devs to write more efficient code more easily, while also specifying intent (indicating that the input delegate will be immediately consumed), and make the existing struct + interface "value delegate" feature more accessible 😄 |
@ZacharyPatten From what I see, both proposals would require some changes to the C# language too. This one in particular would likely require allowing I think there's some overlap in the two issues, yes. At the very least, the intent seems to be very similar - they're both proposing ways to improve the usage of constrained value type delegates and make them more accessible and widespread 😊 |
|
Yup, Michal had a good example of why this could cause issues in cases where default interface methods are present. Personally, especially for an initial version of this feature, I'd be 100% fine with not having the generate closure value type be a |
@HaloFour , casting is not possible in C# or any other .NET language. Compiler can emit interface impl for |
And equally forbidden. Casting a |
@HaloFour , I understand that. It is forbidden by compiler as well as casting |
It looks to me like this has all the same benefits as #1060, but is much simpler and more straight forward. Are there any differences besides declaration syntax that I'm missing? |
@timcassell , we can exclude capturing ref structs for simplicity. However, I don't see any issues with that if ref struct constraint on generic type will be implemented (I saw that appropriate proposal exists already). From other side, ref struct can implement interface if it doesn't have default implementation (that could be checked by compiler easily). From #1060 personally I don't like many aspects:
|
I think since C# supports ref fields in ref structs now, mutability of locals can be allowed without requiring the parameter to be by-ref. The display struct would just be filled with refs to the locals, instead of the actual values. |
There are some cases that can be solved by this but it's a case where the details get complicated. For example you can't capture a |
Overall like the motivations and general outline of the proposal here. Think the challenge with this proposal, and really any proposal that moves capture into int local = 42;
M(
() => local = 13,
() => Console.WriteLine(local));
void M(Action a1, Action a2)
{
a1();
a2();
} This needs to print "13". Even though the second lambda in this case doesn't mutate This behavior pattern in the above sample needs to be true even if you expand the set of types a lambda can target to include functional interfaces. I do not think that LDM would accept that whether or not mutations are observable is dependent upon whether the target type is a delegate or functional interface. Instead I think LDM would maintain the position that lambdas allow and observe mutations. There are a few mitigations around this:
These are very real challenges of integrating |
One advantage I realized #1060 has over this is method overloading. Legal: public void Func<TPredImpl>(ValueFunc<int, bool>.Reference<TPredImpl> callback);
public void Func<TPredImpl>(ValueFunc<int, Task<bool>>.Reference<TPredImpl> callback); Illegal: public void Func<TCallback>(TCallback callback) where TCallback : IFunc<int, bool>;
public void Func<TCallback>(TCallback callback) where TCallback : IFunc<int, Task<bool>>; I definitely prefer the syntax here, but overloading is not possible without #2013. |
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
This proposal is inspired by FI from Java but it's not just a copy. It's aimed to solve some problems with delegates and has different implementation.
Motivation
We have several ways to pass executable code as an argument to method:
The last approach typically used to avoid on-heap allocation if variables are captured in read-only manner. It may look like as following:
This allows to declare custom value type implementing
IComparer<T>
interface and place additional context to the fields of such value type. Then I can instantiate value type on the stack with all necessary context passed through constructor and use it in delegate-like manner.Of course, I can achieve the same goal with
Comparer<T>
delegate but it requires allocation on the heap plus allocation of captured context. Additionally, it's not possible to capture variables of ref-like value types.With #1148, it will be possible to use this pattern with ref-like value types. However, the type itself should be still hand-written.
It's a little bit verbose. What if compiler will allow to convert lambda expression and its captured context to automatically generated type implementing required interface?
Goals
ref struct
typesNon-goals
This proposal is not about
Proposal
The key idea is to allow lambda expression to be passed as parameter if it satisfied to one of the following conditions:
struct
,ref struct
,class
are also allowed.The semantics of FI is the same as in Java: it may have multiple DIMs and single method without default implementation. For instance,
IComparer<T>
can be treated as functional interface.In this case, calling of
Sort
method can be done without boilerplate code:C# compiler uses translation strategy depends on the target type of lambda expression:
The last use case requires special procedure for handling mutability of captured variables. However, it's easier than it might seem at first glance. We know that value of ref-like struct exists on the stack only, so compiler just emits the code that rewrites the mutated local variables.
For instance, the following code
can be translated to
Resolving issues with rvalue
In the current version of C# compiler, lambda expression can be used in rvalue position only if target type is delegate type. This proposal extends context where lambda expression is allowed so it's necessary to cover translation strategy for such cases.
Target type is functional interface:
Should be translated to compiler-generated class.
Target type is var. In this case compiler should allow to apply new operator to interface type:
Should be translated to compiler-generated value type. Very similar to anonymous type instantiation using
new { }
syntax that already exists in C#.The last use case with ref-like value type is under consideration:
Should be translated to compiler-generated ref-like value type. This is the only place when new syntax should be introduced. However, it can be solved with #2975.
IMO, this proposal might replace #302 which is rejected now.
The text was updated successfully, but these errors were encountered: