The three printed lines are:
g in A, f in C
h in B, f in A
k in A, h in B, f in AThe inherited
A.g makes an ordinary virtual call to f, selecting C.f by dynamic dispatch. B.h instead invokes super.f, which selects the next implementation after B, namely A.f. Finally, A.k makes an ordinary virtual call to h, selecting B.h and its already determined result. A super call is not redirected back to the most-derived override.Ignoring the predefined universal bases, the Scala trait linearizations are
D1, B2, B1, A and D2, B1, B2, A. The rightmost mixed-in trait is searched first, while the shared base occurs only once.The output is:
h in B2, g in B1, f in A
h in B1, g in B2, f in AFor
D1, h is the B2 implementation. Its super.g continues after B2 to B1.g; that method's super.f continues after B1 to A.f. The second line follows the reversed mixin order. In particular, a trait's unqualified super is interpreted in the concrete receiver's linearization, not by assuming the trait always sees only its textual parent.[ref]The printed lines are:
f with X at M
f with Y at N
f with Y at NMethod overloading first uses static types. The receiver
m has static type M, whose available signatures are f(X) and f(Y). Argument x selects f(X); y selects f(Y); and z, statically Z, also selects f(Y) because that is the most specific available applicable signature. N.f(Z) is not available through the static receiver type.Only after a signature is selected does dynamic dispatch choose an override for the runtime receiver
N. That class overrides f(Y) but not f(X), explaining all three lines. The shared argument object's runtime class does not trigger a second overload-resolution pass.Replace the overloaded entry family by one forwarding method and introduce the second dispatch explicitly:
class X {
def dispatch(to: M): String = to.visit(this)
}
class Y extends X {
override def dispatch(to: M): String = to.visit(this)
}
class Z extends Y {
override def dispatch(to: M): String = to.visit(this)
}
class M {
def f(x: X): String = x.dispatch(this)
def visit(x: X): String = "f with X at M"
def visit(x: Y): String = "f with Y at M"
def visit(x: Z): String = "f with Z at M"
}
class N extends M {
override def visit(x: X): String = "f with X at N"
override def visit(x: Y): String = "f with Y at N"
override def visit(x: Z): String = "f with Z at N"
}The original three calls still type-check because all their arguments are subtypes of
X. They all refer to the same runtime Z object already; the change is how the operation discovers that runtime type. The first virtual call selects Z.dispatch, where this has static type Z, so overload resolution selects visit(Z). The second virtual call selects N.visit(Z) from the receiver's runtime type. Thus double dispatch gives f with Z at N on all three lines. Articles by others on the same topic
There are currently no matching articles.