[swift-evolution] Delegate Method Conventions

plx plxswift at icloud.com
Sat Jan 30 12:06:22 CST 2016


> On Jan 26, 2016, at 6:34 PM, Dave Abrahams via swift-evolution <swift-evolution at swift.org> wrote:
> 
> 
> on Tue Jan 26 2016, plx <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
> 
>>> On Jan 24, 2016, at 5:57 PM, Dave Abrahams via swift-evolution
>>> <swift-evolution at swift.org> wrote:
>>> 
>>> 
>>> on Sat Jan 23 2016, plx <swift-evolution at swift.org> wrote:
>> 
>>> 
>>>>> On Jan 23, 2016, at 2:33 PM, Dave Abrahams via swift-evolution
>>>>> <swift-evolution at swift.org> wrote:
>>>>> 
>>>>> 
>>>>> on Sat Jan 23 2016, plx
>>>> 
>>>>> <swift-evolution at swift.org
>>>>> <mailto:swift-evolution at swift.org>> wrote:
>>>>> 
>>>>>>> On Jan 22, 2016, at 6:12 PM, Ross O'Brien via swift-evolution
>>>>>>> <swift-evolution at swift.org> wrote:
>>>>>>> 
>>>>>>> How would we apply this to delegate patterns?
>>>>>>> For example, would we keep
>>>>>>> tableview(tableView:cellForRowAtIndexPath:), or would we switch to
>>>>>>> delegate(tableView:cellForRowAtIndexPath:) ?
>>>>>>> Or perhaps better, for clarity over which protocol is being
>>>>>>> conformed to / which property of the delegator is calling the
>>>>>>> function:
>>>>>>> dataSource(tableView:cellForRowAtIndexPath:),
>>>>>>> delegate(tableView:didSelectRowAtIndexPath:)
>>>>>> 
>>>>>> FWIW, I am personally favorable to a more radical-renaming for
>>>>>> delegate methods, roughly the below:
>>>>>> 
>>>>>> func numberOfSections(inTableView tableView: UITableView) -> Int
>>>>>> // <- against guidelines, but symmetric
>>>>>> func numberOfRows(inTableView tableView: UITableView, forSection section: Int) -> Int
>>>>>> func cellForRow(inTableView tableView: UITableView, atIndexPath
>>>>>> indexPath: NSIndexPath) -> UITableView
>>>>> 
>>>>> The interesting thing about delegate methods is that, for the most part,
>>>>> use-sites don't appear in user code.  So *if* you're going to come up with
>>>>> special conventions just for delegate methods you'd want to serve the
>>>>> declaration site.  I don't know what these things *ought* to look like,
>>>>> but the declarations above look to me like they've got an awful lot of
>>>>> redundancy that doesn't help readability.
>>>> 
>>>> Most of what follows should really be in the discussion about the
>>>> Objective-C import, not here, but I’ll respond here with the parts
>>>> relevant to the guidelines.
>>>> 
>>>> It seems self-evident that imported delegate methods violate the
>>>> spirit of Swift’s API guidelines; in particular, the rule that
>>>> “Methods can share a base name when they share the same basic meaning
>>>> but operate on different types, or are in different domains” seems
>>>> relevant. 
>>> 
>>> That's quite true.
>>> 
>>>> It’s thus been a bit surprising to me that delegate-style methods
>>>> haven’t *already* gotten some special treatment; 
>>> 
>>> Well, it's a fact of life that major efforts like this one (probably
>>> property behaviors are the same bucket) are going to have to land
>>> without solving all the problems they are related to.  I believe
>>> strongly that we should do *something* about delegate methods.  I also
>>> believe they're a separable problem and we should be able to evaluate
>>> the current direction without working out all the details of how we're
>>> going to handle them.  That's why I changed the subject line: I'd like
>>> to agree that special treatment for delegate methods in the importer is
>>> out-of-scope in this review.
>>> 
>>>> what I had isn’t great, but put it and some variants up against the
>>>> original, like so:
>>>> 
>>>> func numberOfRows(in tableView: UITableView, forSection section: Int) -> Int
>>>> func numberOfRowsIn(tableView: UITableView, forSection section: Int) -> Int
>>>> func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
>>>> func numberOfRows(inTableView tableView: UITableView, forSection section: Int) -> Int
>>> 
>>> I assume you mean the 3rd one to be "the original?”
>> 
>> Yes, here: tableView(_:numberOfRowsInSection:)
>> <https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableViewDataSource_Protocol/#//apple_ref/occ/intfm/UITableViewDataSource/tableView:numberOfRowsInSection: <https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableViewDataSource_Protocol/#//apple_ref/occ/intfm/UITableViewDataSource/tableView:numberOfRowsInSection:>>
>>> 
>>>> …(note the longest is only ~10 characters longer than the shortest!). 
>>> 
>>> Sorry, I don't see why that is relevant.  Care to explain?
>> 
>> I did not make the intention clear; apologies. I was intending to
>> illustrate that although all of the examples contain redundancies,
>> none of them are egregiously worse than the others (including the
>> original); the worst case is only moderately more-redundant than the
>> best case.
> 
> I understand that, but if you'll forgive me for being blunt, so what?

At the time I thought you meant simply that:

- that the suggestion was annoyingly verbose and redundant

…which seemed an odd criticism, given that “the original” was similarly verbose-and-redundant; however, I think you may have been thinking:

- that because said suggestion was not appreciably-less-redundant than "the original", it’s hard to justify such a transformation (more work for marginal benefit)

…in which case we have may have simply been talking past each other.

> 
>>>> Although there might be an as-yet unseen option that’s superior to all
>>>> of the above, just out of those 4 it’s hard to see how you can justify
>>>> option #3 using the API guidelines; 
>>>> it also seems hard to envision a self-consistent expansion of the
>>>> guidelines that’d lead to favoring #3.
>>> 
>>> You can't.
>>> 
>>>> As already noted this is really more-relevant to the “objective-c
>>>> import revision”, but you can frame my points as obliquely asking “to
>>>> what extent should the Swift API guidelines actually matter when doing
>>>> the big Objective-C import?”
>>> 
>>> We're willing to accept that some imported APIs will not follow the
>>> guidelines.
>>> 
>>>> I also question your sense of real-world use of delegate protocols;
>>>> just taking inventory of the most recent project I completed, it looks
>>>> like it had 5 custom delegate-style protocols. Of these, 4 had exactly
>>>> one implementation each, and 1 had exactly 2 implementations; 
>>> 
>>> And how many use-sites were there?
>> 
>> 5, just counting “classes using said delegates”; 14 if you go by
>> individual method use.
> 
> My point is that with most methods, the use sites clearly overwhelm the
> declaration sites.  I don't believe that to be the case with delegates.
> But this is really a minor detail.

For the “SDK” protocols from Apple this is of course true; on the other hand, most one-off, in-app protocols are declared once, used once, and implemented at-most a couple times (1 being the mode, I’d imagine).

TBH I wouldn’t have noticed the oddity in delegate-style APIs if I hand’t had to create a few one-off ones and noticed how *odd* it is.

> 
>>> 
>>>> I don’t think this is that untypical. If you accept it as not too
>>>> atypical,
>>> 
>>> I do.
>>> 
>>>> it suggests a more uniform balance between defining a delegate
>>>> protocol, using said protocol, and implementing said protocol.
>>> 
>>> Not necessarily.  How many times did this project implement delegate
>>> protocols that were defined elsewhere?  
>> 
>> Looks like 12, for implementations; “a lot”, going by by the method
>> count.
>> 
>> In any case I don’t dispute the general point, just perhaps the exit.
> 
> ?

I meant to type `extent`; apologies.

> 
>>> In any case, for what it's worth, I personally think the direction
>>> you're going with those delegate APIs is great, and it has the benefit
>>> of bringing them into conformance with other guidelines.  My only point
>>> in saying that the declaration site is more important with delegate
>>> methods than with others is that there's more type information at the
>>> declaration site of a method than at its use site, so there's definitely
>>> no reason to make them more verbose than others.  Making them simply
>>> follow the existing guidelines exactly is a simple solution that IMO
>>> leads to good code, and one I would support.
>>> 
>>> However, what Cocoa guys like Tony Parker say about the eventual
>>> direction of delegate APIs should probably carry a lot more weight than
>>> what I say.
>>> 
>>>> To wind this digression down now, the API guidelines’ attitude towards
>>>> redundancy seems somewhat troubling; no one wants needless redundancy,
>>>> but natural languages tend towards redundancy (cf
>>>> agreement/pleonasm/etc) and it’s not at all self-evident that less
>>>> redundancy always implies increased readability (which you may or may
>>>> not be intending to imply; I can’t tell)…especially when it’s easy to
>>>> get fooled by increased speed-of-reading.
>>> 
>>> This seems like a pretty vague concern.  Let's see concrete examples of
>>> problems you think the guidelines' attitude toward redundancy will
>>> cause.  FWIW, "omit needless words" isn't something we just came
>>> up with ourselves: it's a time-honored principle of clear English
>>> writing (google it).
>> 
>> Sure, sure, but if you’ll forgive a cheap shot I’d point out Strunk
>> would’ve tut-tutted here and suggested, perhaps, “we didn’t invent
>> ‘omit needless words’”. The tricky part of that rule is that what’s
>> needless is highly contextual, and to be *understood* when writing in
>> a highly-condensed style usually requires a large amount of shared
>> context.
> 
> Oh, that *was* cheap; and I'm not sure I can forgive it! :-)

I only felt justified taking it b/c “came up with ourselves” is at least a borderline pleonasm.

> 
>> Which need-for-context is at the root of my admittedly-vague concern;
>> I’ve done my best to come up with a concrete-ish example, but it’s a
>> bit contrived and not as strong as I’d like, either. It’s more of an
>> "ecosystem concern”, too.
>> 
>> Here are *six* functions that could conceivably be named `min` under the guidelines:
>> 
>> func min() -> Generator.Element? // obviously only where `Generator.Element` is `Comparable`
>> func min(isLessThan comparator: (Generator.Element,Generator.Element)
>> -> Bool) -> Generator.Element?
>> func min<K:Comparable>(extractor: (Generator.Element) -> K) -> K?
>> func min<K:Comparable>(extractor: (Generator.Element) -> K?) -> K?
>> func min<T>(extractor: (Generator.Element) -> T, isLessThan comparator: (T,T) -> Bool) -> T?
>> func min<T>(extractor: (Generator.Element) -> T?, isLessThan comparator: (T,T) -> Bool) -> T?
>> 
>> …and perhaps they *all* should be named `min` (and we simply let
>> context and type information sort it all out for us).
>> 
>> But if the names should be different, what’re good choices?
>> 
>> My vague concern is that having “maximally-terse” names for the
>> standard library functions makes it trickier to choose
>> "non-misleading” names for such closely-related variants.
> 
> We are not going for maximal terseness in the standard library.  When we
> pick a name like "min" it's because of precedent and expectations.
>> 
>> EG: if you go with `minValue` for the variants, to a casual reader
>> there’s room for confusion vis-a-vis `min` (I suspect many would
>> initially guess that `minValue` does what `minElement` does today, but
>> would guess the behavior correctly if given a choice between
>> `minElement` and `minValue`).
>> 
>> Unfortunately for my case, I think `minFor` is a perfectly-reasonable
>> choice here, which undermines my concrete example (I warned you the
>> case wasn’t going to be very convincing).
> 
> It is just as you said :-)
> 
>> But that’s the kind of vague concern I have here: that a
>> “maximally-terse” naming convention can be harder to extend in a way
>> that’s both self-consistent and not-potentially-misleading.
>> 
>> But I don’t have a great suggestion for an additional guideline, and
>> there may be nothing serious to worry about here, either.
> 
> I think we've made it very clear that we're not going for maximal
> terseness:
> 
>   Clarity is more important than brevity. Although Swift code can be
>   compact, it is a non-goal to enable the smallest possible code with
>   the fewest characters. Brevity in Swift code, where it occurs, is a
>   side-effect of the strong type system and features that naturally
>   reduce boilerplate.
> 
>
I understand you don’t think you’re going for maximal terseness, but guidelines aimed at redundancy-reduction with a lack of concern for what the linguists call “markedness” (which is amongst other things context-dependent, and thus not easy to judge in isolation).

Specifically, what counts as “weak type information” will always be somewhat context-dependent. Here’s another example, this time drawn from the Objective-C import proposal: `documentFor(url: NSURL)`.

`documentFor(url: NSURL)` is certainly in conformance to the current guidelines.

However, in an application context, I’d usually want application code to *largely* avoid calling such “low-level” methods directly, in favor of wrappers defined in terms of application-level concepts, e.g. some possible examples:

documentFor(windowState: WindowState) // e.g. state-restoration
documentFor(bookmark: BookmarkData) // e.g. user-favorites menu
documentFor(user: User) // e.g. a 1:1 user-document thing (consider a test-administration use case)
documentFor(template: TemplateDescriptor) // e.g. create-new-document from a template of some kind
documentFor(image: Image) // e.g. create-a-new-document containing `image` and ready for further use

…and so on and so forth. Note that all of the above are also in conformance with the guidelines—if `documentForURL` is wrong, then `documentForWindowState` is even more wrong!

So, given that I would prefer application-level code use the "application-level variants", I’m a bit stuck if I want the “application-level” variants to be easy to distinguish (visually) from the “low-level” variant: 

- `documentFor(url: NSURL)` is out of my control, so I can’t unilaterally-demote it to `documentForURL(url: NSURL)` on my end (I can write a wrapper, but can’t hide the base method at this time...)
- lengthening the “application-level” names goes against the spirit of the guidelines
- changing the “application-level” names often feels unidiomatic (`restoredDocumentFor(windowState: WindowState)` is ok, but `bookmarkedDocumentFor(bookmark: BookmarkData)` feels awful-and-unnecessary)

…which wan’t a concern in Objective-C, b/c the increased verbosity kept everything uniform and easily-verifiable:

- `documentForURL:`
- `documentForWindowState:`
- `documentForBookmark:`
- `documentForUser:`
- `documentForTemplate:`
- `documentForImage:`

…(with the extensions likely having `prefix_` in front to boot!).

I do think this is going to be a bit awkward in all settings where the guidelines steer us towards having a bunch of identically-named extension methods calling through to “standard-library” functionality; in such settings what used to be adequate type-information (within the standard library context) starts to look like weak type information (in its context-of-use).

I don’t think the right answer is for, e.g., the Objective-C import to uniformly start using `documentForURL`-type names; that feels like overkill.

I do think a loosened attitude about when-and-how to include a first-argument label will open up a lot of room to address such concerns; I suspect that the other discussions around when to use identical base names and when to use first argument labels is originating from at-least broadly-similar concerns.

Perhaps it can be addressed by taking the below:

> Methods can share a base name when they share the same basic meaning but operate on different types, or are in different domains.

…and tweaking it to include a note that such methods *may* label their first argument when necessary to clarify either:

- the method variant (when the methods do the same thing to the point the same base name should be used, but nevertheless are meant for different-enough intents to benefit from an explicit distinction)
- the interpretation of said argument (hopefully this is rather rare)

…but at this point I think I’ve said my bit, brought up the delegate-naming issue, and appreciate the time and consideration. The delegate convention seems to need a reboot, anyways, and should seemingly at least wait for the first review of the objective-c import discussion to conclude.

> -Dave
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160130/99b9022e/attachment.html>


More information about the swift-evolution mailing list