Is there an underlying reason why optional protocol requirements need @objc?


(Simon Pilkington) #1

This seems like a strange/unrelated restriction on what is quite a useful feature.

-Simon


(Dave Abrahams) #2

...or an abomination. What sense does “optional requirement” make,
after all?!

···

on Fri Mar 04 2016, Simon Pilkington <swift-dev-AT-swift.org> wrote:

This seems like a strange/unrelated restriction on what is quite a useful feature.

--
-Dave


(Erica Sadun) #3

However oxymoronic, I kind of get the point. If you want default no-op behaviors, with
the option to override, which is what optional Objective-C requirements really are, just
create protocol extensions.

extension MyProtocol {
   func optionalMember() {} // nothing to do
}

This guarantees that the member exists, can be called by all consumers, but that there's a
default in place that frees the conforming type from actually having to implement anything
unless it really wants to.

-- E

···

On Mar 4, 2016, at 9:25 AM, Dave Abrahams via swift-dev <swift-dev@swift.org> wrote:

on Fri Mar 04 2016, Simon Pilkington <swift-dev-AT-swift.org> wrote:

This seems like a strange/unrelated restriction on what is quite a useful feature.

...or an abomination. What sense does “optional requirement” make,
after all?!


(Shawn Erickson) #4

Well one missed aspect is that the delegator can test if the delegate
responds to the method and modify its logic to adjust to the reality (which
could have reasonable optimization hanging off it). If you depend on
default no-op you don't get that ability (at least as "easily"). ...it of
course is intertwined with the runtime capabilities of objective-c.

···

On Fri, Mar 4, 2016 at 9:16 AM Erica Sadun via swift-dev < swift-dev@swift.org> wrote:

> On Mar 4, 2016, at 9:25 AM, Dave Abrahams via swift-dev < > swift-dev@swift.org> wrote:
>
>
> on Fri Mar 04 2016, Simon Pilkington <swift-dev-AT-swift.org> wrote:
>
>> This seems like a strange/unrelated restriction on what is quite a
useful feature.
>
> ...or an abomination. What sense does “optional requirement” make,
> after all?!
>

However oxymoronic, I kind of get the point. If you want default no-op
behaviors, with
the option to override, which is what optional Objective-C requirements
really are, just
create protocol extensions.

extension MyProtocol {
   func optionalMember() {} // nothing to do
}

This guarantees that the member exists, can be called by all consumers,
but that there's a
default in place that frees the conforming type from actually having to
implement anything
unless it really wants to.

-- E

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


(Dave Abrahams) #5

Well one missed aspect is that the delegator can test if the delegate
responds to the method and modify its logic to adjust to the reality (which
could have reasonable optimization hanging off it). If you depend on
default no-op you don't get that ability (at least as "easily"). ...it of
course is intertwined with the runtime capabilities of objective-c.

All that testing puts too much burden on the client, IMO. It's much
better to provide customization points with default behaviors that work
sensibly when not overridden.

···

on Fri Mar 04 2016, Shawn Erickson <shawnce-AT-gmail.com> wrote:

On Fri, Mar 4, 2016 at 9:16 AM Erica Sadun via swift-dev < > swift-dev@swift.org> wrote:

> On Mar 4, 2016, at 9:25 AM, Dave Abrahams via swift-dev < >> swift-dev@swift.org> wrote:
>
>
> on Fri Mar 04 2016, Simon Pilkington <swift-dev-AT-swift.org> wrote:
>
>> This seems like a strange/unrelated restriction on what is quite a
useful feature.
>
> ...or an abomination. What sense does “optional requirement” make,
> after all?!
>

However oxymoronic, I kind of get the point. If you want default no-op
behaviors, with
the option to override, which is what optional Objective-C requirements
really are, just
create protocol extensions.

extension MyProtocol {
   func optionalMember() {} // nothing to do
}

This guarantees that the member exists, can be called by all consumers,
but that there's a
default in place that frees the conforming type from actually having to
implement anything
unless it really wants to.

-- E

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

--
-Dave


(Shawn Erickson) #6

(Sorry I hate top posting but fighting with Google inbox to avoid it)

In delegation patterns it can be very helpful in terms of optimization
(performance and memory) if the delegator can interrogate a delegate to
know if certain delegation points are needed or not. I have code that has
done this interrogation at delegate registration allowing potentially
complex code paths and/or state maintenance to be avoided. It also could do
impl caching to improve dispatch (did not support after registration
"reconfiguring" delegates in this model purposely).

Under objective-c leveraging optional protocol methods and runtime checks
for responds to those was one built-in way for that. It also could add
boilerplate code to check before dispatch that was potentially error prone
and cumbersome. ...hence why I often did the check and configuration at
registration in my code often avoiding peppering code with dispatch checks.

Anyway not attempting to state anything for against this question about
optional requirements in swift protocols. ...a handful of reasonable
patterns exist in swift that allows one to achieve the same thing in safer
and likely more performant ways.

-Shawn

···

On Fri, Mar 4, 2016 at 9:38 AM Dave Abrahams <dabrahams@apple.com> wrote:

on Fri Mar 04 2016, Shawn Erickson <shawnce-AT-gmail.com> wrote:

> Well one missed aspect is that the delegator can test if the delegate
> responds to the method and modify its logic to adjust to the reality
(which
> could have reasonable optimization hanging off it). If you depend on
> default no-op you don't get that ability (at least as "easily"). ...it of
> course is intertwined with the runtime capabilities of objective-c.

All that testing puts too much burden on the client, IMO. It's much
better to provide customization points with default behaviors that work
sensibly when not overridden.

>
> On Fri, Mar 4, 2016 at 9:16 AM Erica Sadun via swift-dev < > > swift-dev@swift.org> wrote:
>
>>
>> > On Mar 4, 2016, at 9:25 AM, Dave Abrahams via swift-dev < > >> swift-dev@swift.org> wrote:
>> >
>> >
>> > on Fri Mar 04 2016, Simon Pilkington <swift-dev-AT-swift.org> wrote:
>> >
>> >> This seems like a strange/unrelated restriction on what is quite a
>> useful feature.
>> >
>> > ...or an abomination. What sense does “optional requirement” make,
>> > after all?!
>> >
>>
>> However oxymoronic, I kind of get the point. If you want default no-op
>> behaviors, with
>> the option to override, which is what optional Objective-C requirements
>> really are, just
>> create protocol extensions.
>>
>> extension MyProtocol {
>> func optionalMember() {} // nothing to do
>> }
>>
>> This guarantees that the member exists, can be called by all consumers,
>> but that there's a
>> default in place that frees the conforming type from actually having to
>> implement anything
>> unless it really wants to.
>>
>> -- E
>>
>>
>> _______________________________________________
>> swift-dev mailing list
>> swift-dev@swift.org
>> https://lists.swift.org/mailman/listinfo/swift-dev
>>

--
-Dave


(John McCall) #7

(Sorry I hate top posting but fighting with Google inbox to avoid it)

In delegation patterns it can be very helpful in terms of optimization (performance and memory) if the delegator can interrogate a delegate to know if certain delegation points are needed or not. I have code that has done this interrogation at delegate registration allowing potentially complex code paths and/or state maintenance to be avoided. It also could do impl caching to improve dispatch (did not support after registration "reconfiguring" delegates in this model purposely).

Under objective-c leveraging optional protocol methods and runtime checks for responds to those was one built-in way for that. It also could add boilerplate code to check before dispatch that was potentially error prone and cumbersome. ...hence why I often did the check and configuration at registration in my code often avoiding peppering code with dispatch checks.

Anyway not attempting to state anything for against this question about optional requirements in swift protocols. ...a handful of reasonable patterns exist in swift that allows one to achieve the same thing in safer and likely more performant ways.

Folks, this is obviously a language design discussion. Please do this sort of thing on swift-evolution. I’ve moved swift-dev to BCC.

John.

···

On Mar 4, 2016, at 10:09 AM, Shawn Erickson via swift-dev <swift-dev@swift.org> wrote:

-Shawn

On Fri, Mar 4, 2016 at 9:38 AM Dave Abrahams <dabrahams@apple.com <mailto:dabrahams@apple.com>> wrote:

on Fri Mar 04 2016, Shawn Erickson <shawnce-AT-gmail.com> wrote:

> Well one missed aspect is that the delegator can test if the delegate
> responds to the method and modify its logic to adjust to the reality (which
> could have reasonable optimization hanging off it). If you depend on
> default no-op you don't get that ability (at least as "easily"). ...it of
> course is intertwined with the runtime capabilities of objective-c.

All that testing puts too much burden on the client, IMO. It's much
better to provide customization points with default behaviors that work
sensibly when not overridden.

>
> On Fri, Mar 4, 2016 at 9:16 AM Erica Sadun via swift-dev < > > swift-dev@swift.org <mailto:swift-dev@swift.org>> wrote:
>
>>
>> > On Mar 4, 2016, at 9:25 AM, Dave Abrahams via swift-dev < > >> swift-dev@swift.org <mailto:swift-dev@swift.org>> wrote:
>> >
>> >
>> > on Fri Mar 04 2016, Simon Pilkington <swift-dev-AT-swift.org> wrote:
>> >
>> >> This seems like a strange/unrelated restriction on what is quite a
>> useful feature.
>> >
>> > ...or an abomination. What sense does “optional requirement” make,
>> > after all?!
>> >
>>
>> However oxymoronic, I kind of get the point. If you want default no-op
>> behaviors, with
>> the option to override, which is what optional Objective-C requirements
>> really are, just
>> create protocol extensions.
>>
>> extension MyProtocol {
>> func optionalMember() {} // nothing to do
>> }
>>
>> This guarantees that the member exists, can be called by all consumers,
>> but that there's a
>> default in place that frees the conforming type from actually having to
>> implement anything
>> unless it really wants to.
>>
>> -- E
>>
>>
>> _______________________________________________
>> swift-dev mailing list
>> swift-dev@swift.org <mailto:swift-dev@swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-dev
>>

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


(Joe Groff) #8

Even without language support, you can model optional protocol conformances as optional property requirements, with pretty much the exact same behavior on the user side that we give officially optional requirements:

protocol OptionalMethod {
  var optionalMethod: Optional<() -> ()> { get }
}

-Joe

···

On Mar 4, 2016, at 10:49 AM, John McCall via swift-evolution <swift-evolution@swift.org> wrote:

On Mar 4, 2016, at 10:09 AM, Shawn Erickson via swift-dev <swift-dev@swift.org <mailto:swift-dev@swift.org>> wrote:
(Sorry I hate top posting but fighting with Google inbox to avoid it)

In delegation patterns it can be very helpful in terms of optimization (performance and memory) if the delegator can interrogate a delegate to know if certain delegation points are needed or not. I have code that has done this interrogation at delegate registration allowing potentially complex code paths and/or state maintenance to be avoided. It also could do impl caching to improve dispatch (did not support after registration "reconfiguring" delegates in this model purposely).

Under objective-c leveraging optional protocol methods and runtime checks for responds to those was one built-in way for that. It also could add boilerplate code to check before dispatch that was potentially error prone and cumbersome. ...hence why I often did the check and configuration at registration in my code often avoiding peppering code with dispatch checks.

Anyway not attempting to state anything for against this question about optional requirements in swift protocols. ...a handful of reasonable patterns exist in swift that allows one to achieve the same thing in safer and likely more performant ways.

Folks, this is obviously a language design discussion. Please do this sort of thing on swift-evolution. I’ve moved swift-dev to BCC.


(Brent Royal-Gordon) #9

In delegation patterns it can be very helpful in terms of optimization (performance and memory) if the delegator can interrogate a delegate to know if certain delegation points are needed or not. I have code that has done this interrogation at delegate registration allowing potentially complex code paths and/or state maintenance to be avoided.

You can always add another delegation point along the lines of `shouldDoTheExpensiveThing()`. Or introduce a FooDelegateWithExpensiveThing protocol which conforms to FooDelegate, and test at runtime to see if your delegate is a FooDelegateWithExpensiveThing.

···

--
Brent Royal-Gordon
Architechies


(Douglas Gregor) #10

(Sorry I hate top posting but fighting with Google inbox to avoid it)

In delegation patterns it can be very helpful in terms of optimization (performance and memory) if the delegator can interrogate a delegate to know if certain delegation points are needed or not. I have code that has done this interrogation at delegate registration allowing potentially complex code paths and/or state maintenance to be avoided. It also could do impl caching to improve dispatch (did not support after registration "reconfiguring" delegates in this model purposely).

Under objective-c leveraging optional protocol methods and runtime checks for responds to those was one built-in way for that. It also could add boilerplate code to check before dispatch that was potentially error prone and cumbersome. ...hence why I often did the check and configuration at registration in my code often avoiding peppering code with dispatch checks.

Anyway not attempting to state anything for against this question about optional requirements in swift protocols. ...a handful of reasonable patterns exist in swift that allows one to achieve the same thing in safer and likely more performant ways.

Folks, this is obviously a language design discussion. Please do this sort of thing on swift-evolution. I’ve moved swift-dev to BCC.

Even without language support, you can model optional protocol conformances as optional property requirements, with pretty much the exact same behavior on the user side that we give officially optional requirements:

protocol OptionalMethod {
  var optionalMethod: Optional<() -> ()> { get }
}

One issue with this modeling is argument labels. You can't really have a bunch of properties name "tableView".

···

Sent from my iPhone

On Mar 4, 2016, at 1:24 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Mar 4, 2016, at 10:49 AM, John McCall via swift-evolution <swift-evolution@swift.org> wrote:
On Mar 4, 2016, at 10:09 AM, Shawn Erickson via swift-dev <swift-dev@swift.org> wrote:

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


(Dave Abrahams) #11

(Sorry I hate top posting but fighting with Google inbox to avoid it)

In delegation patterns it can be very helpful in terms of optimization
(performance and memory) if the delegator can interrogate a delegate to
know if certain delegation points are needed or not. I have code that has
done this interrogation at delegate registration allowing potentially
complex code paths and/or state maintenance to be avoided.

There's always a way to do this kind of thing without making the
requirement itself optional. For example, you can wrap the return type
in an Optional and have the default implementation always return nil, or
you can have the method accept a closure passed by the caller that
represents the extra work one would have to do in case the “optional
requirement” were supplied, and make sure it's called in any non-default
implementation, etc.

It also could do impl caching to improve dispatch (did not support
after registration "reconfiguring" delegates in this model purposely).

Under objective-c leveraging optional protocol methods and runtime checks
for responds to those was one built-in way for that. It also could add
boilerplate code to check before dispatch that was potentially error prone
and cumbersome. ...hence why I often did the check and configuration at
registration in my code often avoiding peppering code with dispatch checks.

Not sure what you have in mind in any of the text above; I think I'd
need to see examples.

Anyway not attempting to state anything for against this question about
optional requirements in swift protocols. ...a handful of reasonable
patterns exist in swift that allows one to achieve the same thing in safer
and likely more performant ways.

IMO “optional requirements” only express things that can already be
expressed in other ways, add complexity to the language, weaken the
concept of what a protocol is, and worst of all, encourage the creation
of weak abstractions. I don't doubt they've been useful to people in
the past but that doesn't mean they should be in the language.

It sounds on the surface like maybe you don't disagree with me here, in
which case there's not much more to say about it(?)

···

on Fri Mar 04 2016, Shawn Erickson <swift-dev-AT-swift.org> wrote:

-Shawn

On Fri, Mar 4, 2016 at 9:38 AM Dave Abrahams <dabrahams@apple.com> wrote:

on Fri Mar 04 2016, Shawn Erickson <shawnce-AT-gmail.com> wrote:

> Well one missed aspect is that the delegator can test if the delegate
> responds to the method and modify its logic to adjust to the reality
(which
> could have reasonable optimization hanging off it). If you depend on
> default no-op you don't get that ability (at least as "easily"). ...it of
> course is intertwined with the runtime capabilities of objective-c.

All that testing puts too much burden on the client, IMO. It's much
better to provide customization points with default behaviors that work
sensibly when not overridden.

>
> On Fri, Mar 4, 2016 at 9:16 AM Erica Sadun via swift-dev < >> > swift-dev@swift.org> wrote:
>
>>
>> > On Mar 4, 2016, at 9:25 AM, Dave Abrahams via swift-dev < >> >> swift-dev@swift.org> wrote:
>> >
>> >
>> > on Fri Mar 04 2016, Simon Pilkington <swift-dev-AT-swift.org> wrote:
>> >
>> >> This seems like a strange/unrelated restriction on what is quite a
>> useful feature.
>> >
>> > ...or an abomination. What sense does “optional requirement” make,
>> > after all?!
>> >
>>
>> However oxymoronic, I kind of get the point. If you want default no-op
>> behaviors, with
>> the option to override, which is what optional Objective-C requirements
>> really are, just
>> create protocol extensions.
>>
>> extension MyProtocol {
>> func optionalMember() {} // nothing to do
>> }
>>
>> This guarantees that the member exists, can be called by all consumers,
>> but that there's a
>> default in place that frees the conforming type from actually having to
>> implement anything
>> unless it really wants to.
>>
>> -- E
>>
>>
>> _______________________________________________
>> swift-dev mailing list
>> swift-dev@swift.org
>> https://lists.swift.org/mailman/listinfo/swift-dev
>>

--
-Dave

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

--
-Dave


(Dietmar Planitzer) #12

I’ve played around with your idea a bit and I have some questions (please see below). Here is the code that I wrote up in playground (but I also ran all experiments outside the playground):

protocol Delegate {
    var optionalMethod: Optional<() -> Int> { get }
}

class Foo {
    
    var delegate : Delegate?
    
    func doIt() {
        if let d = delegate {
            let i: Int = d.optionalMethod?() ?? 0
            
            print(i)
        }
    }
}

class Bar : Delegate {
    
    init() {
// optionalMethod = { self.p } [A]
    }
    
    deinit {
        print("dead")
    }
    
    var p = 1
    
// var optionalMethod = Optional({ return 1 })

    var optionalMethod: Optional<() -> Int> = nil
}

class Bar1 : Bar {
    
// override var optionalMethod: Optional<() -> Int> = { 1 }
    
}

do {
    let f = Foo()
    f.delegate = Bar()
    f.doIt()
}

Assuming that this is what you had in mind as a replacement for optional protocol conformances, here are some questions:

1) How would forward compatibility work with this approach? Eg we may create a class or struct as part of our v1 framework API and the class/struct may support a delegate protocol with a non-optional method. But based on feedback from the field we want to change the non-optional delegate method to an optional one in v2. The goal is to create a new version of the framework which will not break existing apps which were built against the v1 API. To me it looks like that I could not do this with your approach without breaking existing apps since the v1 protocol definition:

protocol Delegate {
    var foo: Optional<() -> Int> { get }
}

would change to this:

protocol Delegate {
    var foo: Int { get }
}

which implies that the call site needs to be different. The same problem exists the other way around: an optional requirement may change to a non-optional requirement. The extra complication here is that the delegating entity needs to be able to treat a method which is declared as non-optional in the protocol, as effectively optional at runtime in order to provide backward compatibility until the optionality can be phased out for good.

2) The example above shows class Bar, which adopts the delegate protocol and class Bar1 which subclasses Bar. Now Bar1 wants to override the “optionalMethod” in order to return a different value. But overriding doesn’t work because “optionalMethod” in Bar is now a stored property. To make this more concrete, assume that we are developing a media app for a mobile device and that the UI of this app is organized into tabs. Eg one tab for videos, another one for things that you shared, another one for folders, etc. All those pages have the same underlying principle in common: set up a db query, run the query asynchronously, wait for the query result while managing a progress spinner and finally post-process the results and show them in a table view. One of the things we want to support is drag & drop. The drag & drop delegation methods are all optional conformances and our common base view controller provides a generic implementation which makes sense for 90% of all pages. It’s just 1 or 2 pages that want to do drag & drop a bit differently and thus the view controller subclasses for those tabs should be able to just override the inherited d & d methods (the optional methods).

In your model, the only way I see this kind of work is when I do [A] (see code above) and the subclass would store different closures in the property. But this then introduces other issues.

Here are my thoughts and observations on this:

a) I have a problem with the inconsistency that this approach introduces when you compare how to adopt a non-optional requirement vs an optional requirement:

protocol Delegate {
   var requiredProperty: Int { get}
}

class Foo : Delegate {

   var requiredProperty = 1

}

So things are simple and intuitive if the method / property is non-optional. But if it is optional then I need to write this:

protocol Delegate {
   var optionalProperty: Optional<() -> Int> { get}
}

class Foo : Delegate {

   var optionalProperty = Optional({1})

}

or this:

class Foo : Delegate {

   var optionalProperty: Optional<() -> Int> = {1}

}

I really do not like that we put the burden of adopting optional methods on the adopter rather than the implementor of the delegating class. For every hour that is spent on implementing the delegating class, potentially hundreds and more hours will be spent writing code that adopts the delegate (think framework provided delegate protocols). So it clearly makes more sense to put the burden on the implementor of the delegating class rather than the individual adopters.

b) the solution doesn’t scale well beyond trivial code because you can not simply write this:

class Foo : Delegate {

    var value = 1

    var optionalMethod = Optional({ value })
}

because “self” in the closure does not refer to Foo. But even if it would, you would have to prefix “value” with “self” because it’s now inside a closure rather than a regular method / property body. Now one possible workaround for this would be this:

class Foo : Delegate {

   init() {
      optionalMethod = { self.value}
   }
}

which however puts code that has nothing got to do with initialization inside the initializer.

c) it makes it easy to create retain cycles because the optional method is now a closure. The adopter object has a strong reference to the closure. If the closure now captures the adopter’s self, and we forget to mark the capture as unowned, then we’ve created a retain cycle and the adopter object won’t get freed. You can uncomment [A] in the example above to see this problem.

d) so given (b) and (c) maybe we can rewrite the optional method like this:

class Foo : Delegate {

   init() {
      optionalMethod = optionalMethodImpl
   }

   var optionalMethod: Optional<() -> Int> = nil

   func optionalMethodImpl() -> Int {
      return 1
   }
}

which fixes (b) but apparently doesn’t fix (c) since the Foo object still does not get deallocated.

Overall I prefer ObjC’s solution for optional protocol requirements over this because it would:

I) allow us to ensure that we can provide forward compatibility exactly the way we need it without compromising performance or safety

II) give us syntax that is easy to understand for the language user, is consistent with non-optional requirements and does not force us to compromise the design of our APIs

III) because of (II) consistency between fulfilling non-optional and optional requirements, it is less likely to write buggy code and refactoring from optional to non-optional and the other way around is less time consuming and safer

Regards,

Dietmar Planitzer

···

On Mar 4, 2016, at 13:24, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Mar 4, 2016, at 10:49 AM, John McCall via swift-evolution <swift-evolution@swift.org> wrote:

On Mar 4, 2016, at 10:09 AM, Shawn Erickson via swift-dev <swift-dev@swift.org> wrote:
(Sorry I hate top posting but fighting with Google inbox to avoid it)

In delegation patterns it can be very helpful in terms of optimization (performance and memory) if the delegator can interrogate a delegate to know if certain delegation points are needed or not. I have code that has done this interrogation at delegate registration allowing potentially complex code paths and/or state maintenance to be avoided. It also could do impl caching to improve dispatch (did not support after registration "reconfiguring" delegates in this model purposely).

Under objective-c leveraging optional protocol methods and runtime checks for responds to those was one built-in way for that. It also could add boilerplate code to check before dispatch that was potentially error prone and cumbersome. ...hence why I often did the check and configuration at registration in my code often avoiding peppering code with dispatch checks.

Anyway not attempting to state anything for against this question about optional requirements in swift protocols. ...a handful of reasonable patterns exist in swift that allows one to achieve the same thing in safer and likely more performant ways.

Folks, this is obviously a language design discussion. Please do this sort of thing on swift-evolution. I’ve moved swift-dev to BCC.

Even without language support, you can model optional protocol conformances as optional property requirements, with pretty much the exact same behavior on the user side that we give officially optional requirements:

protocol OptionalMethod {
  var optionalMethod: Optional<() -> ()> { get }
}

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


(Joe Groff) #13

We could conceivably allow for properties with closure types to have compound names. That would also benefit the optional binding `if let tableView(_:blah:) = delegate.tableView(_:blah:) { ... }`.

-Joe

···

On Mar 4, 2016, at 8:32 PM, Douglas Gregor <dgregor@apple.com> wrote:

Sent from my iPhone

On Mar 4, 2016, at 1:24 PM, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Mar 4, 2016, at 10:49 AM, John McCall via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Mar 4, 2016, at 10:09 AM, Shawn Erickson via swift-dev <swift-dev@swift.org <mailto:swift-dev@swift.org>> wrote:
(Sorry I hate top posting but fighting with Google inbox to avoid it)

In delegation patterns it can be very helpful in terms of optimization (performance and memory) if the delegator can interrogate a delegate to know if certain delegation points are needed or not. I have code that has done this interrogation at delegate registration allowing potentially complex code paths and/or state maintenance to be avoided. It also could do impl caching to improve dispatch (did not support after registration "reconfiguring" delegates in this model purposely).

Under objective-c leveraging optional protocol methods and runtime checks for responds to those was one built-in way for that. It also could add boilerplate code to check before dispatch that was potentially error prone and cumbersome. ...hence why I often did the check and configuration at registration in my code often avoiding peppering code with dispatch checks.

Anyway not attempting to state anything for against this question about optional requirements in swift protocols. ...a handful of reasonable patterns exist in swift that allows one to achieve the same thing in safer and likely more performant ways.

Folks, this is obviously a language design discussion. Please do this sort of thing on swift-evolution. I’ve moved swift-dev to BCC.

Even without language support, you can model optional protocol conformances as optional property requirements, with pretty much the exact same behavior on the user side that we give officially optional requirements:

protocol OptionalMethod {
  var optionalMethod: Optional<() -> ()> { get }
}

One issue with this modeling is argument labels. You can't really have a bunch of properties name "tableView".


(Joe Groff) #14

I’ve played around with your idea a bit and I have some questions (please see below). Here is the code that I wrote up in playground (but I also ran all experiments outside the playground):

protocol Delegate {
   var optionalMethod: Optional<() -> Int> { get }
}

class Foo {

   var delegate : Delegate?

   func doIt() {
       if let d = delegate {
           let i: Int = d.optionalMethod?() ?? 0

           print(i)
       }
   }
}

class Bar : Delegate {

   init() {
// optionalMethod = { self.p } [A]
   }

   deinit {
       print("dead")
   }

   var p = 1

// var optionalMethod = Optional({ return 1 })

   var optionalMethod: Optional<() -> Int> = nil
}

class Bar1 : Bar {

// override var optionalMethod: Optional<() -> Int> = { 1 }

}

do {
   let f = Foo()
   f.delegate = Bar()
   f.doIt()
}

Assuming that this is what you had in mind as a replacement for optional protocol conformances, here are some questions:

1) How would forward compatibility work with this approach? Eg we may create a class or struct as part of our v1 framework API and the class/struct may support a delegate protocol with a non-optional method. But based on feedback from the field we want to change the non-optional delegate method to an optional one in v2. The goal is to create a new version of the framework which will not break existing apps which were built against the v1 API. To me it looks like that I could not do this with your approach without breaking existing apps since the v1 protocol definition:

protocol Delegate {
   var foo: Optional<() -> Int> { get }
}

would change to this:

protocol Delegate {
   var foo: Int { get }
}

which implies that the call site needs to be different. The same problem exists the other way around: an optional requirement may change to a non-optional requirement. The extra complication here is that the delegating entity needs to be able to treat a method which is declared as non-optional in the protocol, as effectively optional at runtime in order to provide backward compatibility until the optionality can be phased out for good.

You can't resiliently change a required protocol requirement to an optional one, since this would break existing clients of the protocol that assume the existence of the method. You could at best add a default implementation.

2) The example above shows class Bar, which adopts the delegate protocol and class Bar1 which subclasses Bar. Now Bar1 wants to override the “optionalMethod” in order to return a different value. But overriding doesn’t work because “optionalMethod” in Bar is now a stored property. To make this more concrete, assume that we are developing a media app for a mobile device and that the UI of this app is organized into tabs. Eg one tab for videos, another one for things that you shared, another one for folders, etc. All those pages have the same underlying principle in common: set up a db query, run the query asynchronously, wait for the query result while managing a progress spinner and finally post-process the results and show them in a table view. One of the things we want to support is drag & drop. The drag & drop delegation methods are all optional conformances and our common base view controller provides a generic implementation which makes sense for 90% of all pages. It’s just 1 or 2 pages that want to do drag & drop a bit differently and thus the view controller subclasses for those tabs should be able to just override the inherited d & d methods (the optional methods).

In your model, the only way I see this kind of work is when I do [A] (see code above) and the subclass would store different closures in the property. But this then introduces other issues.

Here are my thoughts and observations on this:

a) I have a problem with the inconsistency that this approach introduces when you compare how to adopt a non-optional requirement vs an optional requirement:

protocol Delegate {
  var requiredProperty: Int { get}
}

class Foo : Delegate {

  var requiredProperty = 1

}

So things are simple and intuitive if the method / property is non-optional. But if it is optional then I need to write this:

protocol Delegate {
  var optionalProperty: Optional<() -> Int> { get}
}

class Foo : Delegate {

  var optionalProperty = Optional({1})

}

or this:

class Foo : Delegate {

  var optionalProperty: Optional<() -> Int> = {1}

}

I really do not like that we put the burden of adopting optional methods on the adopter rather than the implementor of the delegating class. For every hour that is spent on implementing the delegating class, potentially hundreds and more hours will be spent writing code that adopts the delegate (think framework provided delegate protocols). So it clearly makes more sense to put the burden on the implementor of the delegating class rather than the individual adopters.

In principle it could be possible to satisfy get-only property requirements with `func` declarations, and vice versa.

b) the solution doesn’t scale well beyond trivial code because you can not simply write this:

class Foo : Delegate {

   var value = 1

   var optionalMethod = Optional({ value })
}

because “self” in the closure does not refer to Foo. But even if it would, you would have to prefix “value” with “self” because it’s now inside a closure rather than a regular method / property body. Now one possible workaround for this would be this:

class Foo : Delegate {

  init() {
     optionalMethod = { self.value}
  }
}

which however puts code that has nothing got to do with initialization inside the initializer.

c) it makes it easy to create retain cycles because the optional method is now a closure. The adopter object has a strong reference to the closure. If the closure now captures the adopter’s self, and we forget to mark the capture as unowned, then we’ve created a retain cycle and the adopter object won’t get freed. You can uncomment [A] in the example above to see this problem.

d) so given (b) and (c) maybe we can rewrite the optional method like this:

class Foo : Delegate {

  init() {
     optionalMethod = optionalMethodImpl
  }

  var optionalMethod: Optional<() -> Int> = nil

  func optionalMethodImpl() -> Int {
     return 1
  }
}

which fixes (b) but apparently doesn’t fix (c) since the Foo object still does not get deallocated.

Overall I prefer ObjC’s solution for optional protocol requirements over this because it would:

I) allow us to ensure that we can provide forward compatibility exactly the way we need it without compromising performance or safety

II) give us syntax that is easy to understand for the language user, is consistent with non-optional requirements and does not force us to compromise the design of our APIs

III) because of (II) consistency between fulfilling non-optional and optional requirements, it is less likely to write buggy code and refactoring from optional to non-optional and the other way around is less time consuming and safer

Fair points.

-Joe

···

On Mar 6, 2016, at 2:57 PM, Dietmar Planitzer <dplanitzer@q.com> wrote: