> Protocol requirements with default (no-op) implementations already satisfy 
> that design goal, no?

Kind of. If I may steelman* optional members for a moment...

In cases where a default implementation would do, the default implementation 
will usually also be the behavior you want for a nil instance, but there's no 
convenient way to share logic between the two. For example, consider this:

        protocol UITableViewDelegate {
                ...
                func tableView(_ tableView: UITableView, 
heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
        }
        extension UITableViewDelegate {
                func tableView(_ tableView: UITableView, 
heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
                        return tableView.rowHeight
                }
        }
        
        class UITableView {
                ...
                private func addRow(at indexPath: NSIndexPath) {
                        ...
                        cell.size.height = delegate?.tableView(self, 
heightForRowAtIndexPath: indexPath) ?? rowHeight
                        ...
                }
                ...

You have to duplicate the default logic both in the default implementation and 
at the call site, but there is no convenient way to share it—the extension 
method can't call into an expression at some call site, and contrarily the call 
site can't invoke the default logic from the extension.

If the method were optional, then optional chaining would solve this problem 
for us:

        protocol UITableViewDelegate {
                ...
                optional func tableView(_ tableView: UITableView, 
heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
        }
        
        class UITableView {
                ...
                private func addRow(at indexPath: NSIndexPath) {
                        ...
                        cell.size.height = delegate?.tableView?(self, 
heightForRowAtIndexPath: indexPath) ?? rowHeight
                        ...
                }
                ...

This way, there is only one source of default behavior: the call site.

I'm also concerned by the thought of just how many sub-protocols we might end 
up with. When I try to fully factor NSTableViewDelegate (as it currently exists 
in the headers), I end up with ten protocols:

        NSTableViewDelegate
                - tableView:willDisplayCell:forTableColumn:row:

        NSTableViewLayoutDelegate: NSTableViewDelegate
                - tableView:heightOfRow:

        NSTableViewRowSelectionDelegate: NSTableViewDelegate
                - tableView:shouldSelectRow:
                - selectionShouldChangeInTableView:
                - tableViewSelectionIsChanging:
                - tableViewSelectionDidChange:
                - tableView:shouldTrackCell:forTableColumn:row: (10.5)
                - tableView:selectionIndexesForProposedSelection: (10.5)

        NSTableViewTypeSelectDelegate: NSTableViewDelegate (10.5)
                - tableView:typeSelectStringForTableColumn:row:
                - tableView:nextTypeSelectMatchFromRow:toRow:forString:
                - tableView:shouldTypeSelectForEvent:withCurrentSearchString:

        NSTableViewToolTipDelegate: NSTableViewDelegate
                - tableView:toolTipForCell:rect:tableColumn:row:mouseLocation:

        NSTableViewColumnDelegate: NSTableViewDelegate
                - tableView:shouldEditTableColumn:row:
                - tableView:shouldSelectTableColumn:
                - tableView:mouseDownInHeaderOfTableColumn:
                - tableView:didClickTableColumn:
                - tableView:didDragTableColumn:
                - tableViewColumnDidMove:
                - tableViewColumnDidResize:
                - tableView:sizeToFitWidthOfColumn: (10.6)
                - tableView:shouldReorderColumn:toColumn: (10.6)

        NSTableViewCellExpansionDelegate: NSTableViewDelegate (10.5)
                - tableView:shouldShowCellExpansionForTableColumn:row:
        
        NSTableViewCustomCellDelegate: NSTableViewDelegate (10.5)
                - tableView:dataCellForTableColumn:row:
                - tableView:isGroupRow:

        NSTableViewCellViewDelegate: NSTableViewDelegate (10.7)
                - tableView:viewForTableColumn:row:

        NSTableViewRowViewDelegate: NSTableViewDelegate (10.7)
                - tableView:rowViewForRow:
                - tableView:didAddRowView:forRow:
                - tableView:didRemoveRowView:forRow:
                - tableView:rowActionsForRow:edge: (10.11)

Some of these are probably unnecessary; they could be merged into 
NSTableViewDelegate and given default implementations. But at least a few of 
them would be very much needed. Would users be able to navigate this mess? 
Would they discover the features tucked away in sub-protocols? I'm just not 
sure.

And of course the safety issues that make optional protocol members dangerous 
in Objective-C don't exist in Swift. Swift will force you to test for the 
presence of an optional member; you can't carelessly call one.

(Incidentally, resilience might also benefit from supporting optional protocol 
members and adding a `public(optional)` feature which made all call sites 
outside the resilience domain treat all members as optional. You could then 
mark protocols meant to be called only by clients inside the resilience 
domain—like data sources and delegates—with `public(optional)` and gain the 
ability to delete obsolete members.)



* Steelmanning is the opposite of strawmanning.

-- 
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to