[swift-evolution] [Proposal] Make optional protocol methods first class citizens

Brent Royal-Gordon brent at architechies.com
Fri Apr 1 19:37:59 CDT 2016


> 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



More information about the swift-evolution mailing list