Proposal sounds nice, but shouldn't it go hand in hand with the review of 
dispatching rules for protocol extensions (i.e.: dynamic dispatch by default 
unless overridden by a user declaration/annotation or when the compiler is sure 
no side effects will occur... ProtocolA and InstanceAImplementingProtocolA must 
behave the same when calling a method)?
In a type safe language, the lack of safety current complex dispatching rules 
bring seems odd not to address :/. Sorry for the aside rant.

[[iOS messageWithData:ideas] broadcast]

> On 8 Apr 2016, at 01:12, Douglas Gregor via swift-evolution 
> <swift-evolution@swift.org> wrote:
> 
> Hi all,
> 
> Optional protocol requirements in Swift have the restriction that they only 
> work in @objc protocols, a topic that’s come up a number of times. The start 
> of these threads imply that optional requirements should be available for all 
> protocols in Swift. While this direction is implementable, each time this is 
> discussed there is significant feedback that optional requirements are not a 
> feature we want in Swift. They overlap almost completely with default 
> implementations of protocol requirements, which is a more general feature, 
> and people seem to feel that designs based around default implementations and 
> refactoring of protocol hierarchies are overall better.
> 
> The main concern with removing optional requirements from Swift is their 
> impact on Cocoa: Objective-C protocols, especially for delegates and data 
> sources, make heavy use of optional requirements. Moreover, there are no 
> default implementations for any of these optional requirements: each caller 
> effectively checks for the presence of the method explicitly, and implements 
> its own logic if the method isn’t there.
> 
> A Non-Workable Solution: Import as optional property requirements
> One suggestion that’s come up to map an optional requirement to a property 
> with optional type, were “nil” indicates that the requirement was not 
> satisfied. For example, 
> 
> @protocol NSTableViewDelegate
> @optional
> - (nullable NSView *)tableView:(NSTableView *)tableView 
> viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row;
> - (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row;
> @end
> 
> currently comes in as
> 
> @objc protocol NSTableViewDelegate {
>   optional func tableView(_: NSTableView, viewFor: NSTableColumn, row: Int) 
> -> NSView?
>   optional func tableView(_: NSTableView, heightOfRow: Int) -> CGFloat
> }
> 
> would come in as:
> 
> @objc protocol NSTableViewDelegate {
>   var tableView: ((NSTableView, viewFor: NSTableColumn, row: Int) -> 
> NSView?)? { get }
>   var tableView: ((NSTableView, heightOfRow: Int) -> CGFloat)? { get }
> }
> 
> with a default implementation of “nil” for each. However, this isn’t 
> practical for a number of reasons:
> 
> a) We would end up overloading the property name “tableView” a couple dozen 
> times, which doesn’t actually work.
> 
> b) You can no longer refer to the member with a compound name, e.g., 
> “delegate.tableView(_:viewFor:row:)” no longer works, because the name of the 
> property is “tableView”.
> 
> c) Implementers of the protocol now need to provide a read-only property that 
> returns a closure. So instead of
> 
> class MyDelegate : NSTableViewDelegate {
>   func tableView(_: NSTableView, viewFor: NSTableColumn, row: Int) -> NSView? 
> { … }
> }
> 
> one would have to write something like
> 
> class MyDelegate : NSTableViewDelegate {
>   var tableView: ((NSTableView, viewFor: NSTableColumn, row: Int) -> 
> NSView?)? = {
>     … except you can’t refer to self in here unless you make it lazy ...
>   }
> }
> 
> d) We’ve seriously considered eliminating argument labels on function types, 
> because they’re a complexity in the type system that doesn’t serve much of a 
> purpose.
> 
> One could perhaps work around (a), (b), and (d) by allowing compound 
> (function-like) names like tableView(_:viewFor:row:) for properties, and work 
> around (c) by allowing a method to satisfy the requirement for a read-only 
> property, but at this point you’ve invented more language hacks than the 
> existing @objc-only optional requirements. So, I don’t think there is a 
> solution here.
> 
> Proposed Solution: Caller-side default implementations
> 
> Default implementations and optional requirements differ most on the caller 
> side. For example, let’s use NSTableView delegate as it’s imported today:
> 
> func useDelegate(delegate: NSTableViewDelegate) {
>   if let getView = delegate.tableView(_:viewFor:row:) { // since the 
> requirement is optional, a reference to the method produces a value of 
> optional function type
>     // I can call getView here
>   }
> 
>   if let getHeight = delegate.tableView(_:heightOfRow:) {
>     // I can call getHeight here
>   }
> }
> 
> 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
> }
> 
> 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@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to