SE-0442: Allow TaskGroup's ChildTaskResult Type To Be Inferred

Hello, Swift community.

The review of SE-0442: Allow TaskGroup's ChildTaskResult Type To Be Inferred begins now and runs until August 6th.

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to me as the review manager, either via email or the forum messaging feature. When contacting me directly, please put "SE-0442" somewhere near the start of the subject line.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  • What is your evaluation of the proposal?
  • Is the problem being addressed significant enough to warrant a change to Swift?
  • Does this proposal fit well with the feel and direction of Swift?
  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

More information about the Swift evolution process is available at

https://github.com/apple/swift-evolution/blob/main/process.md

Thank you,
Doug Gregor
Review Manager

19 Likes

Toolchains with this implementation are available:

1 Like

This has been an obvious gap in the original design of withTaskGroup, and I'd assumed the nuisance was necessary — either to make type inference possible, or to keep compilation times reasonable.

If in fact this design is practical, it's a very clear +1 from me.

The situations the proposal outlines in which it becomes necessary to make the type explicit are indeed confusing corner cases. However, they are entirely of a piece with type inference in Swift in general. They should not hold back this proposal; the way to address them is with ongoing improvements to compiler error messages and tooling.

7 Likes

Obviously +1.

Good.

But along with this change, the parameter names childTaskResultType and returnType should be both replaced with _, as is the case with all arguments that are not used within function bodies.

It would also be useful to amend the proposal to provide an example of an API that should not have a similar .self default for a metatype argument, when inference is possible. I do not believe these cases exist. If no example exists, then this proposal should be broadened in scope so that a similarly narrow-scope proposal will never be necessary in the future.

Disclaimer: I have helped out a bit polish up the proposal and verify it can be safely rolled out etc.

+1, this is a good proposal and great improvement that we can now do ever since improvements in type inference.

We verified with @xedin who has experience on type inference impact on performance, especially in multi-line closures which task groups have, and we're not concerned about the compilation complexity changes caused by this (i.e. this should not have a big negative impact on compile times).

It also does not prevent any future task group work we'd like to do (e.g. converging Throwing... versions, or maybe a with-less API someday, as mentioned in Future Directions).

Thanks for fleshing out the proposal and implementation @rlziii!

7 Likes

For me that was an important concern, thanks for clarifying!

+1 to the change, that was indeed missing.

The parameter names (childTaskResultType and returnType) are used within the implementation body of withTaskGroup(...) and withThrowingTaskGroup(...), and therefore cannot be changed to _.

I don't quite understand the second part of your post. Swift allows for using metatype default arguments as a feature for cases such as this proposal (and similarly with how the default argument of just returnType for withTaskGroup(...)/withThrowingTaskGroup(...) worked previously) where type inference is desired.

As somebody who not that long ago wrote their first code using withTaskGroup I fully support this change. I believe that the ergonomics are much improved. And at least in my case, would reduce some confusion about why the type needed to be specified in the first place.

+1

Can you help me find that, please? I haven't been able to. Unless the metatype is a superclass, it's never necessary to use a named metatype parameter, but it can be used for aliasing.

You can't document "argumentLabel _", but that's a bug that should be fixed along with the implementation of this proposal.


There are three options for metatype defaults.

  1. A concrete default. Not relevant.
func concreteDefault<T>(_: T.Type = String.self) { }
concreteDefault()
  1. A generic default which cannot be inferred. Useful only before coding the body is completed.
func genericDefault<T>(_: T.Type = T.self) { }
genericDefault() // Generic parameter 'T' could not be inferred
  1. A generic default which can be inferred.
func genericDefault<T>(_: T.Type = T.self) -> T.Type { T.self }
genericDefault() as String.Type

Have you ever come across an API that didn't have that default, and was better for it?

This has nothing to do with this proposal; feel free to start another thread on it but let’s keep this thread focused on what’s proposed.

1 Like

Will do. I'm done.

But, this is wrong. The fact that a parameter has a name, and no default, both suggest to the human mind that it has a level of explicit necessity that childTaskResultType does not. Here, resulting in a discussion and proposal that should have been uncontroversial by way of standard practice.

I've written withTaskGroup(of: Void.self) so many times… yes, please, let's allow type inference here and be done with that.

1 Like

Isn't withDiscardingTaskGroup addressing Void case?

Sorry, I was using Void as a placeholder there.

1 Like

I see nothing to quibble about here. +1

This looks great, a big improvement to ergonomics. It's a shame that the type inference relies on the first use of the task group being to call .addTask (I think it's plausible that someone may want to call group.isCancelled for example) however I think it's a reasonable compromise especially if it keeps compilation times down given the escape-hatches available.

2 Likes

I would love to have this.

+1

I think unsafeBitCast(_:to:) is a counter-example. In the early days of Swift, we explicitly added the to argument to make it clear from the call site what case was being performed, even though type inference could always infer from the result type.

Doug