Archive for June, 2016

Convergent Evolution: Rejections and Acceptances

Accepted

SE-0096: “Converting dynamicType from a property to an operator” is accepted with revision. “This proposal establishes dynamicType as a named operator rather than a property.” Team feedback:

The feedback on the community on the proposal was very light, but there were significant concerns about its name. After discussion, the core team agrees that x.dynamicType should use the syntax of a global function as stated by the proposal, but would prefer for that function to be spelled type(of: x) to follow the naming guidelines. The rationale is:

  • The function is side-effect free, so it should have a noun that describes what is returned.
  • “type” is ambiguously a noun, verb, and perhaps other things, so the “of” preposition is necessary for clarity.

The core team recognizes that this means that we should probably resyntax the existing sizeof/strideof functions, but that should be a follow-on discussion.

Returned for Revision

SE-0050 “Decoupling Floating Point Strides from Generic Implementations” returned for revision. “Swift strides create progressions along “notionally continuous one-dimensional values” using a series of offset values. This proposal supplements Swift’s generic stride implementation with separate algorithms for floating point strides that avoid error accumulation.” Team feedback:

There was very little feedback from the community – the feedback was positive about solving the floating point error accumulation problem indicated by the proposal, but wasn’t strongly positive about the solution.  The core team also agrees that the problem needs to be solved, however they don’t think this API change is the right approach.

The core team believes that the existing strideable API cannot efficiently and correctly handle all the real-world use cases one would want.  However, a multiplication-based implementation similar to the one proposed in SE-0050 (but potentially extended) seems sufficiently general to solve the existing use cases as well as solve the floating point error accumulation issue.  Once the design of this iterates on swift-evolution, it would be great to see a revised version of this proposal.

SE-0095: Replace protocol<P1, P2> syntax with Any<P1, P2> is returned for revision. “The current protocol<> construct, which defines an existential type consisting of zero or more protocols, should be renamed Any<>.” Team feedback:

There was an incredible amount of feedback from the community, continuing even today.  The principle problem identified by community about the proposal is that Any<T1, T2> implies very strongly an “any of T1 OR T2” relationship (aka disjunction) and not “any type conforming to T1 AND T2” relationship (aka conjunction).  There was also some related discussion as to how the Any<> syntax aligns with future generalized existential syntax, much discussion as to whether it should be spelled Any<> or any<>, and some discussion about “angle bracket blindness”.

The core team extensively discussed the feedback and considered many different possible paths forward.  The conclusion of this is that it recommends that SE-0095 be revised into a fairly different proposal, one that introduces a new infix “&” type operator for representing protocol and other type compositions.  Instead of:

func f(a : protocol<A, B>) {}
func g<T : A>(x : T) where T : B {}    or   func g<T : protocol<A, B>>(x : T) {}

You would instead write:

func f(a : A & B) {}
func g<T : A & B>(x : T) {}

The degenerate case of protocol<> needs an answer, but only for the definition of the Any typealias in the standard library, so it can be left out of the proposal.

The core team feels that this is an elegant solution for Swift 3 that conveys exactly the right intent.  When generalized existentials are introduced, an elaborated syntax can be introduced to generalize this, e.g. Any<A & B where … > or (T : A & B where …) or whatever else makes sense in context of its design.

The principle concern with this is that having an “&” operator for generic constraints leads the question of whether the language should introduce an “|” operator to represent disjunctions in type constraints (something that the type system cannot and should not support).  This is a topic that the C++ committee grappled with in its discussions of C++ concepts.  That said, the core team feels that “&” directly expresses the relationship that we want, that “|” can be addressed in the “commonly rejected proposals” list, and that other proposals for an infix operator (like +) skirt this issue but are strictly worse at communicating intent in code.

Rejected

SE-0098: “Lowercase didSet and willSet for more consistent keyword casing” is rejected. “This proposal adopts consistent conjoined keyword lowercasing.” Team feedback:

The feedback on the proposal from both the core team and the community was that these should remain camel cased, particularly given that they may become user-definable aspect names in a future release.

SE-0097: Normalizing naming for “negative” attributes is rejected.

The core team agrees with the principle guiding the proposal (that negative attributes should start with “non” instead of “no”) and highly values standardized naming for attributes.  The community was lukewarm about nonescaping but pretty significantly opposed to nonreturning.

The core team discussed this at length and agreed that the problem identified by the proposal needs to be solved, but prefers to explore directions that would define away these attributes completely:

1) For noreturn, the core team prefers to explore a solution where a function can be declared as returning an non-constructable bottom type (e.g. an enum with zero cases).  This would lead to something like:

func abort() -> NoReturn { … }

This will require some new support in the compiler, but should flow better through the type system than @noreturn in function composition and other applications.  Joe Groff offered to write a proposal for this.

2) For noescape, the core team feels that the right solution is for closure arguments to default to noescape, which means that the attribute we should really need is @escaping.To provide some more details, this approach has the following advantages:

  • Most functional algorithms written in pure Swift will benefit because they are naturally noescape. The core team feels that this will reduce the boilerplate involved with writing these algorithms.
  • The compiler has enough logic in it to provide a great QoI experience when a developer doesn’t think about escaping, and tries to escape a closure – it can provide a fixit that suggests adding @escaping.
  • Recent changes (to disallow escaping closures to close over an inout parameter) are pushing the language to prefer noescape closures. noescape closures have also always been the preferred default, since they eliminate a class of retain cycle issues.
  • @autoclosure(escaping)” can be simplified and standardized to “@autoclosure @escaping

The two primary concerns with taking this direction were that it is would adversely impact resilience, and that imported Objective-C APIs would be too annoying to work with, because the compiler would have to be conservative and assume they are escaping:

On resilience, the concern with this approach is that an API may not thinking about whether a closure parameter should be escaping or not, and this behavior makes it possible that someone could write “V1” of an API and not accidentally promise noescape semantics, but then need it in “V2” of the same API.

John McCall pointed out that resilience in the type system is different than resilience in practice: An API changing to capture a closure and use it long after it was originally passed is likely to break the clients regardless of whether the type system captures this as an issue.

He argues (and the argument is strong IMO) that it is better for resilient APIs to default to @noescape, since that forces the author of V2 to think about whether they are breaking their clients. If they are doing something that is “logically” noescape in their V2, then they can unsafe bitcast away the escaping aspect of the closure. This is better than changing the client’s view of the API in any case.

On imported Objective-C API, the core team did a quick study of the Cocoa APIs and found that most closure/block parameters are escaping in practice. As such, the core team feels that it isn’t overly burdensome to ask that imported Objective-C APIs annotate their semantically noescape block parameters with the clang __attribute__((noescape)) attribute.

I’m happy to write up this proposal, but won’t have cycles to do so for several weeks. If someone else wants to take it up, that would be great.

Platform Configurations: Now and Future

Screen Shot 2016-06-01 at 11.03.35 AM

Swift enables you to conditionally compile code using build configurations. These #if-delimited tests let you decide whether to include or exclude code from compilation. There are the literals true and false, and you can test for command line flags (-D <#flag#>), but you can also test for current operating system (os), architecture (arch), and swift language release (swift, added in Swift 2.2).

In the latest source, Swift offers the following conditional tests for operating system:  “OSX“, “tvOS“, “watchOS“, “iOS“, “Linux“, “FreeBSD“, “Windows“, and “Android“, which is quite a lot more options than the language started with.

#if os (OSX)
    // use Cocoa stuff
#endif

Valid architectures include:  “arm“, “arm64“, “i386“, “x86_64“, “powerpc64“, “powerpc64le“, and “s390x“.

#if (arch(i386) || arch(x86_64)) && os(iOS) 
    print("Probably simulator")
#endif

Swift version tests are always >=, and you supply a version number, e.g. #if swift(>=3.0). Use negative tests (the exclamation point) or else clauses to test for “earlier than” conditions.

In Swift 3.0, expect to see a new canImport configuration test, based on the recently accepted SE-0075 proposal. You’ll be able to test whether a module is available for import before calling the import statement or using code that’s tied to that module.

Topics brought up on the Swift evolution list for additional configuration tests include testing for compilation endianness, Objective-C interoperation, OS versions, vendor, simulator/emulator vs hardware destinations, and whether debug assertions can fire.

Endianness

The pieces are already in place to test for endianness, even though there is no build configuration test available at this time. The following code is ready in lib/Basic/LangOptions.cpp:

static const StringRef SupportedConditionalCompilationEndianness[] = {
    "little",
    "big"
};

ObjC Interop

The same can be said for interop availability:

if (EnableObjCInterop)
    addPlatformConditionValue("_runtime", "_ObjC");
else
    addPlatformConditionValue("_runtime", "_Native");

OS Versions

Both the demand for this configuration and the code are a bit lighter. The OS name and architecture are available but it’s unclear whether  calls like getMacOSXVersion(major, minor, micro) can be provided for universal configuration.

Vendor

There appears to be a getVendor() call defined but no specific values beyond llvm::Triple::Apple are called out in code.

Simulator/Emulator vs Hardware

In Platform.cpp, you’ll find tests for the iOS Simulator, Apple TV Simulator, watch simulator, and:

bool swift::tripleIsAnySimulator(const llvm::Triple &triple) {
    return tripleIsiOSSimulator(triple) ||
    tripleIsWatchSimulator(triple) ||
    tripleIsAppleTVSimulator(triple);
}

Here, platform names are broken down to macosx, iphoneos, iphonesimulator, appletvos, appletvsimulator, watchos, and watchsimulator. Configuration tests at this detail level are not available in Swift today.

Debug Assertions

Testing for debug state is tricky. As previous posts have made clear, there’s not a common consensus on when “debug builds” are taking place:

  • There’s a general consensus that a debug state occurs when assertions can fire and are not disabled by compile-time optimizations.
  • The concept of “debug” is nuanced enough that introducing a single  #if debug build configuration test is insufficient for substantial set of community members who interacted in previous discussions and Swift developers who have sent me feedback outside this list.
  • Conditioning debug on Xcode debug/release schemes is a no-go.
  • Hidden helper functions already exist in Swift.
  • Some members of the core team believe using build configurations is the wrong point to conditionalize code.

Swift offers three assertion configurations _isDebugAssertConfiguration, _isReleaseAssertConfiguration, and _isFastAssertConfiguration, which are guaranteed to be constant-folded away before final code generation.

I’m still not entirely sold on whether there should be a build configuration test, but it’s easy enough to promote at least _isDebugAssertConfiguration (if not all three) to public developer-consumable items and eliminate the need to add custom build flags (such as -D debug) in order to conditionalize expensive tests and verbose logging.

I’m not sure exactly what subset of the already-implemented tests need SE proposals to change the user-facing APIs. I’m curious as to how you’d prioritize configuration tests for those items not yet adopted into the official language. Thanks for your feedback on that.

Update: