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

Dave Abrahams dabrahams at apple.com
Sun Jan 24 13:17:25 CST 2016


on Sat Jan 23 2016, David Owens II <swift-evolution at swift.org> wrote:

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

Hi David,

Let me start by expressing my appreciation for the way you're struggling
with the hard issues here.  The questions you're asking reflect many of
the same ones we asked ourselves during the development of these
guidelines.

> 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.

No, that's not the intent of the guideline.

> 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

The reason "for" is there is that otherwise you can't tell what the
relationship between the observer and the keyPath is.  For example, if
it had been "at" instead of "for," it would completely change the
meaning. "Of" would probably be more descriptive, frankly.  But the
example isn't trying to illustrate anything about that preposition or
that relationship.  

> 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.

I don't know what you mean here, sorry.

> 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)

I'll have to go back and look at the rules you described, but regarding
the above example, my reason to keep "observer" in the base name is that
adding observers is really a totally distinct thing from adding, say,
gesture recognizers, or animation steps, or anything else.  There isn't
one big pot to which you're adding everything.  If there were, the
receiver would be like a collection, and we wouldn't need a noun there
at all.

> 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.

The point of the guidelines is not to make code look like English; it's
to make code clear and understandable.  It happens that in many cases,
leveraging English grammar serves that purpose well.  In other cases
it's necessary to take a different approach, which is why we have sin(x)
and zip(x, y).  But as I think we make clear, one shouldn't add words
just to make an API more “English-like.”

> If the intention is truly that Swift APIs are supposed to read as
> naturally as ObjC APIs, 

Ah... “naturally” is in the eye of the beholder, though.

> 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`.

I'm confused; "path" has an argument label; it's "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. 

I don't think that's the key distinction.

> 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, 

Well, part of that may be that we haven't had any clarity on that up to
this point, so a couple of years of experience don't help you much.

> and the guidelines don't bring any of that clarity that I'm missing to
> the table. 

That part, I'm more worried about.

> 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]

Personally I don't see those two as significantly different, and the
Swift pattern has the benefit of un-nesting method chains, allowing
"fluent" interfaces.  But we're not reviewing the basic syntax choices
of Swift here.

> 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)

I think we're asking you to let go of your inclinations and think more
carefully about what will serve the reader of code using your API :-).
This is less about having a pattern to follow than about how to make
choices that lead to readable, maintainable code.

> 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? 

It doesn't say that explcitly anywhere.  Good APIs aren't the result of
applying a set of mechanical rules.  You have to consider what the usage
will look like.

> The stride parameter does not have a lack of type
> information; in fact, it's quite strong. 

And there's no noun preceding it.  Where's the problem?

> The call site *is* arguably better with "by" vs. "stride". 

By Jove, I think he's got it! :-)

> 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...

I actually think you're on the right track.  Let's keep working on this;
maybe it will lead to something we can clarify in the guidelines.

Thanks,

> -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
>> 
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

-- 
-Dave



More information about the swift-evolution mailing list