[swift-evolution] Proposal: Universal dynamic dispatch for method calls

Paul Cantrell cantrell at pobox.com
Tue Dec 8 23:27:48 CST 2015


Syntactic details aside, I strongly suppose the general spirit of Brent’s proposal.

I’m not sure about @incoherent and all the details, but the general approach seems clearly correct to me: explicitness, clarity, and a simplified mental model that do not limit existing capabilities.

> How does this handle the case where the extension was not done in the
> same module that defined the protocol

Did Joe Groff not already address this early on in this thread?

Joe wrote:
> It's helpful to think of method names as being namespaced in Swift, by both their enclosing module and type. If two modules independently extend a protocol with a method of the same name, you still semantically have two distinct methods that dispatch independently. The extension would have to be factored into a common module both modules see for them to interact.


P


> On Dec 8, 2015, at 4:53 PM, Kevin Ballard <kevin at sb.org> wrote:
> 
> How does this handle the case where the extension was not done in the
> same module that defined the protocol, and the extension method's name
> is the same as a method on one of its implementing types (from the same
> module, or another module that can see the protocol but not the
> extension)? Calling the method via the protocol cannot possibly call the
> implementing type's version, it must call the default implementation in
> the extension, for two reasons:
> 
> 1. The module that defined the type doesn't know about the extension, so
> the method on the type was written without any knowledge of the
> extension's method and may not match the expected behavior for the
> protocol method.
> 2. There's no protocol witness table that can contain the extension
> method for that type, so even if #1 was irrelevant, there'd be no way to
> actually call it. The module that defined the type has no knowledge of
> the protocol extension, so it can't construct a protocol witness table.
> And the module that defined the extension doesn't know about the type
> (since it's in a different module).
> 
> The only way this could work is if the extension is in the same module
> as the protocol definition, or if the type that conforms to the protocol
> can also see the extension.
> 
> These same problems occur with an opt-in `dynamic` keyword for protocol
> extension methods, of course, but at least there the method is
> explicitly called-out as being special and so it's a little more
> reasonable to understand that it will only be overridable by types that
> can see the extension.
> 
> -Kevin Ballard
> 
> On Tue, Dec 8, 2015, at 01:51 PM, Brent Royal-Gordon wrote:
>>>> It seems
>>>> to me that, this case would be better served by some mechanism that makes
>>>> the non-override intent explicit at the point of declaration.
>>> 
>>> There is already such a mechanism. It's the fact that the declaration
>>> happens in an extension instead of in the original protocol.
>> 
>> No, what you’re saying is that there’s a simple rule governing which
>> declarations are dispatched statically. This does not make it *explicit*.
>> In the Swift 1 betas, there was a simple rule governing whether `if foo`
>> was a nil check (is `foo` of type Optional?), but this was deemed
>> insufficiently explicit and removed. Making the protocol extension
>> behaivor *explicit* would mean, for instance, forcing the user to mark
>> statically-dispatched protocol extension methods `final`, much like how
>> you have to use `override` purely to reassure the compiler that you mean
>> to do what your code says.
>> 
>> Given the tenor of the rest of the language, here’s how I would design
>> this:
>> 
>> 1. You can mark a protocol extension member as `final`. This means that,
>> when casted to the protocol’s type, all instances will use *this*
>> implementation, so it can safely be statically dispatched.
>> 
>> 	protocol Foo {
>> 		func bar()
>> 	}
>> 	extension Foo {
>> 		final func baz() { … }
>> 	}
>> 
>> 2. Currently, all protocol extension members which aren’t listed in the
>> protocol itself must be final:
>> 
>> 	protocol Foo {
>> 		func bar()
>> 	}
>> 	extension Foo {
>> 		func baz() { … }	// error: declaration must be ‘final’.
>> 	}
>> 
>> If a future version of the language were to allow overriding of extension
>> methods, this rule would go away, and use of final would be purely
>> voluntary.
>> 
>> 3. Attempting to make a type conform to a protocol that has `final`
>> members conflicting with its own causes an error:
>> 
>> 	class ConcreteFoo {
>> 		func baz() { … }
>> 	}
>> 	extension ConcreteFoo: Foo {	// error: Foo.baz() conflicts with ConcreteFoo.baz()
>> 		func bar() { ... }
>> 	}
>> 
>> 4. The preferred way to handle these conflicts is to rename one of the
>> members so they no longer conflict. However, if that isn’t possible, you
>> can use a keyword to acknowledge and silence the error, either at the
>> site of the conformance (preferred):
>> 
>> 	extension ConcreteFoo: @incoherent Foo {
>> 
>> Or at the site of the extension (as a fallback for when someone else
>> wrote the conformance):
>> 
>> 	@incoherent(ConcreteFoo) extension Foo {
>> 		final func baz() { … }
>> 	}
>> 
>> It may also be necessary to allow it at the site of an import:
>> 
>> 	import FooKit
>> 	import ConcreteFooKit
>> 	@incoherent(ConcreteFoo: Foo) import FooExtensions
>> 
>> I like “incoherent” because, to me, it expresses the symptom you’re going
>> to see (viewing the object as a Foo will not give you the same members as
>> viewing it as a ConcreteFoo). But @conflicting would be more obvious, and
>> of course we could bikeshed this endlessly, in what appears to be the
>> manner of our people.
>> 
>> -- 
>> Brent Royal-Gordon
>> Architechies
>> 



More information about the swift-evolution mailing list