[swift-evolution] [Idea] How to eliminate 'optional' protocol requirements

David Waite david at alkaline-solutions.com
Thu Apr 7 22:33:21 CDT 2016

> On Apr 7, 2016, at 6:12 PM, Douglas Gregor via swift-evolution <swift-evolution at swift.org> wrote:
> With my proposal, we’d have some compiler-synthesized attribute (let’s call it @__caller_default_implementation) that gets places on Objective-C optional requirements when they get imported, e.g.,
> @objc protocol NSTableViewDelegate {
>   @__caller_default_implementation func tableView(_: NSTableView, viewFor: NSTableColumn, row: Int) -> NSView?
>   @__caller_default_implementation func tableView(_: NSTableView, heightOfRow: Int) -> CGFloat
> }

If I understand correctly:

1. Optional methods in protocols imported from objective C gain a special flag when imported as Swift methods
2. Such methods can be unimplemented (such that the message will fail, or responds(to:) returns false) in objective-c code and bridged swift instances.
3. To implement that protocol in swift, you must have implementations of every protocol method, including optional ones
4. This means that there may be manual work around bridging protocols with optional messages into swift.
5. If the method implementation is marked @nonobjc it will fulfill the swift requirement that there be a method implementation, but that implementation will not be exposed to Objective-C
6. Swift code can call such a @nonobjc implementation, while Objective-C code will consider there to be nothing there.
7 An implementation that is only be applied to the swift variant is possibly fine in extensions while generates a warning within a swift class, or perhaps requires an explicit @nonobjc attribute.

I like it, except for the requirement for a manual implementation in Swift before you can support the protocol. 

I looked at whether the protocol members might be implemented by returning the nil/zero/false value you would get if you sent the message to nil. 

Cursory search quickly hit NSAccessibilityElement, which has an optional “accessibilityIdentifier” method returning a non-nil NSString. I suspect that the method also requires the string to be non-empty. Thus, a default implementation that represents the right thing to represent in a generated default implementation would likely be brittle.

I could also see an imported protocol where *any* default implementation of the optional method would not meet the requirements of an actual implementation of the method (not being versed in this particular interface, I’ll just straw man that the identifier is required to be unique within an application)

Thus I wonder if there may be some other way to support the idea of two distinct protocols, the protocol as defined in Objective C, and the protocol as defined in Swift.

Options that sprang to mind:
- the methods which return optional values have a default implementation that returns nil. Methods which return non-optional values will have the Swift protocol modified to return an Optional value, which they will do by default. So for example, Still on NSAccessibilityElement,


would be imported as

	func isAccessibilityFocused() -> Bool?

with a default implementation returning nil. To actually implement the objective C protocol’s optional method, you must implement the version with the correct nullability variant, so in this case:

     @objc func isAccessiblityFocused() -> Bool { return focused }

(Of course, this means that a non-optional result value would need to satisfy an optional result valued variant in a protocol)

- similar to the above, but rather than overriding result values to support a default variant, overload ErrorType. Imported variants which throw will by default throw a bridging-specific ErrorType when called from Swift. Optional methods which do not throw will have a throwing variant generated on the Swift side. 

Again similar to the above, to satisfy the objective-C protocol requirement your implementation would need to be non-throwing.

I like this better in terms of capturing to the best of ones ability the ‘spirit’ of optional methods and behavior in swift. However, this seems like it will result in more deviation between the Swift and Objective-C protocol method signatures.



> And “optional” disappears from the language. Now, there’s no optionality left, so our useDelegate example tries to just do correct calls:
> func useDelegate(delegate: NSTableViewDelegate) -> NSView? {
>   let view = delegate.tableView(tableView, viewFor: column, row: row)
>   let height = delegate.tableView(tableView, heightOfRow: row)
> }
> Of course, the code above will fail if the actual delegate doesn’t implement both methods. We need some kind of default implementation to fall back on in that case. I propose that the code above produce a compiler error on both lines *unless* there is a “default implementation” visible. So, to make the code above compile without error, one would have to add:
> extension NSTableViewDelegate {
>   @nonobjc func tableView(_: NSTableView, viewFor: NSTableColumn, row: Int) -> NSView? { return nil }
>   @nonobjc func tableView(_: NSTableView, heightOfRow: Int) -> CGFloat { return 17 }
> } 
> Now, the useDelegate example compiles. If the actual delegate implements the optional requirement, we’ll use that implementation. Otherwise, the caller will use the default (Swift-only) implementation it sees. From an implementation standpoint, the compiler would effectively produce the following for the first of these calls:
> if delegate.responds(to: #selector(NSTableViewDelegate.tableView(_:viewFor:row:))) {
>   // call the @objc instance method with the selector tableView:viewForTableColumn:row:
> } else {
>   // call the Swift-only implementation of tableView(_:viewFor:row:) in the protocol extension above
> }
> There are a number of reasons why I like this approach:
> 1) It eliminates the notion of ‘optional’ requirements from the language. For classes that are adopting the NSTableViewDelegate protocol, it is as if these requirements had default implementations.
> 2) Only the callers to these requirements have to deal with the lack of default implementations. This was already the case for optional requirements, so it’s not an extra burden in principle, and it’s generally going to be easier to write one defaulted implementation than deal with it in several different places. Additionally, most of these callers are probably in the Cocoa frameworks, not application code, so the overall impact should be small.
> Thoughts?
> 	- Doug
> _______________________________________________
> swift-evolution mailing list
> 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/20160407/60015faf/attachment.html>

More information about the swift-evolution mailing list