[swift-evolution] [Review] SE-0023 API Design Guidelines

David Owens II david at owensd.io
Sat Jan 23 23:19:29 CST 2016


I think Joe's reply is a pretty good summary.

At a high-level, I really get no sense on how APIs are really supposed to be developed in Swift. Joe talks about second arguments generally becoming prepositional phrases, but is that the true intent? Because that's not what the guideline says, nor what the language semantics really promote or suggest to do.

For example, the guidelines say to do one thing but the example does something different:

> Compensate For Weak Type Information as needed to clarify a parameter’s role.
> 
> Especially when a parameter type is NSObject, Any, AnyObject, or a fundamental type such Int or String, type information and context at the point of use may not fully convey intent. In this example, the declaration may be clear, but the use site is vague:
> 
> func add(observer: NSObject, for keyPath: String)
> grid.add(self, for: graphics)
> 
> To restore clarity, precede each weakly-typed parameter with a noun describing its role:
> 
> func addObserver(_ observer: NSObject, forKeyPath path: String)
> grid.addObserver(self, forKeyPath: graphics) // clear

This example already had a "for" in the label, but if it were not already there, the API, according to the guidelines, should become this:

func addObserver(_ observer: NSObject, keyPath path: String)
grid.addObserver(self, keyPath: graphics) // clear

Especially when compared with the ObjC import items. Seemingly, the ObjC APIs would be using prepositional clauses for parameter labels while the Swift labels would simply be more descriptive nouns.

The rules I described are keeping in strict guidance to the API design guidelines of adding nouns. So the above would actually be:

func add(observer o: NSObject, keyPath path: String)
grid.add(observer: self, keyPath: graphics)

However, if the API guidelines are really about creating APIs that are to be read in a more English-like manner, like ObjC APIs are designed, then the guidelines should really be amended to make that clear.

If the intention is truly that Swift APIs are supposed to read as naturally as ObjC APIs, then I completely agree with Joe that there should be a language change to actually require the label for parameters.

func addObserver(o: NSObject, path: String) // error: Argument label required for `path`.

However, this pattern of using prepositional phrases is still going to be in contradiction when using init(). 

let items = Array<Int>(capacityOf: 12, filledWith: 10)

// vs

let items = Array<Int>(count: 12, repeatedValue: 10)

The difference is that init has a much tighter coupling with direct sets to properties on the type. 

So yeah... basic story is, even after using Swift since its been released, I still have no clear sense of what a Swift API should look like, and the guidelines don't bring any of that clarity that I'm missing to the table. At the end of the day, Swift chose a C-style pattern which actively dissuades API authors from building the more fluid types of APIs we have in ObjC; there's simply more sigils in the way to break up a natural reading of the API.

grid.addObserver(self, forKeyPath: graphics)

// vs

[grid addObserver:self forKeyPath:graphics]

All of my inclinations for C-style syntactical languages say the API should be (in order of my conceptual preference model):

grid.add(observer: self, keyPath: graphics)

// or

grid.addObserver(self, keyPath: graphics)

// or

grid.addObserver(self, graphics)

And then take this proposed change to a Swift based on guideline updates:

 extension Strideable {

-  public func stride(through end: Self, by stride: Stride) -> StrideThrough<Self>
+  public func strideThrough(end: Self, by stride: Stride) -> StrideThrough<Self>

 }

In what part of the guidelines does it say that we should "by" as the argument label? The stride parameter does not have a lack of type information; in fact, it's quite strong. The call site *is* arguably better with "by" vs. "stride". However, "stride" is not missing context on type information or role specificity. 

I guess I'm starting to ramble on here, so I'll stop now...

-David

> On Jan 23, 2016, at 10:45 AM, Joe Groff <jgroff at apple.com> wrote:
> 
> This all looks good to me (aside from the linguistic problems with verb conjugation I've raised in another subthread). However, I think these naming guidelines lead us to reconsider our default argument labeling rules for 'func' declarations again, as David Owens and others have suggested. The stated goal of the current language rule is to guide people into good API design following our conventions, but I don't think it succeeds in serving that purpose. If you follow the guidelines, the argument labels for your secondary arguments generally end up becoming prepositional phrases, which make for poor variable names, and you're naturally guided to giving the argument an explicit descriptive binding name:
> 
> func perform(stuff: Stuff, with: Thing) {
>   with.apply(stuff) // 'with' is a weird variable name
> }
> 
> func perform(stuff: Stuff, with thing: Thing) {
>   thing.apply(stuff) // 'thing' is better
> }
> 
> The shorthand thus doesn't save the good API citizen from much work. On the other hand, a developer who's unaware or uninterested in the guidelines and is just trying to C or Java in Swift gets argument labels by default that neither follow the guidelines nor meet their expectation:
> 
> func atan2(y: Double, x: Double) -> Double { ... }
> 
> atan2(10, 10) // Why doesn't this work?
> atan2(10, x: 10) // Nobody wants this
> 
> And when staring down potentially dozens or hundreds of compile errors at various mismatched use sites, they're unlikely to reconsider their API naming choice, and will instead do the minimal amount of work to get their code to compile by suppressing the argument label. The language hasn't led this developer to better conventional API design either.
> 
> I can think of a couple possible modifications to the language rule that could help reduce the surprise factor, and still lead people to good API design:
> 
> - Require all 'func' arguments after the first to explicitly specify both a label and a binding name. Users following the guidelines will usually end up doing this anyway, and users who aren't will get a helpful message instead of unexpected behavior. This also avoids a problem with our current rule, where otherwise identical-looking parameter declarations in a 'func' end up behaving differently based on position. A diagnostic immediately at the declaration site also seems more likely to me to lead the developer to think more about their API naming; diagnosing call sites that don't look the way they want is just going to lead them to reactively suppress the labels to get their code to compile.
> - Change the default rule so that all arguments *after an explicitly labeled argument* default to being labeled (instead of all arguments after the first). It's unlikely anyone wants an unlabeled argument positionally after a labeled one, and the rare methods in Cocoa that take more than two arguments do tend to use noun rather than preposition phrases for arguments after the second. Users following the guidelines get nice labeled APIs, and users who aren't get the bare, uncaring anonymous arguments they deserve:
> 
> func perform(stuff: Stuff, with thing: Thing, options: StuffOptions) // perform(_:with:options:)
> func atan2(y: Double, x: Double) // atan2(_:_:)
> 
> -Joe
> 
>> On Jan 22, 2016, at 1:02 PM, Douglas Gregor via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>> 
>> Hello Swift community,
>> 
>> The review of SE-0023"API Design Guidelines" begins now and runs through January 31, 2016. The proposal is available here:
>> 
>> https://github.com/apple/swift-evolution/blob/master/proposals/0023-api-guidelines.md <https://github.com/apple/swift-evolution/blob/master/proposals/0023-api-guidelines.md>
>> Reviews are an important part of the Swift evolution process. All reviews should be sent to the swift-evolution mailing list at
>> 
>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>> or, if you would like to keep your feedback private, directly to the review manager. When replying, please try to keep the proposal link at the top of the message:
>> 
>> Proposal link:
>> 
>> https://github.com/apple/swift-evolution/blob/master/proposals/0023-api-guidelines.md <https://github.com/apple/swift-evolution/blob/master/proposals/0023-api-guidelines.md>
>> Reply text
>> 
>> Other replies
>>  <https://github.com/apple/swift-evolution#what-goes-into-a-review-1>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/master/process.md <https://github.com/apple/swift-evolution/blob/master/process.md>
>> Thank you,
>> 
>> -Doug Gregor
>> 
>> Review Manager
>> 
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution
> 

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160123/d7fdf801/attachment.html>


More information about the swift-evolution mailing list