Cenozoic Park: Swift Evolution

Accepted

SE-0093: Adding a public base property to slices. “Slice types provided by the standard library should allow public readonly access to their base collections to make efficient implementations of protocol requirements possible in conforming types.” Team writes:

  • There was little feedback from the community other than to get clarification.
  • The core team recognizes the proposal as exposing an important basis operation of slice types.

SE-0094: Add sequence(initial:next:) and sequence(state:next:) to the stdlib is accepted (with a tiny revision). “This proposal introduces sequence(first:next:) and sequence(state:next:), a pair of global functions that return (potentially-infinite) sequences of lazy applications of a closure to an initial value or a mutable state.” Team feedback:

  • Feedback from the community & core team is positive.
  • Core team discussed whether it made sense to add just the first form, or whether it made sense to add both. They agree that although the form using an explicit state is much more infrequently used, when it is necessary, it is extremely helpful to have. It is also useful to consider both as a pair.
  • On naming, the core team agrees with the community that “sequence(first:next:)” is a better name than “sequence(initial:next:)”. “sequence(state:next:)” is approved as-is.

Deferred / Returned for Revisions

SE-0083: Remove bridging conversion behavior from dynamic casts is deferred for re-evaluation later in Swift 3. “Dynamic casts using as?as!, and is are currently able to dynamically perform Cocoa bridging conversions, such as from String to NSString or from an ErrorProtocol-conforming type to NSError. This functionality should be removed to make dynamic cast behavior simpler, more efficient, and easier to understand. To replace this functionality, initializers should be added to bridged types, providing an interface for these conversions that’s more consistent with the conventions of the standard library.” Team writes:

The core team and much of the community would like to get the predictability wins of this proposal. However, recent experience with the fallout of “SE-0072: Fully eliminate implicit bridging conversions from Swift” has raised some concerns that it may have overly harmed Objective-C APIs designed around the “id” type. Joe Groff will be investigating a few ideas that may help make SE-0072 work better in practice, and until they are explored and understood, the core team prefers to defer discussion on SE-0083.

Once we have clarity on the fate of SE-0072 (in the next 3-6 weeks), we can discuss SE-0083 again. Thank you to Joe Groff for this proposal, and also for helping to sort out the issues related to SE-0072.

SE-0089: Renaming String.init<T>(_: T) is returned for revisions. “Swift’s String type ships with a large number of initializers that take one unlabeled argument. One of these initializers, defined as init<T>(_: T), is used to create a string containing the textual representation of an object. It is very easy to write code which accidentally invokes this initializer by accident, when one of the other synonymous initializers was desired. Such code will compile without warnings and can be very difficult to detect.” The core team would love to see the revised proposal make it into Swift 3. The team writes:

The community and core team both want to remove this “footgun” from the standard library, where someone could write “String(x)” with the intention of getting a value-preserving conversion to String, but may instead get a potentially lossy and potentially expensive reflection-based conversion to a String.  After extensive discussion, the core team recommends that the community consider a somewhat more elaborate design:

  • Rename the existing reflection-based “String.init<T>(_: T)” initializer to “String.init<T>(describing: T)” as recommend by the community.  This initializer would rarely be invoked in user code directly.
  • Introduce a new protocol (for sake of discussion, call it “ValuePreservingStringConvertible“) that refines CustomStringConvertible but that adds no new requirements.  Conformance to this protocol indicates that the “description” requirement produces a value-preserving representation in String form.
  • Introduce a new unlabeled initializer on String: “init<T: ValuePreservingStringConvertible>(_ v: T) { return v.description }“.  This permits the “String(x)” syntax to be used on all values of types that can be converted to string in a value-preserving way.
  • Audit important standard library types (e.g. the integer and floating point types), and make them explicitly conform to ValuePreservingStringConvertible with an explicitly implemented “description” property.
  • As a performance optimization, change the implementation of the string literal interpolation syntax to prefer the unlabeled initializer when interpolating a type that is ValuePreservingStringConvertible or that has otherwise has an unlabeled String initializer, but use the “String.init<T>(describing: T)” initializer if not.

The expected advantages of this design are:

  • Swift encourages the T(x) syntax for value preserving conversions, and this design ensures that String(x) continues to work for the value preserving cases.
  • This ensures that the String(x) syntax does not accidentally fall off a performance cliff by using the extremely-dynamic reflection mechanism unintentionally.
  • The preferred “I don’t care how you do it, just convert this value to a string somehow” syntax remains string interpolation syntax.  This syntax is efficient in the cases where the String(x) syntax is allowed, but fully general to the other cases where custom convert-to-string code has not been provided.

Some remaining open questions:

  • Exactly what types should conform to ValuePreservingStringConvertible.  It seems clear that integer, floating point types, and Character can and should conform.  What other types should?
  • Do we need the ValuePreservingStringConvertible at all, or is the existing CustomStringConvertible enough?  We already have a few protocols for handling string convertibility, it would be great to avoid adding another one.

SE-0090: Remove .self and freely allow type references in expressions has been deferred from Swift 3. “Swift’s grammar currently requires that type references only appear as part of a constructor call T(x) or member access T.x. To get the metatype object for T, one must refer to the special member T.self. I propose allowing type references to appear freely in expressions and removing the .self member from the language.” Team feedback:

The community and core team all want this proposal (or something like it) to succeed, but the core team identified several serious implementation concerns with the proposal:

  • Disambiguating type vs expression cannot be done in cases where there is no contextual type available or when that type is “Any”.  For example, in the case of “let x = [Int]” it isn’t clear whether this is an array value that contains the metatype for Int, or whether it is a metatype value for the type “[Int]”.  Similar problems exist with tuple literals, including the degenerate case of “let x = ()” which can either be the type of the empty tuple type or an empty tuple value.
  • As written, the proposal has a defaulting rule that fall back to the container literal when a type literal cannot be formed.  The core team prefers that the compiler treat truly ambiguous cases (where a subexpression could be considered to be either a type or a value) to be ambiguous.
  • Resolving ambiguous cases requires some syntax to disambiguate between the cases, which we don’t have.  This syntax should be part of the proposal.
  • Having the constraint solver determine whether a subexpression is in a type or expression context is conceptually beautiful, but it introduces significant complexity into the type checker and puts more pressure onto the constraint solver.  The core team would prefer to see the already planned optimizations and simplifications go into the constraint solver before this happens.  This would allow us to more accurately gauge the cost of this design in practice.
  • The goal of removing the “Int.self” syntax is a great one, but can be done at any point (beyond Swift 3) at little cost: the goal is to obsolete the T.self syntax, not to repurpose it to mean something else.  This means that we can continue to accept it as deprecated syntax for a very long time with little cost to the community.

The core team would definitely like to circle back to this proposal after Swift 3 is out the door, but would recommend that such a proposal be accompanied with a prototype implementation, to validation that the chosen approach can work in practice.

Joe Groff has posted an updated version incorporating feedback.

Rejected

SE-0087: Rename lazy to @lazy is rejected. “Make lazy declaration modifier an attribute by renaming it to @lazy" Team writes:

The community feedback on this proposal was that the most important thing was for the syntax to align with that of the forthcoming property behavior syntax. The core team does not know what that syntax will be, so it prefers to reject this proposal. While it would be great to align “lazy” with behavior syntax, but the cost of it diverging is very low (it is easy and not harmful to accept this as a legacy syntax for a long time if the preferred syntax changes in the future), and it would be worse to switch “lazy” to “@lazy” in Swift 3, only to switch it to something else in a subsequent release.

SE-0084: Allow trailing commas in parameter lists and tuples is rejected. “Swift permits trailing commas after the last element in array or dictionary literal. This proposal extends that to parameters and tuples.” Team writes:

The feedback from the community was quite divided on this topic: many people contributed to the discussion thread with some people agreeing and some disagreeing.

Swift currently accepts a trailing comma in array and dictionary collection literals, for three reasons: evolution of a project often adds and removes elements to the collection over time, these changes do not alter the type of the collection (so those changes are typically spot changes), and the closing sigil of the collection (a right square bracket) is often placed on the line *following* the elements of the collection. Because of these properties, accepting a trailing comma in a collection literal can help reduce spurious diffs when elements are added or removed.

That said, these properties do not translate to other comma separated lists in Swift, such as variable bindings in a var/let declaration, parameter lists or tuples. For parameter lists and tuples (the specific topic of the proposal), the trailing terminator of the list is typically placed on the same line as the other elements. Further, changes to add or remove an element to a parameter list or tuple element list change the type of the tuple or the signature of the call, meaning that there is almost always changes in other parts of the code to allow the change to build. Finally, the core team does not want to encourage or endorse a coding style that puts the terminating right parenthesis on a line following the arguments to that call.

I was rather hoping that the core team might agree that they be allowed just at call-sites, where changes would not propagate to other parts of the code but allow the easy inclusion and exclusion of defaulted arguments.  It wasn’t to be.

Active Reviews

Awaiting Scheduling

Comments are closed.