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

Zach Waldowski zach at waldowski.me
Fri Apr 8 10:05:34 CDT 2016


Table view semantics were discussed at length on a prior version of the
thread. That pattern is less than ideal; it essentially creates magic
behavior that's only described by documentation… or, worse, completely
forgotten about in documentation; something changing from version to
version of the framework; etc. I can not tell you how many times this
has tripped up members of my teams. Over in Cocoa proper, similar
behavior changes also arise (usually performance optimizations) from
whether or not you *override* a method, and it's even more confusing.
 
Such a practice should not be a cornerstone of a modern language; as
discussed in the prior thread, the different semantics of the measuring
methods (the current return values, as well as the implicit one from not
overriding) should be captured explicitly in an enum, with a clear
default return value. This is in line with the spirit of Swift. Your API
contract with the user is clear, and the introduction of default
implementations is versioned as a matter of public API.
 
It's interesting that you use the phrase "customization points". Our
text for teaching protocol extensions in Swift uses it heavily - that
*is* the behavior of protocols with default implementations in Swift
today. You delegate something out, but give it a default implementation
with logic you specify. That's a customization point, too.
 
Cheers!
Zachary Waldowski
zach at waldowski.me
 
 
On Fri, Apr 8, 2016, at 08:47 AM, Jonathan Hull via swift-evolution wrote:
> Interesting proposal, but I wanted to mention a couple of potential
> issues off the top of my head.  I know when I was using optional
> requirements in Objective C, I would often use the presence/lack of
> the method (not just whether it returned nil) in the logic of my
> program.  I used the presence of a method as a way for the implementor
> of a delegate to naturally communicate whether they wanted a more
> advanced feature.  The absence of the method itself is information
> which can be utilized, not just whether it returns nil, and I believe
> that is part of what people are asking for when they say they want
> optional methods in Swift.
>
>
> Let me try to give a simplified example which I am not sure how you
> would work around in this proposal:
>
> Let’s say there is a datasource protocol which optionally asks for an
> image associated with a particular piece of data (imagine a table or
> collection view type custom control).  If the method is not
> implemented in the data source, then a different view is shown for
> each data point that doesn’t have a place for images at all.  If the
> method is implemented, but returns nil, then a default image is used
> as a placeholder instead (in a view which has a place for images).
>
> tl;dr: Optional methods are often used as customization points,
> and the methods, if implemented, may also have another meaning/use
> for nil.
>
>
> Similarly, a different back-end implementation may be used in the case
> where an optional method is not implemented.  Let’s say you have
> something like a tableview with an optional method giving rowHeights.
> If that method is unimplemented, it is possible to have a much more
> efficient layout algorithm… and in some cases, you may check for the
> existence of the optional method when the delegate is set, and swap
> out a different layout object based on which customizations are needed
> (and again nil might mean that a height/etc... should be automatically
> calculated).  This is the ability I miss the most.
>
>
> Not saying the proposal is unworkable, just wanted to add some food
> for thought.  I know I really miss optional methods in Swift.  In some
> areas Swift is a lot more powerful, but there are lots of things I
> used to do in Obj C that I haven’t figured out how to do in Swift yet
> (if they are even possible).  I am kind of disturbed by the
> trend/desire to get rid of the smalltalk-ness, as opposed to finding
> new and safer ways to support that flexibility/expressiveness.  I
> would really like to see swift deliver on it’s promise of being a more
> modern alternative to ObjC (which it isn’t yet, IMHO) instead of just
> a more modern alternative to C++/Java.
>
> Thanks,
> Jon
>
>> 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 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/20160408/1281d02b/attachment.html>


More information about the swift-evolution mailing list