SE-0358: Primary Associated Types in the Standard Library

Hello, Swift community.

The review of SE-0358: Primary Associated Types in the Standard Library begins now and runs through May 30th, 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 me as the the review manager by email or direct message on the Swift forums. If you do email me, please put [SE-0358] at the start of the subject line.

What goes into a review of a proposal?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift.

When reviewing a proposal, here are some questions to consider:

  • 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 in advance for helping to make Swift a better language.

John McCall
Review Manager

15 Likes

Agree with the overall approach and most of the inclusions and exclusions in the proposal as it is. I think this is a reasonable evolution of the standard library given the adoption of the underlying language feature. I haven’t used something similar in other languages. I’ve read this version of the proposal quickly but participated in previous iterations and followed the discussions in the past.

One point that’s part-question and part-suggestion: I’m not sure on reflection I understand how useful a Strideable<Stride> will be… Do authors have any use cases in mind?

Within the standard library, it was necessary to implement certain protocol requirements differently when there’s a floating-point stride, but doing that correctly was very difficult (at least for me), and I think it’s really a different kettle of fish when we’re talking about the problems being solved when using Strideable as a constraint to write useful generic algorithms.

To me it “feels” like the Stride type is rather more analogous to the Index type for collections in this way, much more likely to be an attractive footgun than to help users achieve testable, correct generic code.

1 Like

Strideable<Stride> is based on the sheer number of extensions we have in the stdlib that constrain Stride, as well as the (subjective) obviousness of it per point 2 in the proposed API guidelines.

./stdlib/public/core/ClosedRange.swift:134:1:where Bound: Strideable, Bound.Stride: SignedInteger {
./stdlib/public/core/ClosedRange.swift:139:23:extension ClosedRange where Bound: Strideable, Bound.Stride: SignedInteger {
./stdlib/public/core/ClosedRange.swift:180:1:where Bound: Strideable, Bound.Stride: SignedInteger, Bound: Hashable {
./stdlib/public/core/ClosedRange.swift:201:1:where Bound: Strideable, Bound.Stride: SignedInteger
./stdlib/public/core/ClosedRange.swift:442:23:extension ClosedRange where Bound: Strideable, Bound.Stride: SignedInteger {
./stdlib/public/core/ClosedRange.swift:478:3:  where Bound.Stride: SignedInteger
./stdlib/public/core/MigrationSupport.swift:144:61:public typealias ClosedRangeIndex<T> = ClosedRange<T>.Index where T: Strideable, T.Stride: SignedInteger
./stdlib/public/core/MigrationSupport.swift:160:17:extension Range where Bound: Strideable, Bound.Stride: SignedInteger {
./stdlib/public/core/MigrationSupport.swift:170:23:extension ClosedRange where Bound: Strideable, Bound.Stride: SignedInteger {
./stdlib/public/core/RandomAccessCollection.swift:271:34:extension RandomAccessCollection where Index: Strideable, Index.Stride == Int {
./stdlib/public/core/RandomAccessCollection.swift:277:1:where Index: Strideable,
./stdlib/public/core/Range.swift:212:1:where Bound: Strideable, Bound.Stride: SignedInteger {
./stdlib/public/core/Range.swift:218:1:where Bound: Strideable, Bound.Stride: SignedInteger
./stdlib/public/core/Range.swift:309:17:extension Range where Bound: Strideable, Bound.Stride: SignedInteger {
./stdlib/public/core/Range.swift:677:3:  where Bound: Strideable, Bound.Stride: SignedInteger
./stdlib/public/core/Range.swift:1018:3:  where Bound.Stride: SignedInteger
./stdlib/public/core/Range.swift:1023:3:  where Bound.Stride: SignedInteger
./stdlib/public/core/Stride.swift:124:15:  ///         where T.Stride: ExpressibleByIntegerLiteral
./stdlib/public/core/Stride.swift:260:22:extension Strideable where Stride: FloatingPoint {
./stdlib/public/core/Stride.swift:275:22:extension Strideable where Self: FloatingPoint, Self == Stride {
./stdlib/public/core/Stride.swift:404:1:where Element.Stride: BinaryInteger {
./stdlib/public/core/Stride.swift:622:1:where Element.Stride: BinaryInteger {
./stdlib/public/core/Stride.swift:746:3:  where Element: Sendable, Element.Stride: Sendable { }
./stdlib/public/core/Stride.swift:748:3:  where Element: Sendable, Element.Stride: Sendable { }
./stdlib/public/core/Stride.swift:750:3:  where Element: Sendable, Element.Stride: Sendable { }
./stdlib/public/core/Stride.swift:752:3:  where Element: Sendable, Element.Stride: Sendable { }
3 Likes

Unless I'm mistaken, none of these uses found in current standard library extensions are utterable with primary associated type notation: only one of these is a same-type constraint with a concrete type, Int, and that one constrains the stride type of RandomAccessCollection.Index, which pointedly is not parameterizable with this proposal.

I think what would be convincing to me, rather, is some sense that it's possible to write useful and testable generic algorithms that make use of, say, Strideable<Int>. Even a small handful of examples would be nice. I ask this because what has convinced me most of the argument that Collection.Index shouldn't be a primary associated type are the discussions where users state what they'd like to do with such a parameterization, and it turns out that use would be incorrect because, e.g., it requires making assumptions about contiguous integer indices that wouldn't actually be guaranteed, leading to correctness pitfalls despite being syntactically permissible. Therefore, I think applying the same test and thinking here would go a long way to substantiate that what appears "obvious" is actually useful rather than an attractive footgun.

9 Likes

A primary associated type expression doesn't have to be a concrete type, at least in generic function signatures; you can write something like Element: Stridable<some BinaryInteger>. So those functions would indeed be able to use primary associated types, unless there are ABI concerns.

You currently can't write that in a constrained protocol type (any Stridable<some BinaryInteger>), but that's an anticipated future direction.

9 Likes

Quick clarification: by "obviousness", I did not mean that the primary annotation is obviously useful -- I meant that, to me, it seems obvious that in some Strideable<some FixedSizeInteger>, the type name within the angle brackets refers to the Stride type. (As required in point 2 of the proposed API guidelines.)

  1. Consider clarity at the point of use. To prevent persistent confusion, people familiar with the protocol ought to be able to correctly intuit the meaning of a same-type constraint such as some Sequence<Int> .
4 Likes

Should the proposed OptionSet<Element> be excluded?
The primary associated type is usually Self (by default).

The only custom API in OptionSet is a non-failable init(rawValue:).
Otherwise, everything is inherited from SetAlgebra and RawRepresentable.
Possibly using OptionSet implies O(1) constant time for its SetAlgebra operations?

2 Likes

This conflicts with the guidance in the proposal, which repeatedly mentions only same-type constraints, and letting usage of same-type constraints inform which things should be primary associated types:

look at its existing clients to discover which associated types get typically mentioned in same-type requirements

Don't feel obligated to add a primary associated type just because it is possible to do so. If you don't expect people will want to put same-type constraints on a type, there is little reason to mark it as a primary.

What you're saying is that if an associated type is commonly used by any constraints at all, that should be a strong signal that it deserves to be a primary associated type -- i.e. that Stride should be a primary associated type because people might want to constrain it to BinaryInteger (which is a conformance constraint, not a same-type constraint).

I guess what I'm saying is: in a world where you can use opaque types in this way, everything ends up being a same-type constraint to a placeholder opaque type. Ultimately this makes the guidance not as discerning as it might appear to be.

3 Likes

The angle-brackets-after-a-protocol-name feature can be used to constrain associated types to protocols, not just to constrain them to one specific type. I'm expecting that beyond cases like Collection<String>, protocol constraints will in fact be the more typical use case.

The term "same-type requirement" comes from the title and text of SE-0346, "Lightweight same-type requirements for primary associated types".)

If this choice of terminology invites misunderstanding, perhaps it would make sense to update both of these proposals with a better phrase.

Hm, this is an extremely good point; I think this is an oversight that needs to be fixed. But rather than removing the primary declaration, I would prefer to correct the proposal to use the right type.

(I believe that OptionSet.Element is only customizable because of legacy compiler limitations that are no longer relevant. If we designed this protocol today, we'd define it as protocol OptionSet: RawRepresentable, SetAlgebra where Element == Self. (Possibly also adding RawValue: FixedWidthInteger.) However, such constraints weren't possible / did not work correctly at the time this landed. And of course, fixing this now would be both ABI- and source-breaking.)

Judging from use cases (i.e., guideline 1), RawValue is clearly the right choice for the primary type: for example, most of OptionSet's utility comes from extension methods / default implementations where RawValue: FixedWidthInteger.

Question is: would OptionSet<RawValue> run afoul of guideline 2 (clarity of point of use)? To me, it feels fairly obvious (in hindsight) that some OptionSet<UInt32> would mean RawValue == UInt32 -- especially given the note on Element above.

This case is somewhat similar to LazySequenceProtocol, where naively one might expect Element to be the primary type (e.g., for consistency with Sequence), but once one thinks about it a little more, Elements becomes the "obviously" correct choice. In that case, I didn't care enough about the matter to press it -- I expect most people who need to be explicitly mentioning the lazy protocols will be comfortable with using the classic generic constraint syntax. (I did conveniently ignore @Ben_Cohen's opaque return type case, where some LazyCollectionProtocol<some BidirectionalCollection<String>> would enable additional functionality not currently achievable with where alone.) To me, OptionSet's case feels far less subtle than the lazy protocols, so I want to press on this a bit harder.

Guideline 3 doesn't seem overly relevant here -- clearly there would be some utility to being able to spell OptionSet<some FixedWidthInteger> or even OptionSet<Int64>. OptionSet doesn't feel like it is in the same category as ExpressibleByIntegerLiteral or KeyedEncodingContainerProtocol; although the use cases aren't wide spread, they clearly exist.

<off topic>

I would love for that to be the case, but sadly this is not spelled out as a requirement in OptionSet's documentation. It is true for the default implementations provided, at least as long as the RawValue is one of the standard fixed-width integer types. Manual conformances to OptionSet that use different implementations ought to be rare (this is largely a utility protocol), but I'd be willing to bet that someone out there has found a use case where doing that makes sense. :nerd_face:

</off topic>

6 Likes

Indeed, this is exactly my position. The stated goal for SE-0346 was to "make generic programming in Swift feel more natural and approachable". Generally, I don't think the Standard Library or the API guidelines should second guess this goal by preventing Swift developers from using the lightweight constraint syntax -- unless of course there is a clear reason to do so.

My intention with the third guideline was to cover cases like ExpressibleByIntegerLiteral or KeyedEncodingContainerProtocol. These are protocols that have one associated type, and it would technically be a suitable primary, but actually declaring it as such would be pointless, as these protocols aren't really designed to be used generically in practice. The guideline is also there to reassure folks that it's okay not to declare a primary type if doing so would go against one of the other points.

(I expect that guideline 2 (clarity at the point of use) is going to be a far bigger sticking point than guideline 3. Personally, I find it regrettable that we don't support mandatory (or even optional) argument labels within angle brackets, limiting Swift's eminent readability in this area of the language. Perhaps this would be an interesting direction for future work.)

I'm misusing OptionSet for a larger bit set with non-default Element and RawValue types. I should probably use SetAlgebra instead.

Off topic
/// A type that presents a mathematical set interface to a 256-bit SIMD vector.
///
/// - Complexity: O(*n*) linear time for initialization from a sequence.
/// - Complexity: O(*n*) linear time for coercion of an array literal.
/// - Complexity: O(1) constant time for coercion of an integer literal.
/// - Complexity: O(1) constant time for other `SetAlgebra` operations.
@frozen
public struct SetOfUInt8: OptionSet, Sendable {

  public typealias Element = UInt8

  public typealias RawValue = SIMD4<UInt64>

  public var rawValue: RawValue

  @inlinable
  public init(rawValue: RawValue) {
    self.rawValue = rawValue
  }
}

I'm not sure if either OptionSet<Element> or OptionSet<RawValue> should be proposed. The protocol seems to exist mostly for the default implementations (and the non-failable initializer).

1 Like

The Core Team took some time to talk today about the application of primary associated types to OptionSet and Strideable. Decisions about when to make associated types primary are not always going to be clear. However, we feel that the guidelines in this proposal should capture the considerations that should go into those decisions, to the point of dictating those decisions whenever possible. If the proposal authors feel that Strideable.Stride should be a primary associated type, that's fine, but the guidelines need to cover the reasons why that's the right decision; and similarly with OptionSet.Element and so on.

In that spirit, we would like to invite a deeper discussion about the guidelines. These controversial cases seem like excellent opportunities to discuss why the guidelines should or should not indicate that an associated type should be primary. Community members might also wish to suggest protocols outside the standard library that we can consider how to apply the guidelines to.

If this discussion takes longer than the current review period, that's fine; the Core Team can ensure that non-controversial cases are officially decided so that we're not held back by the 5.7 schedule.

14 Likes

For me, the completely non-controversial primary associated types are captured by a much narrower subset of the first guideline ("Let usage inform your design"), though somewhat wider than just "elements of collections." I know that @lorentey understands the guideline to mean looking at extensions of the protocol and other usages, but the specific usage that I have in mind is this:

Do concrete types that implement the protocol have generic parameters which correspond to this associated type? (For instance, Array<Element>, Set<Element>, etc., are all conforming implementations of Collection<Element>, and SIMD4<Scalar> conforms to SIMD<Scalar>.)

I surmise that this feels natural and uncontroversial (at least, to me) because the syntax used here deliberately parallels that of concrete types, and if FooImpl<T> and BarImpl<T> meet the bar of being clear (guideline 2) and useful (guideline 3), it seems natural that FooAndBarProtocol<T> will as well.

Of the currently proposed protocols to have primary associated types that are non-controversial and don't fall into that narrower sort of "usage," I can see only four: Identifiable, RawRepresentable, Clock, and InstantProtocol.

The latter two were added recently, and the underlying protocols and their implementing types are very new, so I can't really speak to them from experience or intuition, although they too may benefit from reconsideration along these lines if the overarching notion is generally accepted.

The former two are unified by something more than having a natural preposition to describe the relationship between associated type and protocol ("identifiable by" and "representable by"; guideline 2), but rather they additionally convey some notion that the primary associated type is in some way a stand-in ("identifiable," "representable"). This distinguishes them from the Strideable case where the Stride is not a stand-in for the type that it's associated with.

To put it another way, perhaps it is important that the relationship—association, one might say—between a primary associated type and the protocol has to not just be clear [edit: and not merely empirically the most often constrained type per @lorentey’s interpretation of the first guideline] but, well, meet some bar of semantic primacy.

9 Likes

The Core Team talked today about what to do with this proposal. Reviewing the feedback, the application of primary associated types to most protocols in the standard library seems to be largely uncontroversial, but there's significant discussion about what the guidelines ought to be in general and (in particular) how they ought to apply to Strideable and OptionSet. The Core Team has decided to accept those uncontroversial portions of SE-0358. The remainder of the proposal remains in review, and the review has been extended for three weeks, until June 20th, 2022, to discuss the general guidelines and figure out how they ought to apply to those two protocols. I see no reason not to continue the review in this thread.

16 Likes

Let usage inform your design.

I can't find any such OptionSet usage in the standard library. Are there useful algorithms, without knowing the custom static var properties that give meaning to each option set?

I can imagine a useful generic AnyOptions<Element> or AnyOptions<RawValue> structure, where both associated types are the same fixed-width integer. It would be intended for options imported from #define macros, when SetAlgebra methods are preferred over bitwise operators.

For example:
#define SQLITE_OPEN_READONLY  0x00000001
#define SQLITE_OPEN_READWRITE 0x00000002
#define SQLITE_OPEN_CREATE    0x00000004
public var SQLITE_OPEN_READONLY:  CInt { get }
public var SQLITE_OPEN_READWRITE: CInt { get }
public var SQLITE_OPEN_CREATE:    CInt { get }
public final class Database {

  public init(options: AnyOptions<CInt> = []) throws {
    var options = options
    // If necessary, add the default "mode=rwc" flags.
    if options.isDisjoint(with: [SQLITE_OPEN_READONLY, SQLITE_OPEN_READWRITE]) {
      options.formUnion([SQLITE_OPEN_READWRITE, SQLITE_OPEN_CREATE])
    }
    /*...*/
  }
}

(Highlights by me)

This is a valid analogue, but I think it's too narrowly applicable to be generally helpful. As I see it, there are two problems with it:

  1. First, the role/meaning of T in FooImpl<T> isn't at all clear to me -- this type name alone includes no indication whether T stands for something like the Element type of an Array, the Base type of a Slice, the Root of a PartialKeyPath, or something completely different. Nor is this something that can be reasonably clarified through API design, as Swift provides no tools for API designers to clarify the role of such type arguments at the point of use. (Unlike with function invocations, where we can use mandatory argument labels to help clarify their meaning.)

    So in my view, FooImpl<T> and BarImpl<T> do not meet any reasonable level of clarity at the point of use, and so therefore the premise of the implication quoted above is false -- the statement does not tell me anything useful about the clarity of FooAndBarProtocol<T>.

  2. Second, this analogue does not seem to be applicable outside the Collection protocol hierarchy and its immediate neighborhood (such as AsyncSequence). General-purpose Collection types are indeed usually generic over their Element (or their base collection type, in the case of generic collection transformations, such as those conforming to LazyCollectionProtocol). However, this isn't true for specialized collections (such as String or Bitset), and it seems not to be true in general for protocols that aren't modeling containers/streams.

    Identifiable, RawRepresentable, Clock, InstantProtocol do not seem rare exceptions to me -- rather, these feel like rather typical protocols outside the collection hierarchy. In my view, Strideable and OptionSet are firmly in this category as well.

    (Of course, we could choose to limit the use of this new language feature to Collection-like protocols. However, that seems overly restrictive to me.)

In the specific case of Strideable, I don't see any issues about clarity at point of use whatsoever. On a superficial level, it would not be entirely unreasonable to say that Strideable.Stride feels too similar to Numeric.Magnitude for comfort. However, to me there is a very clear difference between these two protocols: the Strideable protocol consists entirely of operations taking or returning a Stride value, while Magnitude plays a far less crucial role in Numeric. Indeed, Strideable is every way as much a single-purpose protocol as Identifiable or RawRepresentable is -- all three of these protocols are built around their sole associated type, and even share the root of their name with it.

Identifiable types aren't usually generic over their ID; RawRepresentable types aren't typically generic over their RawValue, and Strideable types are rarely if ever generic over their Stride. I don't see how this indicates anything about whether or not we should allow using the lightweight constraint syntax with these protocols.

I believe I have already addressed the usefulness of constraining Stride.

In general, as long as there is a clear choice for a primary associated type, I think it's preferable to err on the side of defining one, rather than omitting it, even if the use case seems marginal. (Which I really don't think it is in Strideable's case.)

As we've seen with Clock, just because I can't think of an obvious use case for enabling the use of the lightweight syntax, it doesn't follow that nobody can. To me, the case for some Clock<Swift.Duration> only seemed obvious in hindsight. (Thanks @stephencellis for pointing it out!)

For what it's worth, the Standard Library does contain a number of useful algorithms on OptionSet that use a where RawValue: FixedWidthInteger clause; they supply default implementations to some core SetAlgebra requirements.

I don't see a reason to actively prevent this code from using the OptionSet<some FixedWidthInteger> syntax (whenever it becomes available for use in extensions).

As for usages outside of the stdlib, I can certainly imagine someone defining new algorithms, such as a method for getting the complement of an option set:

extension OptionSet<Int> {
  func complemented() -> Self {
    Self(rawValue: -1 ^ self.rawValue)
  }
}

(This may or may not be a wise thing to use, but I believe it does work.)

Or I can imagine that someone working on a new serialization facility might want to define generic methods that can work with any option set of a certain raw value:

extension MySerializer {
  func write(_ value: Int)
  func write(_ value: some OptionSet<Int>)
}

Granted, these examples have a distinct smell of desperation about them -- OptionSet is far more of a utility protocol than most of the other standard protocols.

For what it's worth, a quick code search on GitHub does readily uncover such RawValue constraints being used in actual real-life code.

Do we have a good reason to actively prevent people from using the new syntax for these cases? E.g., do you expect to repeatedly misunderstand OptionSet<Int> to mean a constraint on the Element type?


Hm; that is a remarkably high expectation for any set of API guidelines. Removing all subjective decisions seems like an unrealistic expectation, but perhaps we can tie things down a little more.

The "usefulness" guideline appears to be the weakest in this regard:

  1. Not every protocol needs primary associated types. Don't feel obligated to add a primary associated type just because it is possible to do so. If you don't expect people will want to put same-type constraints on a type, there is little reason to mark it as a primary. Similarly, if there are multiple possible choices that seem equally useful, it might be best not to select one. (See point 2 above.) For example, ExpressibleByIntegerLiteral is not expected to be mentioned in generic function declarations, so there is no reason to mark its sole associated type ( IntegerLiteral ) as the primary.

I intended this merely as a way to carve out exceptions for cases like KeyedEncodingContainerProtocol, ExpressibleByIntegerLiteral, where there would be a clear, unambiguous choice for a primary associated type, but it seems pointless to define one, as these protocols are highly unlikely to be ever used in generic context.

Rather, this guideline has triggered arguments on whether it it'd be useful enough to allow lightweight constraint syntax on protocols where there are clearly some use cases for constraining the candidate primary type.

What if we reworded this requirement to something that provides less room for subjectivity?

  1. Err on the side of defining a primary associated type. As long as there is a clear, unambiguously right choice for a primary associated type, and there is even a marginal use case for defining one, it's better to enable the lightweight constraint syntax rather than arbitrarily prohibit its use.

    Conversely, do not feel obligated to select a primary if there are multiple possible choices that seem equally useful, or if the choice would lead to persistent confusion, or if the protocol isn't designed to be ever used in generic context. For example, ExpressibleByIntegerLiteral is not expected to be mentioned in generic function declarations, so there is no reason to mark its sole associated type (IntegerLiteral) as the primary, no matter how obvious a choice it would be.

That said, I do not wish to cut off discussions about requiring a higher level of usefulness, if fellow members of these fora feel it'd be worth exploring this a little more.

@xwu, @Karl, @benrimmington: You have recently raised objections of this nature -- do you feel it would be actively harmful to allow the lightweight syntax on unambiguous-but-perhaps-marginal cases? (Why?)

In other words, would you be willing to accept a certain number of infrequently used declarations in the interest of making the guidelines less subjective? If not, do you have a suggestion for a guideline replacing point 3 above that sets a higher bar for usefulness, without overly increasing subjectivity?

It would be extremely helpful if readers of this forum would try applying the proposed guidelines (with or without the clarification above) to protocols defined in their own code. Can you find examples where the guidelines do not apply well or where they lead to undesirable results?

6 Likes

I agree that it’s not realistic to fully achieve that. But yeah, it does feel like the current guidelines don’t capture all of the kinds of reasoning going into the protocols in the standard library.

1 Like

Bringing this review back to the top now that WWDC is over.

2 Likes

I tried to browse the search results. The vast majority were copies (not forks) of the standard library. Several others were for iterating an option set, all based on the same answer from Stack Overflow. I didn't find any interesting code, but that isn't an argument for or against OptionSet<RawValue>.

Codable.swift has a default implementation for option sets and enums. Your serialization example could use either some RawRepresentable<Int>; or a protocol composition of some OptionSet & RawRepresentable<Int> is also supported by SE-0346.


Option sets consume/produce either Element or Self. They're expressible by an array literal of Elements. Their RawValue is usually an implementation detail.

I can't think of a suitable preposition (from the second guideline) to describe OptionSet<RawValue>.


Have the guidelines been tested against the SDK? The following are defined in Foundation, but I'm unfamiliar with their usage.

Protocol Associated Types
SortComparator Compared
ReferenceConvertible ReferenceType
DecodableWithConfiguration DecodingConfiguration
DecodingConfigurationProviding DecodingConfiguration
EncodableWithConfiguration EncodingConfiguration
EncodingConfigurationProviding EncodingConfiguration
AttributeScope DecodingConfiguration, EncodingConfiguration
FormatStyle FormatInput, FormatOutput
ParseableFormatStyle FormatInput, FormatOutput, Strategy
ParseStrategy ParseInput, ParseOutput
DataProtocol Regions, Element == UInt8, Index, Iterator, SubSequence, Indices
MutableDataProtocol Regions, Element == UInt8, Index, Iterator, SubSequence, Indices

To clarify, FooImpl<T> is a stand-in here for those uses of generics which are clear (that is, Array<Element>, etc.)—to reject the premise would be to reject that there can be any uses of generic parameters which are clear at the point of use, which I don't think is what you mean. I think Array<Element> is plenty clear, for example.

Hence, why I proposed a formulation of a rule that extends beyond Collection-like protocols, although it seems your point (2) is that it doesn't extend too far. That would be a fair critique.

One point of concern here is that Strideable types sometimes but not always are their own Stride, and having worked with (on, rather) this protocol quite extensively I can say that keeping the distinction straight is so important for the correctness of generic code.

I really, really want to emphasize again how much I disagree with this proposed metric of usefulness. I really think it is a serious mistake to refer to the number of extensions in the defining library as the metric for providing some feature for end users.

One of the key reasons we add new APIs to the standard library (recalling the core team's guidance on when something meets the bar for inclusion) is that something commonly used is not trivially composed from existing APIs but rather difficult to implement correctly (lots of corner cases, etc.). As the author of a chunk of those extensions you list that constrain Strideable, I can say that they are safely in the "Don't try this at home, kids!" category of tricky, and some of the constraints I wrote (extension Strideable where Stride: FloatingPoint and extension Strideable where Self: FloatingPoint, Self == Stride—refer to my point above about distinguishing the striding and strided types) are directly because of the trickiness of implementation.

Referring to the number of times we actively choose to put work into the standard library dealing with corner cases so that users don't as a reason to provide users with an easier way to do the same thing is not the correct metric: I feel very strongly about this.

Because the syntax can be added in a future version in an ABI-compatible way but not removed if it's added now, and because I do have concerns about active harm (see above—namely, encouraging folks to parameterize protocols to write overly generic algorithms that are tricky to get right due to underspecified semantics, on the basis of where we are using constraints in the standard library for implementation detail purposes precisely to steer users away from that), my humble opinion is that we ought to be erring strongly on the other side and omitting adoption until we have a better sense of usefulness.

8 Likes