[swift-evolution] Delegate Method Conventions

plx plxswift at icloud.com
Wed Feb 3 09:19:10 CST 2016

After taking the time to look at a few more delegate-style protocols it’s a lot less obvious that the existing delegate-style protocols would, in general, support such a transform (or at least an automated one).

The `UIPageViewDataSource` API is a good short example:

// original:
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController?
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController?

// best-plausible automated output (IMHO):
func viewControllerAfter(pageViewController: UIPageViewController, viewController: UIViewController) -> UIViewController? 
func viewControllerBefore(pageViewController: UIPageViewController, viewController: UIViewController) -> UIViewController? 

// manual-option A:
func viewController(in pageViewController: UIPageViewController, after viewController: UIViewController) -> UIViewController? 
func viewController(in pageViewController: UIPageViewController, before viewController: UIViewController) -> UIViewController? 

// manual-option B:
func nextViewController(in pageViewController: UIPageViewController, after viewController: UIViewController) -> UIViewController? 
func previousViewController(in pageViewController: UIPageViewController, before viewController: UIViewController) -> UIViewController? 

…wherein in case it’s not clear, the problem is that the general rule of “take the first non-delegate argument, split off the explanatory part, and use it as a method” leads to a more-confusingly-named function; this can obviously be corrected manually, but I’m skeptical the automated approach can be extended to cover such scenarios without requiring a lot of manual intervention/annotation.

Additionally, there are related cases like `NSKeyedUnarchiverDelegate`, which have the similar issue of being arguably worse-off for the transformation under most plausible edits in that style; consider e.g.:

// original
func unarchiver(unarchiver: NSKeyedUnarchiver, didDecodeObject object: AnyObject?) -> AnyObject? // lets you replace `object` with something else
func unarchiver(unarchiver: NSKeyedUnarchiver, willReplaceObject object: AnyObject, withObject newObject: AnyObject) 
func unarchiver(unarchiver: NSKeyedUnarchiver, cannotDecodeObjectOfClassName name: String, originalClasses classNames: [String]) -> AnyClass? // lets you suggest an alternative class to try

// plausible mechanical transforms
func didDecode(unarchiver: NSKeyedUnarchiver, object: AnyObject?) -> AnyObject? 
func didDecodeObject(unarchiver: NSKeyedUnarchiver, object: AnyObject?) -> AnyObject? 
func didDecode(unarchiver: NSKeyedUnarchiver, decodedObject: AnyObject?) -> AnyObject? 

func willReplace(unarchiver: NSKeyedUnarchiver, object: AnyObject?, withObject newObject: AnyObject)
func willReplaceObject(unarchiver: NSKeyedUnarchiver, object: AnyObject?, withObject newObject: AnyObject)

func cannotDecodeObject(unarchiver: NSKeyedUnarchiver, className name: String, originalClasses classNames: [String]) -> AnyClass?
func cannotDecodeObjectOfClass(unarchiver: NSKeyedUnarchiver, name: String, originalClasses classNames: [String]) -> AnyClass?
func cannotDecodeObjectOfClass(unarchiver: NSKeyedUnarchiver, className: String, originalClasses classNames: [String]) -> AnyClass?

…none of which are really any clearer, to my eyes (and still rather far from the semantics, as the `didDecode` is really asking if you want a replacement object, and the `cannotDecode…` is really asking for another class to try given that unarchiving using the archive-specified class has failed).

Finally, there are also APIs like `NSURLSessionDataDelegate` which have *2* “delegate-style” parameters: `URLSession(_:dataTask:didReceiveResponse:completionHandler:` (and possibly others), so an automated transform has to somehow handle such cases.

Given all of these, perhaps the best that can be done at this time is to try and get the general guidelines such that for *new* delegate APIs it’ll be possible to give them Swift names that are guideline-conformant, but can still be exposed to Objective-C (where necessary) in an idiomatic way (e.g., has the arguments in the right ordering to be a delegate/datasource/etc. call in Objective-C).

But at least at this point I’m rather dubious there’s a worthwhile transformation out there that can be uniformly applied to the “SDK delegate/datasource protocols” and lead to an overall improvement.

> On Jan 30, 2016, at 12:06 PM, plx via swift-evolution <swift-evolution at swift.org> wrote:
>> On Jan 26, 2016, at 6:34 PM, Dave Abrahams via swift-evolution <swift-evolution at swift.org <mailto: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 <mailto: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 23, 2016, at 2:33 PM, Dave Abrahams via swift-evolution
>>>>>> <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>>>>> on Sat Jan 23 2016, plx
>>>>>> <swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>>>> <mailto: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 <mailto: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>
> _______________________________________________
> 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/20160203/cd2072f5/attachment.html>

More information about the swift-evolution mailing list