On Tue, Dec 8, 2015 at 01:42 Paul Cantrell <cantrell@pobox.com <javascript:_e(%7B%7D,'cvml','cantrell@pobox.com');>> wrote:
I must admit I'm having difficulty understanding why it's a big deal
whether the dispatch will be static or dynamic. This seems like an
implementation detail; any "dynamic dispatch" in the aforementioned sense
can actually became static for a final class.
It’s not just an implementation detail. The original article demonstrates
this compellingly, I think.
Yes, it’s true that, as an optimization, the compiler can choose to use
static dispatch in situations where doing so makes no difference (e.g.
calling a final method, calling a private method with no overrides, etc.).
However, there are situations where static dispatch changes the behavior
of the code. At that point, it’s a semantic difference. It’s those cases
I’m concerned about.
Just think about function defined as having a "magic prefix" that
corresponds technically to vtable where they can be located:
It’s not that it’s hard to understand what’s happening if you *already
know* that a call uses static dispatch. The problem is that it’s
difficult to determine *whether it does*.
• • •
Note that your example code with C_f and P_f does not demonstrate the
problem at hand. It’s worth working through why.
Translating your pseudocode into actual Swift, this does not compile:
class C {
func f() {
print("C_f")
}
}
protocol P {
func f()
}
extension C: P {
func f() { // compiler error here
print("P_f")
}
}
I imagine that you were thinking of something along these lines:
class C {
func f() {
print("C_f")
}
}
protocol P {
func f() // remember this line
}
extension P {
func f() {
print("P_f")
}
}
extension C: P { } // C: P now separate from extension impl of f()
However, this does not behave as you think it does:
(C() as C).f() // C_f
(C() as P).f() // C_f
However *again*, if you remove the line marked “remember this line,”
*then* the code does do what you think it does:
(C() as C).f() // C_f
(C() as P).f() // P_f
I’d say that if you got confused in the course of explaining how it's not
confusing … well, that’s pretty good evidence that it is indeed confusing.
Cheers,
Paul
On Dec 7, 2015, at 3:56 PM, ilya <ilya.nikokoshev@gmail.com > <javascript:_e(%7B%7D,'cvml','ilya.nikokoshev@gmail.com');>> wrote:
On Mon, Dec 7, 2015 at 7:17 AM, Paul Cantrell via swift-evolution < > swift-evolution@swift.org > <javascript:_e(%7B%7D,'cvml','swift-evolution@swift.org');>> wrote:
One of the few things in Swift 2 that feels to me like a design flaw is
the way Swift mixes static and dynamic method dispatch.
Alexandros Salazar gives an excellent explanation of this problem — and I
agree wholeheartedly with his title for the article:
The Ghost of Swift Bugs Future
The upshot is that when we see this:
foo.bar()
…it’s very hard to know how the compiler will determine which
implementation of bar() to use. It might use static dispatch; it might use
dynamic dispatch.
The rules that govern this are arcane, and hard to remember. They have
the feeling of being a “gotcha” question for job interviews — always a red
flag for language features.
Even if you remember the rules, the information needed to determine
whether dispatch is static or dynamic is hard to track down. It depends on
whether bar()’s implementation comes from an extension, whether the
extension method appeared on the extended protocol, and whether the
inferred type of foo is the protocol itself or an implementing type.
A crucial part of the meaning of “foo.bar()” is implicit, and hard to
determine.
I must admit I'm having difficulty understanding why it's a big deal
whether the dispatch will be static or dynamic. This seems like an
implementation detail; any "dynamic dispatch" in the aforementioned sense
can actually became static for a final class.
I understand there can be a confusion about the method called when the
protocol contains a method implementation, but there are some simple ways
to understand why things work as they do. Just think about function defined
as having a "magic prefix" that corresponds technically to vtable where
they can be located:
class C {
func C_f // declares C.C_f
}
protocol P {
func P_f
}
// implementation of P.P_f
extension C:P {
// declares that C.C_f = C.P_f
}
(C() as C).f -> calls C.C_f = C.P_f
(C() as P).f -> calls P.P_f
This runs contrary to Swift’s stated goal of prioritizing clarity at the
point of API use, and its general pattern of making intent explicit. And it
feels dangerous — a wellspring of insidious bugs.
Thus:
PROPOSAL
Make the syntax “foo.bar()” always use dynamic dispatch, i.e. always use
_only_ the runtime type of foo to determine which implementation of bar()
to use. If an extension method collision occurs when a type implements
multiple protocols, require the type to explicitly specify which one to use
(as Swift already requires the caller to do at the point of invocation).
I mean this proposal somewhat as a strawman. It’s such an obvious choice,
I’m sure there were good reasons not to do it. But I’d like to propose the
obvious solution in order to understand what’s wrong with it. I realize
static dispatch precludes some optimizations, but I doubt that this alone
drove the design choice. I see no safety or expressiveness upside to the
way it works now.
Cheers,
Paul
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
<javascript:_e(%7B%7D,'cvml','swift-evolution@swift.org');>
https://lists.swift.org/mailman/listinfo/swift-evolution