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

Dave Abrahams dabrahams at apple.com
Tue Jan 26 15:32:10 CST 2016


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

>> On Jan 24, 2016, at 7:07 PM, Dave Abrahams <dabrahams at apple.com> wrote:
>> 
>> Understood; the "sentence-like" idea keeps getting repeated.  The
>> keepers of Cocoa APIs tell me that, since several years ago, they moved
>> away from adding prepositions to secondary selector pieces to make
>> things more sentence-like.
>
> Yes, I’ve noticed that as well. 
>
>>> The problem I see is that there is a mixed bag of what "good" APIs
>>> look like. And when push comes to shove, code wins. The libraries
>>> exposed in code, even through the ObjC bridging, will be used as the
>>> basis of what determines what good Swift APIs look like more than a
>>> published doc.
>> 
>> Yep.  But a big part of the reason to publish the doc is so we can
>> decide what direction to push the library APIs.
>
> I agree. And really, I find most of the doc fairly good.
>
>>>> 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.  
>>> 
>>> Right, but the argument is that the API is indeed better with the
>>> "for" preposition. I believe it is, I think you are also saying that
>>> it is.
>> 
>> Yes.  So... what are we arguing about?
>
> At the heart of it, that the guidelines don’t seem to help one down a
> path to use prepositions, but rather only descriptive nouns.
>
> So in one sense you are arguing to make things more English-like
> because they read better at the call site, but on the other, you’ve
> stated APIs reading more English-like is a non-goal. Basically, I
> think the guideline tries to give some rules on how to get get APIs,
> but doesn’t talk much about what a good API is.
>
>>> This is the closest guideline that I think attempts to address it:
>>> 
>>>> Clarity at the point of use is your most important goal. Code is
>>>> read far more than it is written.
>>> 
>>> But the clarity here is, ironically, inherently ambiguous. Clarity
>>> could mean to make the weak type information known. Clarity could be
>>> about intention of how the parameter could be used. Clarity could
>>> simply be a more verbose name that provides additional context. I
>>> think it would be clear to have guidelines that actually describe what
>>> you think make up a good call site.
>> 
>> We intend that to mean "clarity of semantics."  Would that change, um,
>> clear this up for you?
>
> Maybe so.
>
>> 
>>> thing.strideTo(10, stride: 1) // clear
>>> thing.strideTo(10, by: 1)     // clear and linguistically better
>>> thing.strideTo(10, 1)         // not clear
>> 
>> Actually I think the first is unclear simply because the word "stride"
>> takes two different roles, one as a noun and one as a verb.  It's
>> possible to misinterpret the 1 as being an expression of the word in the
>> base name, or at least it's possible to be confused by that until you
>> work out all the reasons that it doesn't make sense.
>> 
>>> Both provide some amount clarity at the call site as the value of 1 is
>>> quite clear what it means. So in one sense, I agree that the defaults
>>> of the language today push you towards clarity.
>>> 
>>> To me, the guidelines, as I understand them, lead us to the first
>>> option when I believe the intent is to actually get to the second
>>> usage.
>> 
>> Yes, IMO that is the intent!  But why do you think they lead us to the
>> first option?  Because they explicitly mention using nouns to clarify
>> roles?
>
> Yeah. I don’t think you actually mean descriptive nouns.
>
>>> However, indeed someone thinks that we should use "at", but based on
>>> the guidelines, it's really hard to understand why:
>>> 
>>> -  mutating func insert(newElement: Iterator.Element, atIndex i: Int)
>>> +  mutating func insert(newElement: Iterator.Element, at i: Int)
>>> 
>>> Again, I agree that "at" reads better at the call site. However, it
>>> doesn't provide any real clarity over the default "index" (sidenote: i
>>> is still a bad choice ;)), 
>> 
>> It all depends on what it does for the doc comment.  IMO there's nothing
>> wrong with “Inserts `newElement` before the `i`th element, or at the end
>> if `i == count`”.
>
> Hmm… we disagree on that point. If I need to go the documentation to
> get basic clarification of the parameter, I’m not really sure what the
> point of the label is then.

I don't understand your point.  The argument label (“at”) appears at the
use-site.  The parameter name (“i”) appears only at the declaration
site.  I'm saying the name of the parameter should be chosen to make the
documentation comment read well.  Are you arguing that it shouldn't?

Or are you arguing that

  /// Inserts `newElement` before the `i`th element, or at the end
  /// if `i == count`
  mutating func insert(newElement: Iterator.Element, at i: Int)

would read better with a different name in place of `i`?

>>> I actually really hard pressed to come up with a definitive example as
>>> to why that should be allowed. The best I can argue is that "add"
>>> itself is too generic and could be confused on the implementing type
>>> that may also have an "add" function. However, the additional label
>>> information helps with that.
>> 
>> You have to consider how this protocol is going to be used; it's a
>> mix-in that will be broadly applied to many things that could add an
>> NSObject for many purposes.  Unfortunately, as hard as we tried, we were
>> unable to come up with guidelines that actually make API design easy!
>
> Agreed. API design isn’t easy. =)
>
>>> However, I absolutely believe that the "strideTo/strideThrough"
>>> example is clearly on the side of really being part of the argument
>>> label. In both cases we are striding, the difference is on where we
>>> stop.
>>> 
>>> extension Strideable {
>>> 
>>> -  public func stride(to end: Self, by stride: Stride) -> StrideTo<Self>
>>> +  public func strideTo(end: Self, by stride: Stride) -> StrideTo<Self>
>>> 
>>> }
>>> 
>>> extension Strideable {
>>> 
>>> -  public func stride(through end: Self, by stride: Stride) -> StrideThrough<Self>
>>> +  public func strideThrough(end: Self, by stride: Stride) -> StrideThrough<Self>
>>> 
>>> }
>>> 
>>> This change seems to be simply because the usage of labelled first
>>> parameters are frowned up, not because it's actually a better place to
>>> describe what is going on.
>> 
>> I can't argue with you here.
>
> So the question is this though: what is the better Swift API? I think
> examples like these and reasons as to why one is chosen over the other
> at least help with the guidelines being more inclusive. 

Yeah, the rationales that led to the guidelines specifying #2 may be
hard to appreciate.  I'm going to lay them out here so we can talk about
whether and how to incorporate them in the document (assuming that the
guidelines' ruling on that API won't change):

* Simple guidelines are better than complex guidelines.

* Guidelines with exceptions and special cases are more complex.

* We don't have a clear rule for deciding that one API needs/doesn't
  need a first argument label.

* Even if we come up with such a rule, if it can't be *automated* in the
  importer, Cocoa will fail to follow the guideline in Swift.  So for
  any rule that considers high-level concepts like the “semantic
  connection between parameters,” there needs to at least be a heuristic
  that manages to approximate it when applied to Cocoa APIs.

* If we say “do it where it makes sense,” programmers will waste
  countless hours arguing about whether it makes sense in a given case.
  One great value of API guidelines is that they prevent that kind of
  rat-holing.

> I agree that it’s very hard to generalize something as subjective as a
> “good API”. So in light of that, I think more examples of some of the
> boundaries edges at least give people some reference to compare ask,
> “is my API more like this or more like that?”

...so in many ways, the choice to ban first argument labels is not about
what's good for a particular API, but about what's good for the whole
surface area of our code, and what's good for programmers using the
guidelines.

I'm still hopeful, actually, that we may be able to come up with a
better rule for first argument labels, but the points above are what
we're up against, and I'm not sure there's an appropriate place in the
guidelines document to spell them out.

>
>>> I'm trying to, but we're not really following the ObjC guidelines
>>> either. At least on paper. I think you keep talking about readable
>>> APIs, but I feel the guidelines keep talking more about
>>> descriptive. 
>>> 
>>> The difference is subtle, I agree. But I think it's the difference
>>> between these two APIs:
>>> 
>>> items.insert(12, atIndex: 2)  // or items.insert(12, at: 2)
>>> 
>>> items.insert(12, index: 2)
>>> 
>>> Again, it's about which is supposed to be "canonical" Swift.
>> 
>> 
>> OK, I'll try to come up with some wording adjustments that sort this out.
>> 
>> Thanks for your patience,
>
> Thanks for being willing to listen and discuss!
>
> -David
> _______________________________________________
> 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