SE-0361: Extensions on bound generic types

Hello, Swift community.

The review of SE-0361: Extensions on bound generic types begins now and runs through July 4th, 2022.

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 by email or DM. When contacting the review manager directly, please keep the proposal link at the top of the message and put "SE-0361" 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/main/process.md

Thank you,

John McCall
Review Manager

26 Likes

+1

-1 for supporting additional sugared versions for array and optional. It’s confusing to me to see the square brackets in this position, the same goes for optional. Otherwise we are going to see things like

extension Optional<String?>? {..}
or
extension [Array<String?>?]? {..}

I think folks who want to extend these types should be familiar with the type behind the sugar and standardize on using the more generic form.


If a generic type has a sugared spelling, the sugared type can also be used to extend the generic type:

extension [String] { ... } // Extends Array<String>

extension String? { ... } // Extends Optional<String>
5 Likes

Why is that a concern specifically here and not in all the other places where all notations are already supported? Have you encountered authors who write like this?

14 Likes

When I started writing swift I would mix these. Folks now prefer to use linters. syntactic_sugar Reference

Extensions have always been special ( weird rules around scopes). I think allowing too much sugar will spoil and distract from the main feature.

1 Like

+1

I really like these kinds of changes to the language. Additions that subtract from the cognitive load of exceptions to the rules. As long as we make the language more consistent with fewer exceptions and special rules, I am all for it.

That is why I also fully agree with supporting syntactic sugar that works everywhere else.

Let me add that having a source breaking release on the horizon, we should do more to prune the language, even if it involves some source breaking changes. We should cleanup the language at any chance that we get.

Some cleanup involves adding features that subtract from the complexity that results from the exceptions. The other side is much harder to do, but we should be brave enough to drop the features that do not work as we hoped. I really miss the early days when we could drop features like classic C for loop, ++, curry syntax, and more.

2 Likes

I don’t think anyone will be confused about what type [String?] is, and for what it’s worth, that is how it will appear in almost every other location, so if anything I think people see this as the canonical form.

17 Likes

+1 for the simplicity it brings.

If people are writing extensions on String??? the rest of the code base is probably not super clean anyways.

I think I agree that writing extension [String] { ... } feels "ugly", but I think we should probably let linters and team code standards dictate how people decide to write their code.

Like you said,

That reference prefers sugared forms in parameter and variable declarations, but the non-sugared form when referencing nested types, meta types and when referencing .self on a type. That's fine! I think I like this style, too. The linter will probably be updated to also prefer the non-sugared versions in extensions.

7 Likes

Huge +1 for me, this will be a very welcome addition.

2 Likes

+1

It’s super obvious this should have been the case from the start, to the point it makes me wonder why it wasn’t?

1 Like

Until Swift 3.1, extensions of concrete types were not allowed to make generic parameters concrete. Eg, this was actually rejected:

extension Array where Element == Int {}

Extensions were only permitted to introduce protocol and superclass requirements on top-level generic parameters. This was due to a long-standing limitation where what is now SubstitutionMap was formerly represented as a fixed-length array of archetypes, with the order and number of elements having to match up between the type and the extension.

A while later, right before ABI stability we introduced the ability to extend a generic typealias, to make a source-compatible change where Range and ClosedRange were folded together:

typealias Foo = Array<Int>
extension Foo {}

At this point, the limitation being lifted by this proposal was entirely artificial, because the ability to pick apart a bound generic type into an equivalent set of same-type requirements was already in place.

A future direction is to allow extensions to introduce new generic parameters:

extension <T> Array where Element == (T?, T?) {}

Or

extension Array<some Equatable> {}
10 Likes

I like the 'Detailed Design' section, it does a really good job going through the edge cases. I wonder if we could say a few words about generic typealiases in this context as well. Eg,

typealias G<T> = [T?]

extension G<Int> {}

There's also a restriction here that might be worth writing down here even if it's not directly related. You can only extend a generic typealias if the root of the underlying type is a concrete nominal, or some other alias that recursively obeys this restriction. So this is not allowed:

typealias G<T: P> = T.A  // dependent underlying type
protocol P { associatedtype A }

struct S: P {...}

extension G<S> {...}

Extension binding would have to do associated type inference to resolve G<S> to S.A, but we have to do extension binding before performing any other name lookups.

9 Likes

+1
No brainer, easier to read, easier for newcomers to understand.

Plays well with other improvements to generics such as some/any. Good job!

+1 Easy vote.

Beautiful and simple. Absolutely yes.

The overall proposal looks great to me. Thanks to the author!

In the proposal the author mentioned why we don't allow extension Pair<Int, _>:

The types specified in the type argument list must be concrete types. For example, you cannot extend a generic type with placeholders as type arguments:

extension Pair<Int, _> {} // error: Cannot extend a type that contains placeholders

Rationale: When _ is used as a type placeholder, it directs the compiler to infer the type at the position of the underscore. Using _ in a bound generic extension would introduce a subtly different meaning of _, which is to leave the type at that position unconstrained, so Pair<Int, _> would mean different things in different contexts.

While I understand _ was introduced in type declarations as type placeholder, using extension Pair<Int, _> to extend the Pair type seems natural to me (the extension only cares about the first generic parameter).

In Swift the underscore keyword already have different semantics, like let _ = .... I am not sure if we really want to allow only one meaning for _ in type declarations. If there is no technical difficulty I would prefer extension Pair<Int, _> over extension Pair where T == Int.

3 Likes

Yes, I agree. Sure, the meaning here is slightly different, but I don't think it will lead to any confusion. Besides, the _ here would be unconstrained in the extension code, but whenever that code is used in practice, the type will be constrained.

1 Like

Another option to avoid the potential semantics ambiguity in extension Pair<Int, _> is to use extension Pair<Int, *>, as the asterisk character has already been used for matching-any semantics like @available(macOS 11.0, *).

8 Likes

The problem is that this may introduce an ambiguity for _ in type position. Having a way to write an unconstrained type parameter would also be useful for opaque result types and constrained existential types, e.g. some Publisher<_, Never> and any Publisher<_, Never>, but then if you were to use that on a local variable, whether _ means "unconstrained" or "inferred" is ambiguous.

I agree that it would be very useful to have a way to write this, but I'm not sure _ is the answer, and I think we should have that discussion more holistically to find a solution that works for bound generic extensions as well as primary associated type constraints.

7 Likes
  • What is your evaluation of the proposal?

Big +1 – It feels like it should have been there already.

Except I'm not sure about sugared spelling. Especially this possibility mentioned in the future directions:

extension <Wrapped> [Wrapped?] { ... }

Crypticity of the continuous mix of angles and sugars...
But this is one possibility of the future, and, for newcomers sugars may be welcomed. So I'm not strongly against with.

  • 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?

Yes!

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

A reading.

1 Like