[Idea] How to eliminate 'optional' protocol requirements

I’m not sure whether you’ve read the conclusion of my mail since
you’ve only commented on the introductory part. In the conclusion I
wrote that a possible approach for the replacement of ObjC-style
optional protocol methods would be:

1) the default implementation of a protocol method must be defined in
the protocol (so just like in native Swift protocols today).

? They can and must be defined in protocol extensions today.

I know.

2) we add a way for a protocol provider to check whether the protocol
adopter has provided an “override” of the default method.

I object to this part.

You object why? I do understand why you object to the ObjC model since there is not necessarily an implementation of the protocol method and thus the protocol provider has to guard every call with an existence check. But in this model here we would be guaranteed that there would be an implementation of the protocol method and thus guarding the call wouldn’t be necessary.

3) we improve the Xcode interface generator so that it clearly shows
whether a protocol method comes with a default or whether it doesn’t.

Obvious goodness, long overdue.

(1) should address your main concern since it would guarantee that the
protocol provider is always able to call the protocol method without
having to do any checks. (2) would address the main concern of
protocol providers who need to guarantee that the protocol using type
achieves a certain minimum speed and does not use more than a certain
amount of memory for its internal book-keeping.

I don't how (2) can possibly help with that.

It helps because it allows the protocol provider to *understand* whether the protocol adopter is actually using a certain feature or isn’t. Here is the table view example again:

func useDelegate(delegate: NSTableViewDelegate) {

  if has_override(delegate, tableView(_:, heightForRow:)) {
     // call tableViewNumberOfRows() on the delegate
     // allocate the geometry cache (1 entry per row)
     // call tableView(_:, heightForRow:) for each row
  } else {
    // nothing to do here since here all rows have the same height
   }
}

Note that has_override() is just a placeholder syntax because I’ve not had a good idea yet of how to express this in a Swiftier way.

In this example the table view is able to check whether the protocol adopter has actually “overriden” the default implementation of tableView(_:, heightForRow:). If the adopter did, then the table view knows that the adopter wants variable row heights and thus the table view can now create a cache of row heights and it can enable the layouting code that knows how to lay out rows with different heights. If however the adopter did not provide its own implementation of this method then the table view does not need to create a geometry cache and it can switch over to the simpler fixed-row-height layout code. The reason why we want to cache the row heights in the table view is because computing those heights can be nontrivial and the layout code needs to access those height values in every layoutSubviews() call. And layoutSubviews() is invoked 60 times per second while the user is scrolling. Also keep in mind that, if we would not cache the row heights, then the row height computation would end up competing for CPU cycles with the code that properly configures the views for each row.

Without the ability to do this check on the protocol provider side, we are forced to increase the API surface so that the protocol adopter can explicitly tell us which layouting model he wants. But this also means that the protocol adopter now has to remember that he needs to configure the layouting option correctly in order to get a working and efficiently working table view. So the end result would be a table view that’s hard to use correctly.

Regards,

Dietmar Planitzer

···

On Apr 10, 2016, at 11:46, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:
on Sun Apr 10 2016, Dietmar Planitzer <swift-evolution@swift.org> wrote:

(3) is important because it would fix one of the many aspects that
make Swift protocols confusing for people who are new to the language.

Finally, let me restate that the goal should really be that we
completely remove the syntactical differences between @objc and native
Swift protocols. There should be one concept of protocol in Swift and
we should be able to cover the use cases of formal and informal ObjC
Protocols with them. The use case of formal protocols is already
covered today. The use case of informal protocols could be covered
with the approach above.

So is this an approach that would be acceptable to you?

Regards,

Dietmar Planitzer

On Apr 10, 2016, at 10:29, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

on Fri Apr 08 2016, Dietmar Planitzer <swift-evolution@swift.org> wrote:

The biggest missing part with this model is that we are still not able
to enable macro-level optimizations in the delegating type by checking
whether the delegate does provide his own implementation of an
optional method or doesn’t. However, this is an important advantage of
the ObjC model that we should not lose.

Maybe it’s time to take a big step back and ignore the question of how
to implement things for a moment and to instead focus on the question
of what the conceptual differences are between ObjC protocols with
optional methods and Swift protocols with default
implementations. There are two relevant viewpoints here:

1) From the viewpoint of a protocol adaptor:

ObjC:

1a) adopter may provide his own implementation of the protocol method,
but he is no required to.

1b) adopter can see in the protocol declaration for which methods he
must provide an implementation. Those methods do not have the
“optional” keyword in front of them while optional methods do.

Swift:

1c) same as (1a).

1d) opening a binary-only Swift file in Xcode with a protocol
definition in it which contains methods with default implementations
will not give any indication of which method has a default
implementation and which doesn’t. It’s only possible to see a
difference on the syntax level if you have access to the sources.

This visibility problem is something we aim to correct in Swift, but
that is a question of syntax, documentation, and “header” generation,
and really orthogonal to what's fundamental about “optional
requirements:”

1. The ability to “conform” to the protocol without a
default implementation of the requirement have been provided
anywhere.

2. The ability to dynamically query whether a type actually provides the
requirement.

Both of these “features,” IMO, are actually bugs.

So from the viewpoint of the protocol adopter, there isn’t much of a
difference. The only relevant difference is that its always possible
in ObjC to tell whether a protocol method must be implemented by the
adopter or whether a method already has a default behavior. We
shouldn’t actually have to change anything on the syntax-level in
Swift to fix this problem. It should be sufficient to improve the
Swift interface generator in Xcode so that it gives an indication
whether a protocol method has a default implementation or doesn’t. Eg
if we want to ensure that the generated interface is valid syntax then
we could do this:

protocol Foo {

func void bar() -> Int /* has default */

}

or if we say that it is fine that the generated interface is not valid
syntax (I think it already shows "= default” for function arguments
with a default value which I don’t think is valid syntax), then we
could do this:

protocol Foo {

func void bar() -> Int {…}

}

Now on to the other side of the equation.

2) From the viewpoint of the protocol provider (the person who defines
the protocol and the type that will invoke the protocol methods):

ObjC:

2a) provider has freedom in deciding where to put the default
implementation and he can put the default implementation in a single
place or spread it out if necessary over multiple places. So has the
freedom to choose whatever makes the most sense for the problem at
hand.

But freedom for protocol implementors reduces predictability for protocol
clients and adopters.

2b) provider can detect whether the adopter provides his own protocol
method implementation without compromising the definition of the
protocol (compromising here means making return values optional when
they should not be optional based on the natural definition of the
API). This enables the provider to implement macro-level optimizations
(eg table view can understand whether fixed or variable row heights
are desired).

Swift:

2c) provider is forced to put the default implementation in a specific
place.

2d) provider has no way to detect whether the adopter has provided his
own implementation of the protocol method.

I do think that (2a) would be nice to have but we can probably do
without it if it helps us to make progress with this topic. However,
the ability to detect whether a protocol adopter provides his own
implementation of a protocol method which comes with a default is a
useful and important feature which helps us in optimizing the
implementation of types and which allows us to keep the API surface
smaller than it would be without this ability. Just go and compare eg
UITableView to the Android ListView / RecyclerView to see the
consequences of not having that ability and how it inflates the API
surface (and keep in mind that the Android equivalents provide a
fraction of the UITableView functionality).

The important point about (2b) is actually that we are able to detect
whether an “override” (I’ll just call this overriding for now) of the
default implementation exists or does not exist.

IMO the important point about (2b) is that it leads to protocol designs
that create work and complexity for clients of the protocol, and being
constrained to make your protocol work so that clients don't have to do
these kinds of checks is a Very Good Thing™.

In ObjC we make this distinction by checking whether an implementation
of the method exists at all. But we don’t have to do it that way. An
alternative approach could be based on a check that sees whether the
dispatch table of the delegate contains a pointer to the default
implementation of the protocol method or to some other method. So
conceptually what we want is an operation like this:

func void useDelegate(delegate: NSTableViewDelegate) {

if has_override(delegate, tableView(_:, heightOfRow:)) { // ask the
delegate how many rows it has // allocate the geometry cache // fill
in the geometry cache by calling tableView(_:, heightForRow:) for each
row } else { // nothing to do here } }

Which would get the job done but doesn’t look good. Maybe someone has
a better idea of how the syntax such an operator could look.

So my point here is that what we care about is the ability to detect
whether the adopter provides an implementation of a protocol method
which comes with a default implementation. The point is not that Swift
protocols should work the exact same way that ObjC protocols have been
working under the hood. But I do think that we want to eventually get
to a point where the @objc attribute disappears and that we get a
truly unified language on the syntactical level. An approach where:

I) we accept that the default behavior of a protocol method has to be
provided by the protocol itself

II) the language is extended with a mechanism that makes it possible
for a protocol provider to detect whether the adopter has “overridden”
the default implementation

III) we improve the Xcode Swift interface generator so that it gives a
clear indication whether a protocol method does come with a default
implementation

would give us all the relevant advantages of ObjC-style optional
protocol methods and it should allow us to create a unified syntax
where there is no longer a visible difference between an optional
protocol method that was imported from ObjC and a native Swift
protocol with default implementations.

Regards,

Dietmar Planitzer

On Apr 7, 2016, at 17: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

--
Dave

_______________________________________________
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

--
Dave

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Sent from my iPad

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.

To me, compound names for closure properties and satisfying property requirements with methods aren't hacks, they're missing features we ought to support anyway. I strongly prefer implementing those over your proposed solution. It sounds to me like a lot of people using optional protocol requirements *want* the locality of control flow visible in the caller, for optimization or other purposes, and your proposed solution makes this incredibly obscure and magical.

Do you have the same thought for optional closure properties? If so and heightForRow was an optional closure property it would satisfy all use cases elegantly. It could have a default implementation that returns nil. When non-uniform heights are required a normal method implementation can be provided. Delegates that have uniform row heights some of the time, but not all of the time, would also be supported by implementing the property.

There are still some issues here:

1) It doesn’t handle optional read/write properties at all, because the setter signature would be different. Perhaps some future lens design would make this possible. For now, the workaround would have to be importing the setter as a second optional closure property, I guess. (The current system is similarly broken).

I was only thinking about methods, not properties. :) How common are optional, writeable property requirements? I don’t have a good guess off the top of my head, but my hunch is that they are not that common.

They are *very* rare. Aside from UITextInputTraits <UITextInputTraits | Apple Developer Documentation, I see three in OS X and four in iOS.

2) For an @objc protocol, you won’t actually be able to fully implement the optional closure property with a property of optional type, because “return nil” in the getter is not the same as “-respondsToSelector: returns false”. Indeed, the getter result type/setter parameter type should be non-optional, so we would (at best) need a special rule that optional closure properties of @objc protocols can only be implemented by non-optional properties of closure type or by methods.

This is related to why I asked about feasibility. I know that “return nil” is not that same as a “respondsToSelector:” implementation that returns false if the property was implemented to return nil. Some magic would need to handle that translation to make it work with existing Objective-C protocols. This would automate what I have done in Objective-C several times by implementing respondsToSelector manually to hide protocol method implementations when necessary.

The advantage of going this route is that Swift implementations of the legacy Cocoa protocols will still function as expected in Cocoa while fitting the Swift model much better than optional protocol requirements.

Both Joe’s suggestion and my proposal need hackery to do the right thing for Objective-C interoperability, and both are feasible. I feel like my proposal is more honest about the hackery going on :)

  - Doug

···

On Apr 15, 2016, at 3:55 PM, Matthew Johnson <matthew@anandabits.com> wrote:

On Apr 13, 2016, at 11:42 AM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

On Apr 11, 2016, at 10:30 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Apr 11, 2016, at 12:15 PM, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Apr 7, 2016, at 5:12 PM, Douglas Gregor via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Sent from my iPad

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.

To me, compound names for closure properties and satisfying property requirements with methods aren't hacks, they're missing features we ought to support anyway. I strongly prefer implementing those over your proposed solution. It sounds to me like a lot of people using optional protocol requirements *want* the locality of control flow visible in the caller, for optimization or other purposes, and your proposed solution makes this incredibly obscure and magical.

Do you have the same thought for optional closure properties? If so and heightForRow was an optional closure property it would satisfy all use cases elegantly. It could have a default implementation that returns nil. When non-uniform heights are required a normal method implementation can be provided. Delegates that have uniform row heights some of the time, but not all of the time, would also be supported by implementing the property.

There are still some issues here:

1) It doesn’t handle optional read/write properties at all, because the setter signature would be different. Perhaps some future lens design would make this possible. For now, the workaround would have to be importing the setter as a second optional closure property, I guess. (The current system is similarly broken).

I was only thinking about methods, not properties. :) How common are optional, writeable property requirements? I don’t have a good guess off the top of my head, but my hunch is that they are not that common.

They are *very* rare. Aside from UITextInputTraits, I see three in OS X and four in iOS.

2) For an @objc protocol, you won’t actually be able to fully implement the optional closure property with a property of optional type, because “return nil” in the getter is not the same as “-respondsToSelector: returns false”. Indeed, the getter result type/setter parameter type should be non-optional, so we would (at best) need a special rule that optional closure properties of @objc protocols can only be implemented by non-optional properties of closure type or by methods.

This is related to why I asked about feasibility. I know that “return nil” is not that same as a “respondsToSelector:” implementation that returns false if the property was implemented to return nil. Some magic would need to handle that translation to make it work with existing Objective-C protocols. This would automate what I have done in Objective-C several times by implementing respondsToSelector manually to hide protocol method implementations when necessary.

The advantage of going this route is that Swift implementations of the legacy Cocoa protocols will still function as expected in Cocoa while fitting the Swift model much better than optional protocol requirements.

Both Joe’s suggestion and my proposal need hackery to do the right thing for Objective-C interoperability, and both are feasible. I feel like my proposal is more honest about the hackery going on :)

Hmm. I agree that it's a little bit of hackers, but I don't think it's really dishonest to translate "return nil" into "respondsToSelector:" false and more than it is to make any other mapping from one system to another.

The main reason I prefer that approach is that it enables functionality that has been occasionally useful in Objective-C and is not otherwise possible in Swift - namely the ability to implement the protocol in a general purpose class and make a decision at initialization time whether you need a particular feature (such as dynamic row sizing) or not. Your approach doesn't allow for this.

···

Sent from my iPad

On Apr 15, 2016, at 6:03 PM, Douglas Gregor <dgregor@apple.com> wrote:

On Apr 15, 2016, at 3:55 PM, Matthew Johnson <matthew@anandabits.com> wrote:

On Apr 13, 2016, at 11:42 AM, Douglas Gregor <dgregor@apple.com> wrote:
On Apr 11, 2016, at 10:30 AM, Matthew Johnson <matthew@anandabits.com> wrote:

On Apr 11, 2016, at 12:15 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:
On Apr 7, 2016, at 5:12 PM, Douglas Gregor via swift-evolution <swift-evolution@swift.org> wrote:

  - Doug

Since there's a hack involved more or less any way you slice it, the most honest way to admit to the hackery might be to keep the status quo, but just demote "optional" to an "@objcOptional" attribute or something similar that keeps the current behavior.

-Joe

···

On Apr 15, 2016, at 4:03 PM, Douglas Gregor <dgregor@apple.com> wrote:

Both Joe’s suggestion and my proposal need hackery to do the right thing for Objective-C interoperability, and both are feasible. I feel like my proposal is more honest about the hackery going on :)

I’m not sure whether you’ve read the conclusion of my mail since
you’ve only commented on the introductory part. In the conclusion I
wrote that a possible approach for the replacement of ObjC-style
optional protocol methods would be:

1) the default implementation of a protocol method must be defined

in

the protocol (so just like in native Swift protocols today).

? They can and must be defined in protocol extensions today.

I know.

2) we add a way for a protocol provider to check whether the

protocol

adopter has provided an “override” of the default method.

I object to this part.

You object why? I do understand why you object to the ObjC model since
there is not necessarily an implementation of the protocol method and
thus the protocol provider has to guard every call with an existence
check. But in this model here we would be guaranteed that there would
be an implementation of the protocol method and thus guarding the call
wouldn’t be necessary.

Because it's a needless complication that will encourage protocol and
algorithm designers to create inefficient programs because they know the
user can fall back on this hack. Nobody thinks that classes need the
ability to check whether a given method is overridden. Why should this
be needed for protocols?

3) we improve the Xcode interface generator so that it clearly shows
whether a protocol method comes with a default or whether it
doesn’t.

Obvious goodness, long overdue.

(1) should address your main concern since it would guarantee that
the protocol provider is always able to call the protocol method
without having to do any checks. (2) would address the main concern
of protocol providers who need to guarantee that the protocol using
type achieves a certain minimum speed and does not use more than a
certain amount of memory for its internal book-keeping.

I don't how (2) can possibly help with that.

It helps because it allows the protocol provider to *understand*
whether the protocol adopter is actually using a certain feature or
isn’t.

If they need to understand that, they can make the indicator of that
fact a separate protocol requirement.

Here is the table view example again:

func useDelegate(delegate: NSTableViewDelegate) {

  if has_override(delegate, tableView(_:, heightForRow:)) {
     // call tableViewNumberOfRows() on the delegate
     // allocate the geometry cache (1 entry per row)
     // call tableView(_:, heightForRow:) for each row
  } else {
    // nothing to do here since here all rows have the same height
   }
}

Note that has_override() is just a placeholder syntax because I’ve not
had a good idea yet of how to express this in a Swiftier way.

   if delegate.hasVariableSizedRows { ... }

   if !(delegate is NSUniformTableViewDelegate) { ... }

etc.

In this example the table view is able to check whether the protocol
adopter has actually “overriden” the default implementation of
tableView(_:, heightForRow:).

Which, IMO, is a terrible way to indicate that a view has variable row
heights. It's indirect and maybe even inaccurate (I can imagine a table
view that is uniform and has its height set up once at construction
time, therefore it needs to override heightForRow).

If the adopter did, then the table view knows that the adopter wants
variable row heights and thus the table view can now create a cache of
row heights and it can enable the layouting code that knows how to lay
out rows with different heights. If however the adopter did not
provide its own implementation of this method then the table view does
not need to create a geometry cache and it can switch over to the
simpler fixed-row-height layout code. The reason why we want to cache
the row heights in the table view is because computing those heights
can be nontrivial and the layout code needs to access those height
values in every layoutSubviews() call. And layoutSubviews() is invoked
60 times per second while the user is scrolling. Also keep in mind
that, if we would not cache the row heights, then the row height
computation would end up competing for CPU cycles with the code that
properly configures the views for each row.

Without the ability to do this check on the protocol provider side, we
are forced to increase the API surface so that the protocol adopter
can explicitly tell us which layouting model he wants.

That's exactly what one should do. If layout model is an important
feature, the adopter should be explicit abou tit.

···

on Sun Apr 10 2016, Dietmar Planitzer <dplanitzer-AT-q.com> wrote:

On Apr 10, 2016, at 11:46, Dave Abrahams via swift-evolution > <swift-evolution@swift.org> wrote:
on Sun Apr 10 2016, Dietmar Planitzer <swift-evolution@swift.org> > wrote:

But this also means that the protocol adopter now has to remember that
he needs to configure the layouting option correctly in order to get a
working and efficiently working table view. So the end result would be
a table view that’s hard to use correctly.

Regards,

Dietmar Planitzer

(3) is important because it would fix one of the many aspects that
make Swift protocols confusing for people who are new to the

language.

Finally, let me restate that the goal should really be that we
completely remove the syntactical differences between @objc and

native

Swift protocols. There should be one concept of protocol in Swift

and

we should be able to cover the use cases of formal and informal

ObjC

Protocols with them. The use case of formal protocols is already
covered today. The use case of informal protocols could be covered
with the approach above.

So is this an approach that would be acceptable to you?

Regards,

Dietmar Planitzer

On Apr 10, 2016, at 10:29, Dave Abrahams via swift-evolution > <swift-evolution@swift.org> wrote:

on Fri Apr 08 2016, Dietmar Planitzer <swift-evolution@swift.org> > wrote:

The biggest missing part with this model is that we are still not

able

to enable macro-level optimizations in the delegating type by

checking

whether the delegate does provide his own implementation of an
optional method or doesn’t. However, this is an important

advantage of

the ObjC model that we should not lose.

Maybe it’s time to take a big step back and ignore the question

of how

to implement things for a moment and to instead focus on the

question

of what the conceptual differences are between ObjC protocols

with

optional methods and Swift protocols with default
implementations. There are two relevant viewpoints here:

1) From the viewpoint of a protocol adaptor:

ObjC:

1a) adopter may provide his own implementation of the protocol

method,

but he is no required to.

1b) adopter can see in the protocol declaration for which methods

he

must provide an implementation. Those methods do not have the
“optional” keyword in front of them while optional methods do.

Swift:

1c) same as (1a).

1d) opening a binary-only Swift file in Xcode with a protocol
definition in it which contains methods with default

implementations

will not give any indication of which method has a default
implementation and which doesn’t. It’s only possible to see a
difference on the syntax level if you have access to the sources.

This visibility problem is something we aim to correct in Swift,

but

that is a question of syntax, documentation, and “header”

generation,

and really orthogonal to what's fundamental about “optional
requirements:”

1. The ability to “conform” to the protocol without a
default implementation of the requirement have been provided
anywhere.

2. The ability to dynamically query whether a type actually

provides the

requirement.

Both of these “features,” IMO, are actually bugs.

So from the viewpoint of the protocol adopter, there isn’t much

of a

difference. The only relevant difference is that its always

possible

in ObjC to tell whether a protocol method must be implemented by

the

adopter or whether a method already has a default behavior. We
shouldn’t actually have to change anything on the syntax-level in
Swift to fix this problem. It should be sufficient to improve the
Swift interface generator in Xcode so that it gives an indication
whether a protocol method has a default implementation or

doesn’t. Eg

if we want to ensure that the generated interface is valid syntax

then

we could do this:

protocol Foo {

func void bar() -> Int /* has default */

}

or if we say that it is fine that the generated interface is not

valid

syntax (I think it already shows "= default” for function

arguments

with a default value which I don’t think is valid syntax), then

we

could do this:

protocol Foo {

func void bar() -> Int {…}

}

Now on to the other side of the equation.

2) From the viewpoint of the protocol provider (the person who

defines

the protocol and the type that will invoke the protocol methods):

ObjC:

2a) provider has freedom in deciding where to put the default
implementation and he can put the default implementation in a

single

place or spread it out if necessary over multiple places. So has

the

freedom to choose whatever makes the most sense for the problem

at

hand.

But freedom for protocol implementors reduces predictability for

protocol

clients and adopters.

2b) provider can detect whether the adopter provides his own

protocol

method implementation without compromising the definition of the
protocol (compromising here means making return values optional

when

they should not be optional based on the natural definition of

the

API). This enables the provider to implement macro-level

optimizations

(eg table view can understand whether fixed or variable row

heights

are desired).

Swift:

2c) provider is forced to put the default implementation in a

specific

place.

2d) provider has no way to detect whether the adopter has

provided his

own implementation of the protocol method.

I do think that (2a) would be nice to have but we can probably do
without it if it helps us to make progress with this

topic. However,

the ability to detect whether a protocol adopter provides his own
implementation of a protocol method which comes with a default is

a

useful and important feature which helps us in optimizing the
implementation of types and which allows us to keep the API

surface

smaller than it would be without this ability. Just go and

compare eg

UITableView to the Android ListView / RecyclerView to see the
consequences of not having that ability and how it inflates the

API

surface (and keep in mind that the Android equivalents provide a
fraction of the UITableView functionality).

The important point about (2b) is actually that we are able to

detect

whether an “override” (I’ll just call this overriding for now) of

the

default implementation exists or does not exist.

IMO the important point about (2b) is that it leads to protocol

designs

that create work and complexity for clients of the protocol, and

being

constrained to make your protocol work so that clients don't have

to do

these kinds of checks is a Very Good Thing™.

In ObjC we make this distinction by checking whether an

implementation

of the method exists at all. But we don’t have to do it that

way. An

alternative approach could be based on a check that sees whether

the

dispatch table of the delegate contains a pointer to the default
implementation of the protocol method or to some other method. So
conceptually what we want is an operation like this:

func void useDelegate(delegate: NSTableViewDelegate) {

if has_override(delegate, tableView(_:, heightOfRow:)) { // ask

the

delegate how many rows it has // allocate the geometry cache //

fill

in the geometry cache by calling tableView(_:, heightForRow:) for

each

row } else { // nothing to do here } }

Which would get the job done but doesn’t look good. Maybe someone

has

a better idea of how the syntax such an operator could look.

So my point here is that what we care about is the ability to

detect

whether the adopter provides an implementation of a protocol

method

which comes with a default implementation. The point is not that

Swift

protocols should work the exact same way that ObjC protocols have

been

working under the hood. But I do think that we want to eventually

get

to a point where the @objc attribute disappears and that we get a
truly unified language on the syntactical level. An approach

where:

I) we accept that the default behavior of a protocol method has

to be

provided by the protocol itself

II) the language is extended with a mechanism that makes it

possible

for a protocol provider to detect whether the adopter has

“overridden”

the default implementation

III) we improve the Xcode Swift interface generator so that it

gives a

clear indication whether a protocol method does come with a

default

implementation

would give us all the relevant advantages of ObjC-style optional
protocol methods and it should allow us to create a unified

syntax

where there is no longer a visible difference between an optional
protocol method that was imported from ObjC and a native Swift
protocol with default implementations.

Regards,

Dietmar Planitzer

On Apr 7, 2016, at 17: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

--
Dave

_______________________________________________
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

--
Dave

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
Dave

My approach requires you to use a different design—either split into multiple protocols (which is probably the best answer in most of these cases) or introduce a different kind of API contract. My claim is that these solutions are more natural in Swift than “check if a particular requirement was actually implemented”.

  - Doug

···

On Apr 15, 2016, at 4:15 PM, Matthew Johnson <matthew@anandabits.com> wrote:

Sent from my iPad

On Apr 15, 2016, at 6:03 PM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

On Apr 15, 2016, at 3:55 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Apr 13, 2016, at 11:42 AM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

On Apr 11, 2016, at 10:30 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

Sent from my iPad

On Apr 11, 2016, at 12:15 PM, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Apr 7, 2016, at 5:12 PM, Douglas Gregor via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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.

To me, compound names for closure properties and satisfying property requirements with methods aren't hacks, they're missing features we ought to support anyway. I strongly prefer implementing those over your proposed solution. It sounds to me like a lot of people using optional protocol requirements *want* the locality of control flow visible in the caller, for optimization or other purposes, and your proposed solution makes this incredibly obscure and magical.

Do you have the same thought for optional closure properties? If so and heightForRow was an optional closure property it would satisfy all use cases elegantly. It could have a default implementation that returns nil. When non-uniform heights are required a normal method implementation can be provided. Delegates that have uniform row heights some of the time, but not all of the time, would also be supported by implementing the property.

There are still some issues here:

1) It doesn’t handle optional read/write properties at all, because the setter signature would be different. Perhaps some future lens design would make this possible. For now, the workaround would have to be importing the setter as a second optional closure property, I guess. (The current system is similarly broken).

I was only thinking about methods, not properties. :) How common are optional, writeable property requirements? I don’t have a good guess off the top of my head, but my hunch is that they are not that common.

They are *very* rare. Aside from UITextInputTraits <UITextInputTraits | Apple Developer Documentation, I see three in OS X and four in iOS.

2) For an @objc protocol, you won’t actually be able to fully implement the optional closure property with a property of optional type, because “return nil” in the getter is not the same as “-respondsToSelector: returns false”. Indeed, the getter result type/setter parameter type should be non-optional, so we would (at best) need a special rule that optional closure properties of @objc protocols can only be implemented by non-optional properties of closure type or by methods.

This is related to why I asked about feasibility. I know that “return nil” is not that same as a “respondsToSelector:” implementation that returns false if the property was implemented to return nil. Some magic would need to handle that translation to make it work with existing Objective-C protocols. This would automate what I have done in Objective-C several times by implementing respondsToSelector manually to hide protocol method implementations when necessary.

The advantage of going this route is that Swift implementations of the legacy Cocoa protocols will still function as expected in Cocoa while fitting the Swift model much better than optional protocol requirements.

Both Joe’s suggestion and my proposal need hackery to do the right thing for Objective-C interoperability, and both are feasible. I feel like my proposal is more honest about the hackery going on :)

Hmm. I agree that it's a little bit of hackers, but I don't think it's really dishonest to translate "return nil" into "respondsToSelector:" false and more than it is to make any other mapping from one system to another.

The main reason I prefer that approach is that it enables functionality that has been occasionally useful in Objective-C and is not otherwise possible in Swift - namely the ability to implement the protocol in a general purpose class and make a decision at initialization time whether you need a particular feature (such as dynamic row sizing) or not. Your approach doesn't allow for this.

Sent from my iPad

Sent from my iPad

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.

To me, compound names for closure properties and satisfying property requirements with methods aren't hacks, they're missing features we ought to support anyway. I strongly prefer implementing those over your proposed solution. It sounds to me like a lot of people using optional protocol requirements *want* the locality of control flow visible in the caller, for optimization or other purposes, and your proposed solution makes this incredibly obscure and magical.

Do you have the same thought for optional closure properties? If so and heightForRow was an optional closure property it would satisfy all use cases elegantly. It could have a default implementation that returns nil. When non-uniform heights are required a normal method implementation can be provided. Delegates that have uniform row heights some of the time, but not all of the time, would also be supported by implementing the property.

There are still some issues here:

1) It doesn’t handle optional read/write properties at all, because the setter signature would be different. Perhaps some future lens design would make this possible. For now, the workaround would have to be importing the setter as a second optional closure property, I guess. (The current system is similarly broken).

I was only thinking about methods, not properties. :) How common are optional, writeable property requirements? I don’t have a good guess off the top of my head, but my hunch is that they are not that common.

They are *very* rare. Aside from UITextInputTraits, I see three in OS X and four in iOS.

2) For an @objc protocol, you won’t actually be able to fully implement the optional closure property with a property of optional type, because “return nil” in the getter is not the same as “-respondsToSelector: returns false”. Indeed, the getter result type/setter parameter type should be non-optional, so we would (at best) need a special rule that optional closure properties of @objc protocols can only be implemented by non-optional properties of closure type or by methods.

This is related to why I asked about feasibility. I know that “return nil” is not that same as a “respondsToSelector:” implementation that returns false if the property was implemented to return nil. Some magic would need to handle that translation to make it work with existing Objective-C protocols. This would automate what I have done in Objective-C several times by implementing respondsToSelector manually to hide protocol method implementations when necessary.

The advantage of going this route is that Swift implementations of the legacy Cocoa protocols will still function as expected in Cocoa while fitting the Swift model much better than optional protocol requirements.

Both Joe’s suggestion and my proposal need hackery to do the right thing for Objective-C interoperability, and both are feasible. I feel like my proposal is more honest about the hackery going on :)

Hmm. I agree that it's a little bit of hackers, but I don't think it's really dishonest to translate "return nil" into "respondsToSelector:" false and more than it is to make any other mapping from one system to another.

The main reason I prefer that approach is that it enables functionality that has been occasionally useful in Objective-C and is not otherwise possible in Swift - namely the ability to implement the protocol in a general purpose class and make a decision at initialization time whether you need a particular feature (such as dynamic row sizing) or not. Your approach doesn't allow for this.

My approach requires you to use a different design—either split into multiple protocols (which is probably the best answer in most of these cases) or introduce a different kind of API contract. My claim is that these solutions are more natural in Swift than “check if a particular requirement was actually implemented”.

I completely agree with you. However, unless Apple plans to do that with all of their frameworks over the next couple years it is not enough. We want Swift to play nice with the frameworks as they exist today, don't we?

···

Sent from my iPad

On Apr 15, 2016, at 6:17 PM, Douglas Gregor <dgregor@apple.com> wrote:

On Apr 15, 2016, at 4:15 PM, Matthew Johnson <matthew@anandabits.com> wrote:

On Apr 15, 2016, at 6:03 PM, Douglas Gregor <dgregor@apple.com> wrote:

On Apr 15, 2016, at 3:55 PM, Matthew Johnson <matthew@anandabits.com> wrote:

On Apr 13, 2016, at 11:42 AM, Douglas Gregor <dgregor@apple.com> wrote:
On Apr 11, 2016, at 10:30 AM, Matthew Johnson <matthew@anandabits.com> wrote:

On Apr 11, 2016, at 12:15 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:
On Apr 7, 2016, at 5:12 PM, Douglas Gregor via swift-evolution <swift-evolution@swift.org> wrote:

  - Doug

I’m not sure whether you’ve read the conclusion of my mail since
you’ve only commented on the introductory part. In the conclusion I
wrote that a possible approach for the replacement of ObjC-style
optional protocol methods would be:

1) the default implementation of a protocol method must be defined

in

the protocol (so just like in native Swift protocols today).

? They can and must be defined in protocol extensions today.

I know.

2) we add a way for a protocol provider to check whether the

protocol

adopter has provided an “override” of the default method.

I object to this part.

You object why? I do understand why you object to the ObjC model since
there is not necessarily an implementation of the protocol method and
thus the protocol provider has to guard every call with an existence
check. But in this model here we would be guaranteed that there would
be an implementation of the protocol method and thus guarding the call
wouldn’t be necessary.

Because it's a needless complication that will encourage protocol and
algorithm designers to create inefficient programs because they know the
user can fall back on this hack. Nobody thinks that classes need the
ability to check whether a given method is overridden. Why should this
be needed for protocols?

3) we improve the Xcode interface generator so that it clearly shows
whether a protocol method comes with a default or whether it
doesn’t.

Obvious goodness, long overdue.

(1) should address your main concern since it would guarantee that
the protocol provider is always able to call the protocol method
without having to do any checks. (2) would address the main concern
of protocol providers who need to guarantee that the protocol using
type achieves a certain minimum speed and does not use more than a
certain amount of memory for its internal book-keeping.

I don't how (2) can possibly help with that.

It helps because it allows the protocol provider to *understand*
whether the protocol adopter is actually using a certain feature or
isn’t.

If they need to understand that, they can make the indicator of that
fact a separate protocol requirement.

Here is the table view example again:

func useDelegate(delegate: NSTableViewDelegate) {

if has_override(delegate, tableView(_:, heightForRow:)) {
    // call tableViewNumberOfRows() on the delegate
    // allocate the geometry cache (1 entry per row)
    // call tableView(_:, heightForRow:) for each row
} else {
   // nothing to do here since here all rows have the same height
  }
}

Note that has_override() is just a placeholder syntax because I’ve not
had a good idea yet of how to express this in a Swiftier way.

  if delegate.hasVariableSizedRows { ... }

  if !(delegate is NSUniformTableViewDelegate) { ... }

etc.

In this example the table view is able to check whether the protocol
adopter has actually “overriden” the default implementation of
tableView(_:, heightForRow:).

Which, IMO, is a terrible way to indicate that a view has variable row
heights. It's indirect and maybe even inaccurate (I can imagine a table
view that is uniform and has its height set up once at construction
time, therefore it needs to override heightForRow).

If the adopter did, then the table view knows that the adopter wants
variable row heights and thus the table view can now create a cache of
row heights and it can enable the layouting code that knows how to lay
out rows with different heights. If however the adopter did not
provide its own implementation of this method then the table view does
not need to create a geometry cache and it can switch over to the
simpler fixed-row-height layout code. The reason why we want to cache
the row heights in the table view is because computing those heights
can be nontrivial and the layout code needs to access those height
values in every layoutSubviews() call. And layoutSubviews() is invoked
60 times per second while the user is scrolling. Also keep in mind
that, if we would not cache the row heights, then the row height
computation would end up competing for CPU cycles with the code that
properly configures the views for each row.

Without the ability to do this check on the protocol provider side, we
are forced to increase the API surface so that the protocol adopter
can explicitly tell us which layouting model he wants.

That's exactly what one should do. If layout model is an important
feature, the adopter should be explicit abou tit.

+1. I have found the UITableView design frustrating at times. In Objective-C we can implement respondsToSelector in a delegate to modify behavior as necessary (by returning false when queried about heightForRow). I have had the need to do that occasionally. It is obviously a terrible hack that isn't possible in Swift and is indicative of a design problem as Dave points out.

···

Sent from my iPad

On Apr 11, 2016, at 12:03 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:
on Sun Apr 10 2016, Dietmar Planitzer <dplanitzer-AT-q.com> wrote:

On Apr 10, 2016, at 11:46, Dave Abrahams via swift-evolution >> <swift-evolution@swift.org> wrote:
on Sun Apr 10 2016, Dietmar Planitzer <swift-evolution@swift.org> >> wrote:

But this also means that the protocol adopter now has to remember that
he needs to configure the layouting option correctly in order to get a
working and efficiently working table view. So the end result would be
a table view that’s hard to use correctly.

Regards,

Dietmar Planitzer

(3) is important because it would fix one of the many aspects that
make Swift protocols confusing for people who are new to the

language.

Finally, let me restate that the goal should really be that we
completely remove the syntactical differences between @objc and

native

Swift protocols. There should be one concept of protocol in Swift

and

we should be able to cover the use cases of formal and informal

ObjC

Protocols with them. The use case of formal protocols is already
covered today. The use case of informal protocols could be covered
with the approach above.

So is this an approach that would be acceptable to you?

Regards,

Dietmar Planitzer

On Apr 10, 2016, at 10:29, Dave Abrahams via swift-evolution >> <swift-evolution@swift.org> wrote:

on Fri Apr 08 2016, Dietmar Planitzer <swift-evolution@swift.org> >> wrote:

The biggest missing part with this model is that we are still not

able

to enable macro-level optimizations in the delegating type by

checking

whether the delegate does provide his own implementation of an
optional method or doesn’t. However, this is an important

advantage of

the ObjC model that we should not lose.

Maybe it’s time to take a big step back and ignore the question

of how

to implement things for a moment and to instead focus on the

question

of what the conceptual differences are between ObjC protocols

with

optional methods and Swift protocols with default
implementations. There are two relevant viewpoints here:

1) From the viewpoint of a protocol adaptor:

ObjC:

1a) adopter may provide his own implementation of the protocol

method,

but he is no required to.

1b) adopter can see in the protocol declaration for which methods

he

must provide an implementation. Those methods do not have the
“optional” keyword in front of them while optional methods do.

Swift:

1c) same as (1a).

1d) opening a binary-only Swift file in Xcode with a protocol
definition in it which contains methods with default

implementations

will not give any indication of which method has a default
implementation and which doesn’t. It’s only possible to see a
difference on the syntax level if you have access to the sources.

This visibility problem is something we aim to correct in Swift,

but

that is a question of syntax, documentation, and “header”

generation,

and really orthogonal to what's fundamental about “optional
requirements:”

1. The ability to “conform” to the protocol without a
default implementation of the requirement have been provided
anywhere.

2. The ability to dynamically query whether a type actually

provides the

requirement.

Both of these “features,” IMO, are actually bugs.

So from the viewpoint of the protocol adopter, there isn’t much

of a

difference. The only relevant difference is that its always

possible

in ObjC to tell whether a protocol method must be implemented by

the

adopter or whether a method already has a default behavior. We
shouldn’t actually have to change anything on the syntax-level in
Swift to fix this problem. It should be sufficient to improve the
Swift interface generator in Xcode so that it gives an indication
whether a protocol method has a default implementation or

doesn’t. Eg

if we want to ensure that the generated interface is valid syntax

then

we could do this:

protocol Foo {

func void bar() -> Int /* has default */

}

or if we say that it is fine that the generated interface is not

valid

syntax (I think it already shows "= default” for function

arguments

with a default value which I don’t think is valid syntax), then

we

could do this:

protocol Foo {

func void bar() -> Int {…}

}

Now on to the other side of the equation.

2) From the viewpoint of the protocol provider (the person who

defines

the protocol and the type that will invoke the protocol methods):

ObjC:

2a) provider has freedom in deciding where to put the default
implementation and he can put the default implementation in a

single

place or spread it out if necessary over multiple places. So has

the

freedom to choose whatever makes the most sense for the problem

at

hand.

But freedom for protocol implementors reduces predictability for

protocol

clients and adopters.

2b) provider can detect whether the adopter provides his own

protocol

method implementation without compromising the definition of the
protocol (compromising here means making return values optional

when

they should not be optional based on the natural definition of

the

API). This enables the provider to implement macro-level

optimizations

(eg table view can understand whether fixed or variable row

heights

are desired).

Swift:

2c) provider is forced to put the default implementation in a

specific

place.

2d) provider has no way to detect whether the adopter has

provided his

own implementation of the protocol method.

I do think that (2a) would be nice to have but we can probably do
without it if it helps us to make progress with this

topic. However,

the ability to detect whether a protocol adopter provides his own
implementation of a protocol method which comes with a default is

a

useful and important feature which helps us in optimizing the
implementation of types and which allows us to keep the API

surface

smaller than it would be without this ability. Just go and

compare eg

UITableView to the Android ListView / RecyclerView to see the
consequences of not having that ability and how it inflates the

API

surface (and keep in mind that the Android equivalents provide a
fraction of the UITableView functionality).

The important point about (2b) is actually that we are able to

detect

whether an “override” (I’ll just call this overriding for now) of

the

default implementation exists or does not exist.

IMO the important point about (2b) is that it leads to protocol

designs

that create work and complexity for clients of the protocol, and

being

constrained to make your protocol work so that clients don't have

to do

these kinds of checks is a Very Good Thing™.

In ObjC we make this distinction by checking whether an

implementation

of the method exists at all. But we don’t have to do it that

way. An

alternative approach could be based on a check that sees whether

the

dispatch table of the delegate contains a pointer to the default
implementation of the protocol method or to some other method. So
conceptually what we want is an operation like this:

func void useDelegate(delegate: NSTableViewDelegate) {

if has_override(delegate, tableView(_:, heightOfRow:)) { // ask

the

delegate how many rows it has // allocate the geometry cache //

fill

in the geometry cache by calling tableView(_:, heightForRow:) for

each

row } else { // nothing to do here } }

Which would get the job done but doesn’t look good. Maybe someone

has

a better idea of how the syntax such an operator could look.

So my point here is that what we care about is the ability to

detect

whether the adopter provides an implementation of a protocol

method

which comes with a default implementation. The point is not that

Swift

protocols should work the exact same way that ObjC protocols have

been

working under the hood. But I do think that we want to eventually

get

to a point where the @objc attribute disappears and that we get a
truly unified language on the syntactical level. An approach

where:

I) we accept that the default behavior of a protocol method has

to be

provided by the protocol itself

II) the language is extended with a mechanism that makes it

possible

for a protocol provider to detect whether the adopter has

“overridden”

the default implementation

III) we improve the Xcode Swift interface generator so that it

gives a

clear indication whether a protocol method does come with a

default

implementation

would give us all the relevant advantages of ObjC-style optional
protocol methods and it should allow us to create a unified

syntax

where there is no longer a visible difference between an optional
protocol method that was imported from ObjC and a native Swift
protocol with default implementations.

Regards,

Dietmar Planitzer

On Apr 7, 2016, at 17: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

--
Dave

_______________________________________________
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

--
Dave

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
Dave
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Actually, Apple’s frameworks have often contained code to check whether given methods are overridden, and this has allowed them to deprecate override points and replace them with better API without breaking source or binary compatibility. The most obvious example that comes to mind is NSDocument; when they introduced the newer override points such as -readFromURL:ofType:error: that used NSURLs instead of paths and allowed returning an NSError, they added code in the default implementation to check whether the subclass overrode the older -readFromFile:ofType: method and if it did, called that method. Otherwise, it would call the modern methods. This way, older applications that were still overriding -readFromFile:ofType: would continue to work correctly.

Charles

···

On Apr 11, 2016, at 12:03 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

on Sun Apr 10 2016, Dietmar Planitzer <dplanitzer-AT-q.com <http://dplanitzer-at-q.com/&gt;&gt; wrote:

On Apr 10, 2016, at 11:46, Dave Abrahams via swift-evolution >> <swift-evolution@swift.org> wrote:

on Sun Apr 10 2016, Dietmar Planitzer <swift-evolution@swift.org> >> wrote:

I’m not sure whether you’ve read the conclusion of my mail since
you’ve only commented on the introductory part. In the conclusion I
wrote that a possible approach for the replacement of ObjC-style
optional protocol methods would be:

1) the default implementation of a protocol method must be defined

in

the protocol (so just like in native Swift protocols today).

? They can and must be defined in protocol extensions today.

I know.

2) we add a way for a protocol provider to check whether the

protocol

adopter has provided an “override” of the default method.

I object to this part.

You object why? I do understand why you object to the ObjC model since
there is not necessarily an implementation of the protocol method and
thus the protocol provider has to guard every call with an existence
check. But in this model here we would be guaranteed that there would
be an implementation of the protocol method and thus guarding the call
wouldn’t be necessary.

Because it's a needless complication that will encourage protocol and
algorithm designers to create inefficient programs because they know the
user can fall back on this hack. Nobody thinks that classes need the
ability to check whether a given method is overridden. Why should this
be needed for protocols?

Inline.

I’m not sure whether you’ve read the conclusion of my mail since
you’ve only commented on the introductory part. In the conclusion I
wrote that a possible approach for the replacement of ObjC-style
optional protocol methods would be:

1) the default implementation of a protocol method must be defined

in

the protocol (so just like in native Swift protocols today).

? They can and must be defined in protocol extensions today.

I know.

2) we add a way for a protocol provider to check whether the

protocol

adopter has provided an “override” of the default method.

I object to this part.

You object why? I do understand why you object to the ObjC model since
there is not necessarily an implementation of the protocol method and
thus the protocol provider has to guard every call with an existence
check. But in this model here we would be guaranteed that there would
be an implementation of the protocol method and thus guarding the call
wouldn’t be necessary.

Because it's a needless complication that will encourage protocol and
algorithm designers to create inefficient programs because they know the
user can fall back on this hack.

It’s not clear why you think that the ability to check whether a protocol adopter implements a method or doesn’t would make programs inefficient. The fact that the protocol provider can check whether an adopter actually wants a feature or not (optional method is implemented -> adopter wants the feature; otherwise he clearly doesn’t) is what enables us to create more efficient implementations.

Nobody thinks that classes need the
ability to check whether a given method is overridden. Why should this
be needed for protocols?

Various Mac OS X frameworks have been using this technique for a long time to enable:

a) binary backward compatibility: eg NSDocument or NSBrowser in the AppKit have dramatically changed their respective implementations over the past 10 to 20 years and they have changed their subclassing APIs in some cases. The implementation does check whether an app is overriding the old methods and if so the classes enable backward compatibility functionality as needed while still providing as many of the new pieces of functionality as possible.

b) optimizations: eg the AppKit text system for example is very powerful, Unicode compliant and it offers a lot of customization hooks for app developers. However customization hooks are sometimes only enabled if they are actually used by an app (if the app overrides them in a subclass). Otherwise they are disabled and bypassed to enable faster standard text processing.

3) we improve the Xcode interface generator so that it clearly shows
whether a protocol method comes with a default or whether it
doesn’t.

Obvious goodness, long overdue.

(1) should address your main concern since it would guarantee that
the protocol provider is always able to call the protocol method
without having to do any checks. (2) would address the main concern
of protocol providers who need to guarantee that the protocol using
type achieves a certain minimum speed and does not use more than a
certain amount of memory for its internal book-keeping.

I don't how (2) can possibly help with that.

It helps because it allows the protocol provider to *understand*
whether the protocol adopter is actually using a certain feature or
isn’t.

If they need to understand that, they can make the indicator of that
fact a separate protocol requirement.

Here is the table view example again:

func useDelegate(delegate: NSTableViewDelegate) {

if has_override(delegate, tableView(_:, heightForRow:)) {
    // call tableViewNumberOfRows() on the delegate
    // allocate the geometry cache (1 entry per row)
    // call tableView(_:, heightForRow:) for each row
} else {
   // nothing to do here since here all rows have the same height
  }
}

Note that has_override() is just a placeholder syntax because I’ve not
had a good idea yet of how to express this in a Swiftier way.

  if delegate.hasVariableSizedRows { … }

This means that we not only increase the surface of the API, it also means that we make it now possible for the protocol adopter to provide conflicting information to the caller of the protocol methods. At the same time, the caller of the protocol methods still needs to guard every call of tableView(heightForRow:) with a call to hasVariableSizedRows(). So by the end of the day, this solution doesn’t improve anything. It just adds new complexity and sources of bugs.

The surface of the API increases because the protocol adopter now has to learn 2 methods instead of 1 and he has to provide up to 2 implementations instead of just one. It makes it more likely that the protocol adopter writes buggy software and gets confused by the behavior of the table view. Eg it is safe to assume that both hasVariableSizedRows() and heightForRow() would come with a default implementation (keep in mind that those features didn’t even exist for the first couple years of the NSTableView’s life). So I could write this in my delegate:

func heightForRow(row: Int) -> Float {
   return (i % 2) == 0 ? 60 : 30
}

and then when I compile my app and run it - I don’t get alternate row heights because if forgot to provide my own implementation of hasVariableSizedRows() (and the default implementation returns false for binary backward combat reasons). Or I write this:

func hasVariableSizedRows() -> Boolean {
   return true
}

func heigthForRow(row: Int) -> Float {
   return (i % 2) == 0 ? 60 : 30
}

Compile it - and again the table view doesn’t work “thanks” to the fact that the protocol comes with default implementations AND because Swift does not require that a method which overrides a default protocol implementation must be marked with the “override” (or some other) keyword. Eg this:

override func hasVariableSizedRows() -> Boolean {
   return true
}

override func heigthForRow(row: Int) -> Float {
   return (i % 2) == 0 ? 60 : 30
}

Here at least the compiler would have been able to tell me that something is wrong and that I’m not actually replacing the default protocol method implementation as I thought I would. Yes the ObjC variant of optional protocol methods also suffers from the misspelling problem - but that’s not an argument against the concept of optional protocol methods. It is only an argument against the way that they are implemented in ObjC. One of my hopes, when Swift was revealed to the public as “an Objc without the C part”, was actually that this would have been one of the things that it would have fixed. But it didn’t. Instead it gave us a similar problem with its particular combination of default protocol methods and the way they are supposed to be overriden / shadowed in conforming types.

So, no. Your proposed solution is not an improvement. Especially considering the way that protocols work in Swift today.

Your model also allows a protocol adopter to provide conflicting information because he can now do this:

func hasVariableSizedRows() -> Boolean {
   return false
}

func heightForRow(row: Int) -> Float {
   return 8
}

or he can do this:

func hasVariableSizedRows() -> Boolean {
   return true
}

func heightForRow(row: Int) -> Float {
   return 0
}

and then the question is: what should the table view do? Does the return value of hasVariableSizeRows() represent the truth or is it the return value of heightForRow()? It’s possible to argue either way. But no matter how we decide to handle this, we now need to write code in the protocol using type that detects these conflicts and does something about them. Even if that something is just to kill the app, we still need to write that code correctly. If we miss a case and it just so happens that an app that returns conflicting information to us gets away with it and the table view ends up producing something that looks right to the app vendor, then we are now forever bound to keep this special case intact since breaking an app with a OS update is not an option.

Finally, this model didn’t actually make anything safer from the viewpoint of the protocol provider because we still need to guard every call to heightForRow(). Nothing has improved on that front:

if delegate.hasVariableSizeRows() {
   return delegate.heightForRow(row)
} else {
   return rowHeight
}

is not better than this:

if has_override(delegate, heightForRow) {
   return delegate.heightForRow()
} else {
   return rowHeight
}

in fact it is worse. Because we now need to introduce this “guarding predicate” for every single use case. Each and every use case will require yet another guarding call that we have to design, implement, test and that our users have to learn about. It is much better to solve this problem once and then to reuse this solution for all use cases since it is universally applicable.

What this is fundamentally about, and allows us to do:

if delegate.respondsToSelector(selector(tableView(:heightForRow:)) {
   delegate.tableView(self, heightForRow: row)
}

in ObjC is the idea that the NSTableView is a component which offers a set of features:

- fixed row heights
- variable row heights
- drag & drop
- old style pasteboard support
- new style pasteboard support
- old style table cells
- new style table cell views
- etc, pp

and then the delegate simply picks the features that it wants to use. It simply does this by implementing the corresponding methods and by NOT implementing the methods of a feature it doesn’t care about:

- delegate wants feature X -> implement the method for feature X

- delegate does not want feature X -> nothing to do

and this is why the concept of optional protocol methods captures this idea precisely: I don’t need to write code for feature X if I don’t want it. Why should I write code for something I don’t want? Wouldn’t make sense.

  if !(delegate is NSUniformTableViewDelegate) { … }

I don’t think that replacing 1 protocol with a dozen or more protocols is a good idea or a step forward. Beside that we would actually have to introduce hundreds of new protocols since the optional protocol method feature is used all over the place in public Mac OS X frameworks and also in private ones.

etc.

In this example the table view is able to check whether the protocol
adopter has actually “overriden” the default implementation of
tableView(_:, heightForRow:).

Which, IMO, is a terrible way to indicate that a view has variable row
heights. It's indirect and maybe even inaccurate (I can imagine a table
view that is uniform and has its height set up once at construction
time, therefore it needs to override heightForRow).

I don’t see how it is indirect. I want the feature -> I implement the corresponding method; I don’t want the feature -> I don’t write code for it. This captures exactly the nature of the problem and it really can’t get any simpler than that for the protocol user. Yes, it makes life a bit harder for the protocol provider. But that is absolutely fine and in fact that is what we want since for every protocol provider, there are dozens to hundreds to thousands of developers who are going to write code which adopts the protocol. So naturally we want to focus the complexity of the implementation on the side of the protocol provider while making the life for protocol adopters as easy and safe as possible.

It’s not clear how the optional protocol method concept can cause inaccuracy except that a protocol provider decides to misuse it. If I want a table view with a fixed row height then I just set the row height on the table view like this:

tableView.rowHeight = 16

and I simply don’t provide a heightForRow() implementation since all I want is a single height for all rows. So parameterizing the row height is not necessary and writing code for that would be a waste of time and just be an unnecessary source of bugs and potential confusion.

Regards,

Dietmar Planitzer

···

On Apr 11, 2016, at 10:03, Dave Abrahams <dabrahams@apple.com> wrote:
on Sun Apr 10 2016, Dietmar Planitzer <dplanitzer-AT-q.com> wrote:

On Apr 10, 2016, at 11:46, Dave Abrahams via swift-evolution >> <swift-evolution@swift.org> wrote:
on Sun Apr 10 2016, Dietmar Planitzer <swift-evolution@swift.org> >> wrote:

If the adopter did, then the table view knows that the adopter wants
variable row heights and thus the table view can now create a cache of
row heights and it can enable the layouting code that knows how to lay
out rows with different heights. If however the adopter did not
provide its own implementation of this method then the table view does
not need to create a geometry cache and it can switch over to the
simpler fixed-row-height layout code. The reason why we want to cache
the row heights in the table view is because computing those heights
can be nontrivial and the layout code needs to access those height
values in every layoutSubviews() call. And layoutSubviews() is invoked
60 times per second while the user is scrolling. Also keep in mind
that, if we would not cache the row heights, then the row height
computation would end up competing for CPU cycles with the code that
properly configures the views for each row.

Without the ability to do this check on the protocol provider side, we
are forced to increase the API surface so that the protocol adopter
can explicitly tell us which layouting model he wants.

That's exactly what one should do. If layout model is an important
feature, the adopter should be explicit abou tit.

But this also means that the protocol adopter now has to remember that
he needs to configure the layouting option correctly in order to get a
working and efficiently working table view. So the end result would be
a table view that’s hard to use correctly.

Regards,

Dietmar Planitzer

(3) is important because it would fix one of the many aspects that
make Swift protocols confusing for people who are new to the

language.

Finally, let me restate that the goal should really be that we
completely remove the syntactical differences between @objc and

native

Swift protocols. There should be one concept of protocol in Swift

and

we should be able to cover the use cases of formal and informal

ObjC

Protocols with them. The use case of formal protocols is already
covered today. The use case of informal protocols could be covered
with the approach above.

So is this an approach that would be acceptable to you?

Regards,

Dietmar Planitzer

On Apr 10, 2016, at 10:29, Dave Abrahams via swift-evolution >> <swift-evolution@swift.org> wrote:

on Fri Apr 08 2016, Dietmar Planitzer <swift-evolution@swift.org> >> wrote:

The biggest missing part with this model is that we are still not

able

to enable macro-level optimizations in the delegating type by

checking

whether the delegate does provide his own implementation of an
optional method or doesn’t. However, this is an important

advantage of

the ObjC model that we should not lose.

Maybe it’s time to take a big step back and ignore the question

of how

to implement things for a moment and to instead focus on the

question

of what the conceptual differences are between ObjC protocols

with

optional methods and Swift protocols with default
implementations. There are two relevant viewpoints here:

1) From the viewpoint of a protocol adaptor:

ObjC:

1a) adopter may provide his own implementation of the protocol

method,

but he is no required to.

1b) adopter can see in the protocol declaration for which methods

he

must provide an implementation. Those methods do not have the
“optional” keyword in front of them while optional methods do.

Swift:

1c) same as (1a).

1d) opening a binary-only Swift file in Xcode with a protocol
definition in it which contains methods with default

implementations

will not give any indication of which method has a default
implementation and which doesn’t. It’s only possible to see a
difference on the syntax level if you have access to the sources.

This visibility problem is something we aim to correct in Swift,

but

that is a question of syntax, documentation, and “header”

generation,

and really orthogonal to what's fundamental about “optional
requirements:”

1. The ability to “conform” to the protocol without a
default implementation of the requirement have been provided
anywhere.

2. The ability to dynamically query whether a type actually

provides the

requirement.

Both of these “features,” IMO, are actually bugs.

So from the viewpoint of the protocol adopter, there isn’t much

of a

difference. The only relevant difference is that its always

possible

in ObjC to tell whether a protocol method must be implemented by

the

adopter or whether a method already has a default behavior. We
shouldn’t actually have to change anything on the syntax-level in
Swift to fix this problem. It should be sufficient to improve the
Swift interface generator in Xcode so that it gives an indication
whether a protocol method has a default implementation or

doesn’t. Eg

if we want to ensure that the generated interface is valid syntax

then

we could do this:

protocol Foo {

func void bar() -> Int /* has default */

}

or if we say that it is fine that the generated interface is not

valid

syntax (I think it already shows "= default” for function

arguments

with a default value which I don’t think is valid syntax), then

we

could do this:

protocol Foo {

func void bar() -> Int {…}

}

Now on to the other side of the equation.

2) From the viewpoint of the protocol provider (the person who

defines

the protocol and the type that will invoke the protocol methods):

ObjC:

2a) provider has freedom in deciding where to put the default
implementation and he can put the default implementation in a

single

place or spread it out if necessary over multiple places. So has

the

freedom to choose whatever makes the most sense for the problem

at

hand.

But freedom for protocol implementors reduces predictability for

protocol

clients and adopters.

2b) provider can detect whether the adopter provides his own

protocol

method implementation without compromising the definition of the
protocol (compromising here means making return values optional

when

they should not be optional based on the natural definition of

the

API). This enables the provider to implement macro-level

optimizations

(eg table view can understand whether fixed or variable row

heights

are desired).

Swift:

2c) provider is forced to put the default implementation in a

specific

place.

2d) provider has no way to detect whether the adopter has

provided his

own implementation of the protocol method.

I do think that (2a) would be nice to have but we can probably do
without it if it helps us to make progress with this

topic. However,

the ability to detect whether a protocol adopter provides his own
implementation of a protocol method which comes with a default is

a

useful and important feature which helps us in optimizing the
implementation of types and which allows us to keep the API

surface

smaller than it would be without this ability. Just go and

compare eg

UITableView to the Android ListView / RecyclerView to see the
consequences of not having that ability and how it inflates the

API

surface (and keep in mind that the Android equivalents provide a
fraction of the UITableView functionality).

The important point about (2b) is actually that we are able to

detect

whether an “override” (I’ll just call this overriding for now) of

the

default implementation exists or does not exist.

IMO the important point about (2b) is that it leads to protocol

designs

that create work and complexity for clients of the protocol, and

being

constrained to make your protocol work so that clients don't have

to do

these kinds of checks is a Very Good Thing™.

In ObjC we make this distinction by checking whether an

implementation

of the method exists at all. But we don’t have to do it that

way. An

alternative approach could be based on a check that sees whether

the

dispatch table of the delegate contains a pointer to the default
implementation of the protocol method or to some other method. So
conceptually what we want is an operation like this:

func void useDelegate(delegate: NSTableViewDelegate) {

if has_override(delegate, tableView(_:, heightOfRow:)) { // ask

the

delegate how many rows it has // allocate the geometry cache //

fill

in the geometry cache by calling tableView(_:, heightForRow:) for

each

row } else { // nothing to do here } }

Which would get the job done but doesn’t look good. Maybe someone

has

a better idea of how the syntax such an operator could look.

So my point here is that what we care about is the ability to

detect

whether the adopter provides an implementation of a protocol

method

which comes with a default implementation. The point is not that

Swift

protocols should work the exact same way that ObjC protocols have

been

working under the hood. But I do think that we want to eventually

get

to a point where the @objc attribute disappears and that we get a
truly unified language on the syntactical level. An approach

where:

I) we accept that the default behavior of a protocol method has

to be

provided by the protocol itself

II) the language is extended with a mechanism that makes it

possible

for a protocol provider to detect whether the adopter has

“overridden”

the default implementation

III) we improve the Xcode Swift interface generator so that it

gives a

clear indication whether a protocol method does come with a

default

implementation

would give us all the relevant advantages of ObjC-style optional
protocol methods and it should allow us to create a unified

syntax

where there is no longer a visible difference between an optional
protocol method that was imported from ObjC and a native Swift
protocol with default implementations.

Regards,

Dietmar Planitzer

On Apr 7, 2016, at 17: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

--
Dave

_______________________________________________
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

--
Dave

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
Dave

That’s the other part of my argument: if it is true that Swift code only needs to conform to ObjC protocols with optional requirements, but Swift code does not need to invoke optional requirements of ObjC protocols except very rarely, then it’s okay for calling-an-optional-requirement not to be part of the Swift language.

  - Doug

···

On Apr 15, 2016, at 4:27 PM, Matthew Johnson <matthew@anandabits.com> wrote:

Sent from my iPad

On Apr 15, 2016, at 6:17 PM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

On Apr 15, 2016, at 4:15 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

Sent from my iPad

On Apr 15, 2016, at 6:03 PM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

On Apr 15, 2016, at 3:55 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Apr 13, 2016, at 11:42 AM, Douglas Gregor <dgregor@apple.com <mailto:dgregor@apple.com>> wrote:

On Apr 11, 2016, at 10:30 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

Sent from my iPad

On Apr 11, 2016, at 12:15 PM, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Apr 7, 2016, at 5:12 PM, Douglas Gregor via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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.

To me, compound names for closure properties and satisfying property requirements with methods aren't hacks, they're missing features we ought to support anyway. I strongly prefer implementing those over your proposed solution. It sounds to me like a lot of people using optional protocol requirements *want* the locality of control flow visible in the caller, for optimization or other purposes, and your proposed solution makes this incredibly obscure and magical.

Do you have the same thought for optional closure properties? If so and heightForRow was an optional closure property it would satisfy all use cases elegantly. It could have a default implementation that returns nil. When non-uniform heights are required a normal method implementation can be provided. Delegates that have uniform row heights some of the time, but not all of the time, would also be supported by implementing the property.

There are still some issues here:

1) It doesn’t handle optional read/write properties at all, because the setter signature would be different. Perhaps some future lens design would make this possible. For now, the workaround would have to be importing the setter as a second optional closure property, I guess. (The current system is similarly broken).

I was only thinking about methods, not properties. :) How common are optional, writeable property requirements? I don’t have a good guess off the top of my head, but my hunch is that they are not that common.

They are *very* rare. Aside from UITextInputTraits <UITextInputTraits | Apple Developer Documentation, I see three in OS X and four in iOS.

2) For an @objc protocol, you won’t actually be able to fully implement the optional closure property with a property of optional type, because “return nil” in the getter is not the same as “-respondsToSelector: returns false”. Indeed, the getter result type/setter parameter type should be non-optional, so we would (at best) need a special rule that optional closure properties of @objc protocols can only be implemented by non-optional properties of closure type or by methods.

This is related to why I asked about feasibility. I know that “return nil” is not that same as a “respondsToSelector:” implementation that returns false if the property was implemented to return nil. Some magic would need to handle that translation to make it work with existing Objective-C protocols. This would automate what I have done in Objective-C several times by implementing respondsToSelector manually to hide protocol method implementations when necessary.

The advantage of going this route is that Swift implementations of the legacy Cocoa protocols will still function as expected in Cocoa while fitting the Swift model much better than optional protocol requirements.

Both Joe’s suggestion and my proposal need hackery to do the right thing for Objective-C interoperability, and both are feasible. I feel like my proposal is more honest about the hackery going on :)

Hmm. I agree that it's a little bit of hackers, but I don't think it's really dishonest to translate "return nil" into "respondsToSelector:" false and more than it is to make any other mapping from one system to another.

The main reason I prefer that approach is that it enables functionality that has been occasionally useful in Objective-C and is not otherwise possible in Swift - namely the ability to implement the protocol in a general purpose class and make a decision at initialization time whether you need a particular feature (such as dynamic row sizing) or not. Your approach doesn't allow for this.

My approach requires you to use a different design—either split into multiple protocols (which is probably the best answer in most of these cases) or introduce a different kind of API contract. My claim is that these solutions are more natural in Swift than “check if a particular requirement was actually implemented”.

I completely agree with you. However, unless Apple plans to do that with all of their frameworks over the next couple years it is not enough. We want Swift to play nice with the frameworks as they exist today, don't we?

That’s the other part of my argument: if it is true that Swift code only needs to conform to ObjC protocols with optional requirements, but Swift code does not need to invoke optional requirements of ObjC protocols except very rarely, then it’s okay for calling-an-optional-requirement not to be part of the Swift language.

How much of a complication is the `methodName?` behavior? If you're confident that `optional` is the wrong choice for protocols designed in the future, perhaps you could just make it impossible to declare a new optional requirement in Swift, but leave the calling behavior as-is?

···

--
Brent Royal-Gordon
Architechies

That’s the other part of my argument: if it is true that Swift code only needs to conform to ObjC protocols with optional requirements, but Swift code does not need to invoke optional requirements of ObjC protocols except very rarely, then it’s okay for calling-an-optional-requirement not to be part of the Swift language.

How much of a complication is the `methodName?` behavior?

It’s a semi-complicated path through the type checker, but it’s not awful to maintain. There’s a bit of a “fix it or remove it” issue here, because it doesn’t work for settable properties, even though there are very few such things.

If you're confident that `optional` is the wrong choice for protocols designed in the future, perhaps you could just make it impossible to declare a new optional requirement in Swift, but leave the calling behavior as-is?

Sure, we can make it arbitrarily hard to use. For example, calling it @objc_optional or similar would reinforce that it’s an Objective-C compatibility feature rather than an intended Swift feature.

  - Doug

···

On Apr 15, 2016, at 5:24 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

I don't believe we're aiming to support this kind of evolution in Swift,
but others understand our resilience plans better than I, so we should
probably let them comment.

···

on Mon Apr 11 2016, Charles Srstka <cocoadev-AT-charlessoft.com> wrote:

    On Apr 11, 2016, at 12:03 PM, Dave Abrahams via swift-evolution > <swift-evolution@swift.org> wrote:

    on Sun Apr 10 2016, Dietmar Planitzer <dplanitzer-AT-q.com> wrote:

                On Apr 10, 2016, at 11:46, Dave Abrahams via swift-evolution > > <swift-evolution@swift.org> wrote:

            on Sun Apr 10 2016, Dietmar Planitzer <swift-evolution@swift.org> > > wrote:

                        I’m not sure whether you’ve read the conclusion of my
                mail since
                you’ve only commented on the introductory part. In the
                conclusion I
                wrote that a possible approach for the replacement of ObjC-style
                optional protocol methods would be:

                1) the default implementation of a protocol method must be
                defined

        in

                    the protocol (so just like in native Swift protocols today).

            ? They can and must be defined in protocol extensions today.

        I know.

                2) we add a way for a protocol provider to check whether the

        protocol

                    adopter has provided an “override” of the default method.

            I object to this part.

        You object why? I do understand why you object to the ObjC model since
        there is not necessarily an implementation of the protocol method and
        thus the protocol provider has to guard every call with an existence
        check. But in this model here we would be guaranteed that there would
        be an implementation of the protocol method and thus guarding the call
        wouldn’t be necessary.

    Because it's a needless complication that will encourage protocol and
    algorithm designers to create inefficient programs because they know the
    user can fall back on this hack. Nobody thinks that classes need the
    ability to check whether a given method is overridden. Why should this
    be needed for protocols?

Actually, Apple’s frameworks have often contained code to check whether given
methods are overridden, and this has allowed them to deprecate override points
and replace them with better API without breaking source or binary
compatibility. The most obvious example that comes to mind is NSDocument; when
they introduced the newer override points such as -readFromURL:ofType:error:
that used NSURLs instead of paths and allowed returning an NSError, they added
code in the default implementation to check whether the subclass overrode the
older -readFromFile:ofType: method and if it did, called that method. Otherwise,
it would call the modern methods. This way, older applications that were still
overriding -readFromFile:ofType: would continue to work correctly.

--
Dave

It's a useful enough pattern that I wouldn't want to rule it out entirely, but it's rare enough that it could be some strange runtime call. (Also, in many cases there's a simpler implementation, like "call the old method unilaterally".)

Jordan

···

On Apr 11, 2016, at 11:41 , Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

on Mon Apr 11 2016, Charles Srstka <cocoadev-AT-charlessoft.com <http://cocoadev-at-charlessoft.com/&gt;&gt; wrote:

   On Apr 11, 2016, at 12:03 PM, Dave Abrahams via swift-evolution >> <swift-evolution@swift.org> wrote:

   on Sun Apr 10 2016, Dietmar Planitzer <dplanitzer-AT-q.com> wrote:

               On Apr 10, 2016, at 11:46, Dave Abrahams via swift-evolution >> >> <swift-evolution@swift.org> wrote:

           on Sun Apr 10 2016, Dietmar Planitzer <swift-evolution@swift.org> >> >> wrote:

                       I’m not sure whether you’ve read the conclusion of my
               mail since
               you’ve only commented on the introductory part. In the
               conclusion I
               wrote that a possible approach for the replacement of ObjC-style
               optional protocol methods would be:

               1) the default implementation of a protocol method must be
               defined

       in

                   the protocol (so just like in native Swift protocols today).

           ? They can and must be defined in protocol extensions today.

       I know.

               2) we add a way for a protocol provider to check whether the

       protocol

                   adopter has provided an “override” of the default method.

           I object to this part.

       You object why? I do understand why you object to the ObjC model since
       there is not necessarily an implementation of the protocol method and
       thus the protocol provider has to guard every call with an existence
       check. But in this model here we would be guaranteed that there would
       be an implementation of the protocol method and thus guarding the call
       wouldn’t be necessary.

   Because it's a needless complication that will encourage protocol and
   algorithm designers to create inefficient programs because they know the
   user can fall back on this hack. Nobody thinks that classes need the
   ability to check whether a given method is overridden. Why should this
   be needed for protocols?

Actually, Apple’s frameworks have often contained code to check whether given
methods are overridden, and this has allowed them to deprecate override points
and replace them with better API without breaking source or binary
compatibility. The most obvious example that comes to mind is NSDocument; when
they introduced the newer override points such as -readFromURL:ofType:error:
that used NSURLs instead of paths and allowed returning an NSError, they added
code in the default implementation to check whether the subclass overrode the
older -readFromFile:ofType: method and if it did, called that method. Otherwise,
it would call the modern methods. This way, older applications that were still
overriding -readFromFile:ofType: would continue to work correctly.

I don't believe we're aiming to support this kind of evolution in Swift,
but others understand our resilience plans better than I, so we should
probably let them comment.

Inline.

I’m not sure whether you’ve read the conclusion of my mail since
you’ve only commented on the introductory part. In the conclusion I
wrote that a possible approach for the replacement of ObjC-style
optional protocol methods would be:

1) the default implementation of a protocol method must be defined

in

the protocol (so just like in native Swift protocols today).

? They can and must be defined in protocol extensions today.

I know.

2) we add a way for a protocol provider to check whether the

protocol

adopter has provided an “override” of the default method.

I object to this part.

You object why? I do understand why you object to the ObjC model since
there is not necessarily an implementation of the protocol method and
thus the protocol provider has to guard every call with an existence
check. But in this model here we would be guaranteed that there would
be an implementation of the protocol method and thus guarding the call
wouldn’t be necessary.

Because it's a needless complication that will encourage protocol and
algorithm designers to create inefficient programs because they know the
user can fall back on this hack.

It’s not clear why you think that the ability to check whether a protocol adopter implements a method or doesn’t would make programs inefficient. The fact that the protocol provider can check whether an adopter actually wants a feature or not (optional method is implemented -> adopter wants the feature; otherwise he clearly doesn’t) is what enables us to create more efficient implementations.

Allowing a user of a protocol to dynamically check whether the type conforming to the protocol requests some bit of custom functionality allows fast paths for non-customized behavior. The primary mechanism for that check in Objective-C is -respondsToSelector:, but that does not imply that it is the right mechanism for Swift.

Note that has_override() is just a placeholder syntax because I’ve not
had a good idea yet of how to express this in a Swiftier way.

if delegate.hasVariableSizedRows { … }

This means that we not only increase the surface of the API, it also means that we make it now possible for the protocol adopter to provide conflicting information to the caller of the protocol methods. At the same time, the caller of the protocol methods still needs to guard every call of tableView(heightForRow:) with a call to hasVariableSizedRows(). So by the end of the day, this solution doesn’t improve anything. It just adds new complexity and sources of bugs.

This particular API has been discussed numerous times. For example, here’s a potentially-more-Swifty approach to it:

  http://thread.gmane.org/gmane.comp.lang.swift.evolution/13347/focus=13608

The approach eliminates the coupling between, e.g., hasVariableSizedRows and heightForRow and makes it far more clear (IMO) that you’re choosing among several behaviors.

in ObjC is the idea that the NSTableView is a component which offers a set of features:

- fixed row heights
- variable row heights
- drag & drop
- old style pasteboard support
- new style pasteboard support
- old style table cells
- new style table cell views
- etc, pp

That’s a lot of disparate functionality in one very large protocol. Again, referring to the other thread, Brent started dividing this delegate into a number of related protocols:

  http://thread.gmane.org/gmane.comp.lang.swift.evolution/13347/focus=13601

and then the delegate simply picks the features that it wants to use. It simply does this by implementing the corresponding methods and by NOT implementing the methods of a feature it doesn’t care about:

- delegate wants feature X -> implement the method for feature X

- delegate does not want feature X -> nothing to do

and this is why the concept of optional protocol methods captures this idea precisely: I don’t need to write code for feature X if I don’t want it. Why should I write code for something I don’t want? Wouldn’t make sense.

Everything you say in the above snippet applies equally to default implementations of protocol members.

if !(delegate is NSUniformTableViewDelegate) { … }

I don’t think that replacing 1 protocol with a dozen or more protocols is a good idea or a step forward. Beside that we would actually have to introduce hundreds of new protocols since the optional protocol method feature is used all over the place in public Mac OS X frameworks and also in private ones.

I might agree with that statement, but the same complexity is there whether you split a large protocol into several related protocols or not: having multiple protocols merely acknowledges and groups that functionality in code rather than in documentation. Note how the documentation for NSTableViewDelegate has to be split into numerous groups of methods because it’s covering a number of different aspects of table views:

  NSTableViewDelegate | Apple Developer Documentation

etc.

In this example the table view is able to check whether the protocol
adopter has actually “overriden” the default implementation of
tableView(_:, heightForRow:).

Which, IMO, is a terrible way to indicate that a view has variable row
heights. It's indirect and maybe even inaccurate (I can imagine a table
view that is uniform and has its height set up once at construction
time, therefore it needs to override heightForRow).

I don’t see how it is indirect. I want the feature -> I implement the corresponding method; I don’t want the feature -> I don’t write code for it.

“I want the feature -> I implement the corresponding protocol” is nearly identical, but ties the action specifically to a named entity in the language. It’s also how one *always* deals with protocols.

This captures exactly the nature of the problem and it really can’t get any simpler than that for the protocol user.

From the perspective of the person making their type conform to a protocol (what you’re calling a protocol “user”), there is absolutely no difference between “has a default implementation” and “is an optional requirement”.

  - Doug

···

On Apr 12, 2016, at 2:32 PM, Dietmar Planitzer via swift-evolution <swift-evolution@swift.org> wrote:

On Apr 11, 2016, at 10:03, Dave Abrahams <dabrahams@apple.com> wrote:
on Sun Apr 10 2016, Dietmar Planitzer <dplanitzer-AT-q.com> wrote:

On Apr 10, 2016, at 11:46, Dave Abrahams via swift-evolution >>> <swift-evolution@swift.org> wrote:
on Sun Apr 10 2016, Dietmar Planitzer <swift-evolution@swift.org> >>> wrote: