Skip to content

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] Support extra generic type params in custom AsyncMethodBuilders #3723

Closed
MMJZ opened this issue Jul 25, 2020 · 10 comments
Closed

Comments

@MMJZ
Copy link

MMJZ commented Jul 25, 2020

Currently C# will fail to compile any async block using a type with one than one generic type parameter.

I believe this is so that the type T in SetResult(T result) on AsyncMethodBuilder types has an obvious type parameter.

I propose that this requirement be relaxed, so that AsyncMethodBuilders can have arbitrary type parameters and it's the first of these type parameters that is required in the argument to SetResult. This would allow me to carry extra type information in an async block that doesn't interact with the block itself.

The same issue was raised in #2273 but was framed as a bug rather than a feature request. Supporting multiple type parameters would also allow functionality proposed in issues like #1407 to be more powerful.

@jnm2
Copy link
Contributor

jnm2 commented Jul 25, 2020

This surprised me too. I think the async result type should be detected from the SetResult parameter rather than making assumptions about generic type parameters. This would mirror the same detection on the consuming side with GetResult which determines the result type of an await.

@YairHalberstadt
Copy link
Contributor

Also mentioned here: #3403

@louthy
Copy link

louthy commented Aug 9, 2020

have arbitrary type parameters and it's the first of these type parameters that is required in the argument to SetResult

This limitation of it being the first of the type parameters would be too restrictive. Pretty much every type in the functional programming world has the bound parameter as the last one, so restricting to the first would mean no way to make this work for Either<L, R>, Reader<Env, T>, Writer<W, T>, State<S, T>, Validation<Fail, T>, etc.

There should be an option to choose the first or the last, perhaps through an additional attribute:

    [AsyncMethodBuilder(typeof(EitherAsyncMethodBuilder<,>))]
    [AsyncMethodBuilderParameterSelection(AsyncMethodBuilderParameterSelection.Last)]
    public struct EitherAsync<Left, Right>
    {
        ...
    }

    [AsyncMethodBuilder(typeof(ReaderMethodBuilder<,>))]
    [AsyncMethodBuilderParameterSelection(AsyncMethodBuilderParameterSelection.Last)]
    public struct Reader<Env, T>
    {
        ...
    }

@jnm2
Copy link
Contributor

jnm2 commented Aug 10, 2020

@louthy It would be better to detect the SetResult type than to look at the generic type parameters.

@MMJZ
Copy link
Author

MMJZ commented Aug 10, 2020

@jnm2 I'm happy to adopt the 'determine through SetResult' pattern for this proposal but I'm nervous that doing so might make the proposal less sensible overall.

public async TaskLike<int, EException> Blah(Blah blah)
{
  return "stringForSomeReason";
}

Blocks like these might be less readable if the SetResult function determines the block's return type, because this isn't at all obvious from the type signature. There would ideally be an obvious mapping from async return type to expected block return type (Task -> T and Task -> void and so on) so the return type should probably be inferred from the async type itself.

It's also less obvious to infer a block return type if you can choose a type parameter to represent the type, instead of learning only once that the first parameter is the relevant type. It's arguably a smaller ask to demand a particular type order, even where this is at odds with functional patterns.

@jnm2
Copy link
Contributor

jnm2 commented Aug 10, 2020

The result type is not inferred from a type parameter when you await, and that hasn't caused confusion in practice.

@skarllot
Copy link

@louthy I think that SetResult should define the type of result and method builder class should always match the number of generic type parameters.

The EitherAsync<Left, Right> method buider should be EitherAsyncMethodBuilder<Left, Right> and its SetResult should receive an instance of Either<Left, Right>.

The OptionAsync<A> method builder should be OptionAsyncMethodBuilder<A> and its SetResult should receive an instance of Option<A>.

@AnthonyLloyd
Copy link

This would allow an implementation of something like ZIO<R,A,E> instead of Task. Could open up alternatives to exceptions and DI frameworks for async APIs.

@jzabroski
Copy link

I really don't like the current .NET Task API. In particular, the fact I need to know the ConfigureAwaitable state of the transitive closure of all Awaitable objects is a terrible design and the same problem Java Futures have.

In this sense, I view this feature request as a "must have" and key to improving the currently cumbersome async/await Task API.

Agree with @AnthonyLloyd that supporting more functional effects model of Task (like the Scala ZIO library) is the obvious solution to a real pain point in writing truly modular, EASIER TO DEBUG, systems.

@dr1rrb
Copy link

dr1rrb commented Nov 30, 2023

I think that it would be great to have multiple types parameter on a custom Task, however short term, just using the type of the SetResult would be a great improvement. We could even imagine to have multiple returns types, one for each SetResult overload.

@dotnet dotnet locked and limited conversation to collaborators Nov 30, 2023
@333fred 333fred converted this issue into discussion #7735 Nov 30, 2023

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants