[Returned for revision] SE-0376 (second review): Function back deployment

Hi all,

The second review of SE-0376: Function back deployment ran from November 28 through December 12 and is now concluded. The language workgroup has decided to return the proposal for revision.

The core behavior of the proposal was not really disputed in this review, with the discussion mainly centered on two different aspects: the name of the attribute (along with its argument label) and the requirement that @backDeployed also requires an explicit @available annotation.

Attribute name

During the review, members of the community pushed back against the proposed renaming of the argument label from before: in the first revision of the proposal to upTo: in the second revision, citing that its not-totally-consistent existing use in the standard library doesn't justify the more awkward phrasing it implies.

Different reviewers indicated that both before: and upTo: was the construction that really helped them understand what the feature was doing, and that the alternative phrasing was confusing.

The language workgroup was not convinced that the proposal had settled on the best name for this feature given the objections raised, and was unable to come to a consensus about whether before:, upTo:, or something else entirely would properly communicate what this feature did.

The workgroup was intrigued by the idea that some of the understanding difficulty here may stem from the 'polarity' with which the name @backDeployed describes the feature, i.e., as a backward-looking feature describing what happens on platform versions prior to the implementing version, rather than trying to more directly express the forward-looking condition. The workgroup does not necessarily endorse the @abi spelling suggested in the linked post, but would like to see further discussion on this front to explore whether flipping the 'polarity' might make this feature more understandable.

Thus, the language work group requests that the proposal reconsider the naming for both the argument label and the base attribute in light of the points raised during review and revise the proposal with additional justification as to why the ultimately chosen name was picked over the other alternatives.

Requiring @available

Discussion on this point was relatively brief. It was pointed out that it is perfectly reasonable to back-deploy a function indefinitely, so the requirement that all functions marked with @backDeployed also be marked with @available explicitly is not strictly necessary.

The language workgroup feels that while it may be a noble goal to try to solve the problem of authors of ABI-stable libraries forgetting to add @available annotations, this proposal is not the right vehicle to solve this problem. Relatedly, the workgroup does not feel that the introduction of the @backDeployed attribute would exacerbate any of the existing difficulties in this area.

Therefore, on this front, the language workgroup requests the proposal be revised to drop the requirement functions marked with @backDeployed also be marked with @available.


Once any necessary further discussion concludes and the proposal has been revised, the language workgroup will run another review.

Thank you for participating in Swift evolution.

Freddy Kellison-Linn
Review Manager

10 Likes

Related to a naming with "reversed polarity", this spelling crossed my mind:

@available(macOS 11, *)
@builtin(macOS 13)
func doSomething() {...}

Here "built in" is just another word to say the function is baked into the ABI. Perhaps it's less precise than @abi, but it might be a more approachable word.

I generally agree with Ben's reasoning, so it seems like it should be spelled @backDeploy(before:). Given it's a command for the compiler to do something (fill in a back deployed implementation), the imperative verb form seems best (backDeploy), with before the best fit as the modifier, since we're filling in the first version which doesn't need the deployment.

1 Like

To me, both the @abi and @builtin spellings suffer from the same problem which is that those names do not convey the purpose of applying the attribute. Most functions are automatically "ABI"/"builtin" in the release they are introduced, so this spelling would beg the question of why this attribute is not present on nearly every API. To understand why the attribute has been applied, you have to reason about the implication the attribute makes in combination with the @available attribute (which may itself be implicit since we're removing the requirement that they be specified together); "since this function is ABI starting in iOS 13 but available in iOS 12, I guess that must mean that something special happens between 12 and 13." It seems hard to reason about what an attribute with this spelling accomplishes without going to look up more detailed documentation.

I think this is my criticism of the idea of reversing the polarity in general; it emphasizes an aspect of the function that is already assumed for most APIs and does not help the library author or reader understand that the function will be emitted into the client when certain conditions are met. Perhaps there's a word out there that reverses the polarity but still captures the essence, though.

4 Likes

I'm going to push back against this very lightly by observing that as a user you never need to care about why this attribute is present (and most of the time you don't need to care that it is present). As a library maintainer, you have to care, but from a library maintainer's perspective "abi" or something that implies it is exactly the why.

One of the arguments against bringing the attribute through evolution was that only SDK vendors need this attribute, but the counter argument was that as a developer consuming an API you may need to reason about how this attribute affects the behavior of your program on different operating system versions. I do agree that it's going to be rare that library client will really need to grok the attribute, though.

Even from the library author's perspective, though, I think the arguments I'm making against @abi/ @builitin still apply. The spellings don't help you understand what the attribute is useful for. I'm thinking about this in terms of a user story: "As a library author, I want to back deploy this API. Therefore I should use the ___ attribute."

I'd like to explore the alternative of using until: as the argument label a bit more (thanks @benrimmington for the suggestion). I think that @backDeployed(until: iOS 13) reads well at a glance and it works reasonably well when expanding the attribute into a sentence:

The function is back deployed until (the minimum deployment target is at least) iOS 13

IMO the range exclusivity of until: is clear and I also think it actually reads a bit more naturally than before: as an argument label. Consider this expansion into a sentence with before: as the label:

The function is back deployed (for all minimum deployment targets) before iOS 13

The sentence is understandable and I think grammatically correct, but to me it's a bit more awkward than the previous sentence using until:. There's something more concise about the way that "until" represents that all earlier deployment targets that have the behavior.

Precedent-wise, I could not find examples of until: used as an argument label in the standard library. It feels somewhat similar to while:, though, which is common as an argument label for methods that take predicate closures and return sequences or accumulations. The only precedent for before: as a label is index(before:) which returns the specific index that comes before another index in a BidirectionalCollection, which doesn't feel structurally analogous to what we're representing with the @backDeployed attribute.

5 Likes

One thing I like about until: is that its directionality is toward the endpoint it labels rather than away from it. This makes it (I think) more clear about the information that it leaves out, and that must be gleaned from the availability. For example, I think it's a somewhat plausible reading of @backDeployed(before: iOS 13.0) that the function will be back deployed to any iOS version before 13, because it expresses a relationship that looks like this:

<--------|
       iOS 13

where the endpoint of head of the arrow is unspecified. OTOH, @backDeployed(until: iOS 13) expresses a relationship that looks like this:

...-------->|
          iOS 13

which IMO makes it more difficult to read as "back deployed on every iOS version until 13," because the relationship it describes is more explicitly specifying a single endpoint, with the opposite end specified by @available.

6 Likes

Is until meaningfully different from upTo in this regard?

1 Like

No, this is also something I like about upTo:, mainly just meant to contrast with before:—and until: accomplishes it with one word!

My reading of backdeploy(ed) in my mind implies a visualization of traveling into the past, not traveling into the future up to a specific end point. Using “until” here implies to me that this is the earliest version I back deploy at, which could be a bit confusing especially when paired with functions that use @available.

I think that “backDeployed(before:)” communicates the intent clearly, but an alternative label name that I think could also work is “priorTo:”. “upTo” is more clear to me as well compared with “until” but flips the directionality of the intent of the attribute (as I’m reading it) which makes me take a bit more effort to understand what it’s saying. Any label name that could be a synonym for “before” or “priorTo” would make the most sense to me, as long as it implies that directionality of applying to versions in the past compared with the specified version.

2 Likes

There hasn't been any further discussion of naming, and for each of the options proposed at least one person has said they find some aspect of it unclear. My inclination is to stick with the original suggestion of the language workgroup: @backDeployed(upTo:). I'll add some discussion to Alternatives Considered about the various options discussed.

A bunch of Language Workgroup folks had some (individual) opinions about this spelling, so I've asked them to jump in and join the discussion in this thread. (I was going to do so myself, and then it immediately slipped my mind until Holly reminded me just now.)

2 Likes

I'll repost my views from the previous review, which haven't changed:

I happened to be working with this attribute today [when I posted this last year :] and wanted to register the feedback that only really occurred to me whilst I was doing so, which is that I think the original choice of @backdeploy(before:) was pretty clearly better than the newly proposed upTo:.

I understand the analogy with PartialRangeUpTo, but it seems irrelevant to the naming of this property. PartialRangeUpTo never appears to users and is really an implementation detail of the range expression infrastructure – a mechanic of how we make the ..< prefix operator work. It is not a type to name other more visible parts of the language after.

The decision should instead be made based on what will be most obvious to readers wanting to understand the availability of a function, and in this case before: seems the better choice as it represents more natural English language usage for when the back-deployment functionality applies.

This helped solidify my feeling that upTo: is a poor choice.

  • upTo: is not a single consistent term used throughout the language, as demonstrated by stride
  • it’s striking how much uglier the compound camel-cased word is when comparing it to stride’s to:
  • there is a clear driver here that matters specifically for these two examples: the need to distinguish between “to” and “through”, that requires a matching pair of argument labels That does not apply to the availability usage.

But more important than all of these is that “the function is back deployed up to iOS 13” is an unnatural phrasing. You just wouldn’t say it in normal use. Whereas “the function is back deployed before iOS 13” is entirely natural. This isn’t just hypothetical – someone I spoke to, looking at this for the first time yesterday, was confused by what it mean until I explained it. I believe this wouldn’t have been as likely with before:.

So it seems that we are sacrificing readability for consistency (and, given the comparison with stride, mixed consistency at best), which is the wrong way around.

9 Likes

I've been thinking for a while that perhaps the problem is that we are trying to label the version with a direction to imply what its role is, when we should really just be explicit and label it with its role. After all, @available doesn't look like this:

@available(macOS, from: 10.9, to: 12.0, with: "not recommended on Apple Silicon")

It looks like this, and it's much clearer for it:

@available(macOS, introduced: 10.9, deprecated: 12.0, message: "not recommended on Apple Silicon")

So maybe we should similarly label the @backDeployed argument more explicitly, like this:

@backDeployed(implemented: macOS 12.0)

And leave it to documentation to explain that @backDeployed only does anything special when you're running on an OS older than the implemented: one.

We've gone back and forth on the best way to be cute with this label, but nothing seems to be working out. Let's give that up as a lost cause and be explicit instead.

16 Likes

Interesting idea, I like the thought process. The implemented: label doesn't read quite right to me but I wouldn't mind it if it makes sense to others. Since we've explored using the term "ABI" previously in the naming for this attribute, maybe it would work in this construction? For example:

@backDeployed(abi: macOS 12)
@backDeployed(abiIntroduced: macOS 12)
@backDeployed(introducedAsABI: macOS 12)
1 Like

Having read through the rounds of feedbacks, it feels like the spelling provided in "alternative considered":

extension Toaster {
  @available(toasterOS, backDeployTo: 1.0, introduced: 2.0)
  public func makeBatchOfToast(_ breadSlices: [BreadSlice]) -> [Toast]
}

… deserves another look.

I agree with some of the drawbacks as stated in the proposal¹. But this, to me, has the strong virtue of being unambiguous².

It has an added benefit of preserving the literal meaning "introduced".


¹ I think having to use long-form availability is no worse than having to use a separate attribute. The added verbosity does not harm readers of the code as much.

² … to fresh eyes, anyways. Experienced developers may have to get used to reading beyond "introduced" to find the earliest OS.

2 Likes

Since I like @beccadax's suggestion and there seems to be wide support for it, I've opened a pull request to update the proposal with the following changes:

  • Remove the requirement that @available be present simultaneously with @backDeployed.
  • Rename the before: label to implemented: and address the spellings we've covered in Alternatives Considered.
  • Make minor clarity improvements to the text of the proposal.
2 Likes

Huh? There's been three posts since that idea was floated and two of them are yours, so how is there wide support for it?

IMO, @backDeployed(implemented:) isn't great either. I agree that requiring @available seems redundant, since @backDeployed would set the real availability anyway. I'm not yet sure if there's any reason we need it. I do worry that @backDeployed relies too much on user's inference about the behavior to understand what's happening. Perhaps we can iterate on @duan's suggestion of integrating with @available. @available(iOS 13, backDeployedStartingIn: iOS 15) seems to include the relevant information we're trying to surface (actual availability as well as the version where the back deployment started).

1 Like

I think the 15 likes (so far) that @beccadax's post got is showing the support (and just a single post with a different tack) - at least I often will simply heart a suggestion that I agree with rather than simply posting a +1 - at least I view each like of a post as a +1 (and thought others did too?).

4 Likes