An ordinary call such as the unqualified f inside A.g uses dynamic dispatch on the receiver. Thus c.g enters A.g, but its call to f reaches C.f. In contrast, B.h explicitly invokes super.f, which continues after B and therefore reaches A.f. Finally A.k dynamically dispatches its call to h, reaching B.h again. The output is
g in A, f in C
h in B, f in A
k in A, h in B, f in A
Hence
These arrows describe the method bodies entered in order, not the class inheritance relation. An inherited method continues to use the dynamic receiver for ordinary calls; it does not acquire the defining software class as a new receiver.
Ignoring predefined ancestors as permitted, the Scala trait linearizations are
A super call in a trait continues to the next applicable implementation in the receiver's Scala trait linearization. It is not permanently bound to the parent named when that trait was declared.
For d1.h, enter B2.h. Its super.g reaches B1.g, and that method's super.f reaches A.f. For d2.h, enter B1.h; its super.g reaches B2.g, whose super.f again reaches A.f. Therefore the two output lines are
h in B2, g in B1, f in A
h in B1, g in B2, f in A
The shared ancestor A occurs once in each Scala trait linearization, after the two specialized traits. This accounts for the final f in A in both lines.
Method overloading chooses a signature using the static types available at the call site. The receiver m has static type M, whose visible overloads accept X and Y; the Z overload introduced only in N is not a candidate.
For m.f(x), the argument's static type is X, so the chosen signature is f(X). N does not override it, and the inherited M body runs. For m.f(y), the chosen signature is f(Y), and dynamic dispatch on the receiver chooses N's override. For m.f(z), both visible signatures apply, but f(Y) is more specific than f(X). Dynamic dispatch again chooses N.f(Y). Thus
f with X at M
f with Y at N
f with Y at N
Equivalently,
The runtime type of every argument is Z, but ordinary single-receiver dynamic dispatch does not redo method overloading using that runtime type. This is overload selection followed by dynamic dispatch.
Use double dispatch. Each original overload first sends a dynamically dispatched message to its argument. The argument's selected body then calls an overloaded receiver method with this, whose static type inside that body is the desired argument software class.
class X {
  def sendTo(m: M): String = m.receive(this)
}
class Y extends X {
  override def sendTo(m: M): String = m.receive(this)
}
class Z extends Y {
  override def sendTo(m: M): String = m.receive(this)
}

class M {
  def f(x: X): String = x.sendTo(this)
  def f(x: Y): String = x.sendTo(this)

  def receive(x: X): String = "f with X at M"
  def receive(x: Y): String = "f with Y at M"
  def receive(x: Z): String = "f with Z at M"
}

class N extends M {
  override def f(x: Y): String = x.sendTo(this)
  def f(x: Z): String = x.sendTo(this)

  override def receive(x: X): String = "f with X at N"
  override def receive(x: Y): String = "f with Y at N"
  override def receive(x: Z): String = "f with Z at N"
}
All three receive overloads must be declared in M, because sendTo receives a parameter with static type M. Declaring the Z version only in N would repeat the original problem.
Whatever original f signature is selected, it invokes sendTo on the actual argument. For a Z object, dynamic dispatch enters Z.sendTo; inside that body, this has static type Z, so method overloading selects receive(Z). The final call dynamically dispatches on the actual m, giving N.receive(Z) when m refers to N. Hence
f with Z at N
f with Z at N
f with Z at N
The result depends on both runtime types:
For the stated X, Y, Z hierarchy, the same construction reports each argument software class correctly for either receiver software class. Further subclasses would need their own dispatch protocol to distinguish additional runtime types.

Articles by others on the same topic (0)

There are currently no matching articles.