SE-0315: Placeholder types

Hi everyone. The review of SE-0315: Placeholder types begins now and runs through July 6th, 2021.

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 the review manager. If you do email me directly, please put "SE-0315" somewhere in 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/master/process.md

Thank you for participating in improving Swift!

Joe Groff
Review Manager

31 Likes

Looks great!

The 'motivation' leaves one curiosity unanswered for me: why now? Does this assist some of the concurrency proposals?

  • What is your evaluation of the proposal?

+1

  • Is the problem being addressed significant enough to warrant a change to Swift?

I think so. The "all or nothing" nature to the previous ways of "assisting" the typechecker always felt a little punishing.

  • Does this proposal fit well with the feel and direction of Swift?

Yes, it definitely fits the "progressive disclosure" element, where this can help you specify some types without needing to fully understand/specify the others.

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

N/A

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Full read.

From my perspective, the "why now" is because the constraint system is well-equipped to handle this sort of a feature now. This feature fits perfectly into Swift's bidirectional type inference model, and it's a nice ergonomic improvement, similar to some of the other type system quality-of-life improvements that have gone through evolution alongside, but independent of, the concurrency proposals. With all of the recent work on the new diagnostics architecture and "hole types" used internally in the constraint system to diagnose type ambiguity, this feature also has a relatively straightforward implementation.

18 Likes

Yeah, pretty much what @hborla said. I don’t have a great first-party account for “why now” other than I ran into a situation where I wanted to use a feature like this, people responded well to the pitch, and the implementation was straightforward enough that I could do it in my spare time. :grinning_face_with_smiling_eyes:

9 Likes

So I was able to read through this: the proposal seems well written and understandable to me, but… I am not sure how this will really pan out in practice. From what I can tell this could either be a very, very, VERY, welcomed change for systems like Combine, AsyncSequence, LazySequence and any other chained type system API or… it could not really address the underling concerns for those APIs. From just what the proposal poses a big +1 but in truth I need to set aside some time to use this and see what we can do with it.

1 Like

I'm now reading the proposal. I have a question.

As is the case today, function signatures under this proposal are required to have their argument and return types fully specified.

Does it mean that, placeholder types cannot be used in types of result positions? If so, I cannot understand why this limitation is required. Is there any technical reasons to forbid it?

Similar things are already achieved with opaque result types:

// type of result is automatically inferred as Int
func value() -> some Numeric { return 42 }

I think it makes sense to have this declaration.

func value() -> [_] { return [42] }

I understand it is difficult to infer exact types in argument position, but about result position, it seems almost equal to these declarations :thinking:

let value: [_] = [42]
var value: [_] { return [42] }
2 Likes

The quoted restriction is there mainly for the purpose of consistency with the existing rule that generic types must be fully specified in function signatures (aside from the noted exception within the body of the generic type itself).

Allowing that sort of type inference in function signatures would be a change entirely orthogonal to placeholder types, and IMO deserves a completely separate evolution discussion if people think it’s a restriction worth lifting.

3 Likes

I now understand. Thank you!

I didn't noticed this is OK:

let value: Array = [42]
1 Like

I am very happy to see how the final shape of this proposal. This proposal fits well with the overall feel and direction of the language. It’s a very natural-feeling extension to Swift’s current rules, though I’m not familiar with how it compares to other languages. I participated in the earlier discussion, and really have to thank @Jumhyn for patiently working through some of the corner cases with me :slight_smile:

One final thought: Although internally this and other features are modeled as types, I wonder if for user-facing semantics it’s more accurate to call it a type placeholder rather than a placeholder type. This goes back to my point that there’s nothing about _ that means “type”: it’s just a placeholder, and it’s the place that’s being held here which is a type.

26 Likes

+1 for the feature
-1 for the spelling.

The feature seems much closer to genetic typealiases so I would love to see this modeled in a similar fashion. A spelling like inferredtype or similar would convey the same information.

My understanding is the one of the main use cases is to capture verbose types and to that end I much rather prefer clarity at the time of reading.

I think the best analogy I have for this is the Swift enum pattern matching default : vs case _ : It would be confusing if Swift used only an underscore for matching all, so we say case _ but the word default stands by its own.

3 Likes

I think this slight phraseology change makes sense—a type-checked Swift program has no knowledge of a "placeholder type," there's just a notion in the source of being able to write _ as a placeholder for a type, i.e., a "type placeholder." I'd be in favor of accepting this proposal with an amendment to adopt the latter terminology.

FWIW, it was raised in the first discussion of this feature that Rust offers a similar functionality under the name inferred type.

8 Likes

I'm +1 on the proposal. This is a small but logical extension and fits in with other uses of _ in the language in a predictable way. It's a fairly exotic feature, but it composes well with the rest of the language.

I agree with @xwu that a better name for this is "type placeholder" than "placeholder type". This is syntax for an inferred type, it isn't itself a type.

-Chris

7 Likes

This looks excellent. In my opinion the single underscore spelling is a good reuse of that syntax. I also agree with the suggested terminology change to "type placeholder". I read the proposal carefully, because I was concerned about cases where this could cause more unclarity than help. I really appreciate the proposal's consideration of a wide number of possible variations and questions. Nice work!

+1

Looks like it can simplify and clarify code without significantly complicating the language.

Have read proposal and discussion.

Support the name change to “type placeholder” suggestions.

Trivial text error in this paragraph, frobnicate1 doesn’t exist, should just be frobnicate:

Under this proposal, only frobnicate , frobnicate3 and frobnicate6 would compile without error ( frobnicate1 , of course, …

With respect to naming I just want to mention that the term “placeholder type” is already used in the documentation for the type parameters of a generic function, e.g. the T in func swapTwoValues<T>(_ a: inout T, _ b: inout T).

3 Likes

Ha! Great callout, @Martin. :slightly_smiling_face:

Unfortunately I believe that TSPL sits outside the reaches of the evolution process, but it's definitely good to surface that. Even if this feature were accepted under the "type placeholder" terminology, that passage might have to change—IMO it would be somewhat pedagogically troublesome to try to distinguish between "type placeholder" and "placeholder type." Perhaps that's an argument for adopting Rust's "inferred type" nomenclature...

4 Likes

It doesn’t seem much of a problem to me that T is a placeholder and so too is _. In any case, how a book explains generics shouldn’t impact what we think the best name for this feature is, I think. The authors can always come up with a different word than “placeholder” for that one passage.

5 Likes

I do think there's a lot of value in using terminology that Swift programmers have already internalized. Given that it's pretty common to describe type parameters as "placeholders" for types that will be substituted at the call-site (and how those generic arguments get substituted is somewhat of a separate thing), it feels like we're overloading the term "placeholder" when we have an existing term to describe what this feature is. Essentially, this feature allows the programmer to have more fine-grained control over type inference by providing explicit syntax for an inferred type. You can use this syntax in a generic argument position or inside a structural type to ask the compiler to infer a specific component of a type. In my opinion, if the feature is described in terms of type inference, we should consider using that terminology in the name of the feature.

14 Likes
  • What is your evaluation of the proposal?

+1 this solves a need that has been a strong pain point for writing out complex signatures with systems like lazy sequences, Combine, and now AsyncSequence. Erasers like AnyCollection or AnyPublisher often come at either correctness bugs that prove really gnarly to address or at best performance bottlenecks; all because it is difficult to type out the entire signature of things. This takes a step in the right direction to resolve this.

  • Is the problem being addressed significant enough to warrant a change to Swift?

Absolutely!

  • Does this proposal fit well with the feel and direction of Swift?

It feels thematic, however I do worry from a long term standpoint what this means for the direction of other features that would resolve this even further. It is related to some types (which don't work well for associated types).

let publisher: _<Int, _> = Just(makeValue()).setFailureType(to: Error.self).eraseToAnyPublisher()

This example from the future directions is not exactly what would be wanted: instead you want to say I have some publisher which its element is known to be Int, and it has some failure type.

I would be interested on how the authors think that should be handled? Should it be a placeholder type in that case? or should it be a some type?

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

I am not sure this really compares well (maybe I guess objc's id covariance).

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I read the proposal a few times, discussed it with other folks that work on Combine (which this really has a BIG impact to), built my own version of the toolchain rebased on the main swift branch to test out how it would work. Overall this seems pretty good; both implementation and writing wise.

  • Notes:
    I really would find it helpful to have a more detailed discussion on how this can interact or how it fits in with some types w.r.t. ABI stability and where this pushes some types to from a brittleness standpoint to return values. If we could linguistically dig ourselves out of the eraser type hole that would be great!
1 Like

I think Inferred Type is a better name for this functionality.

3 Likes