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