2

I am using Scala, and wondering why this code works.

trait Base {
  def foo(x: Int): Int
}

trait A extends Base {
  def fooA(x: Int): Int = {
    foo(x)
  }
}

class Impl extends Base with A {
  override def foo(x: Int): Int = x
}

val a = new Impl
a.fooA(10)

The result of a.fooA(10) is 10.
However, in trait A, method fooA is using foo method which is implemented in Impl class.
Again, Impl class extends class A (with A in the declaration of class Impl).

Isn't it circular?
How is it possible?

Thanks.

invictus
  • 81
  • 5

2 Answers2

2

There is nothing special here, the method foo is defined in the trait allowing it to be called and implemented in impl. It isn't really important where it is called from.

The calling goes as follows -> call fooA. It is only defined in A which impl inherits. fooA calls foo. foo is defined in the trait and the implementation appears in impl. This is not circular but rather the most basic usage.

If there were more than one implementation (e.g. in trait A) then the order would be based on linearization (see https://stackoverflow.com/a/34243727/1547734)

Community
  • 1
  • 1
Assaf Mendelson
  • 12,701
  • 5
  • 47
  • 56
2

From the compilation point of view, everything checks out. Base demands an implementation for foo by whoever extends it, and this is what Impl does. Trait A is allowed to use foo because it extends Base. Everything is clear.

However, I see your confusion. I wouldn't really call it circular though; it's more like using something (foo in A) before it has been initalized (in Impl). Reason this works is because you used def. Compiler knows that this value will be available later (because if it doesn't find it in the rest of the compilation process, it will break) and it just says "I know it will be available at the point when it is invoked (given the compilation is successful), and it's a def, which means I will be calculating it then and there. So I don't need to do it now".

If you use val, however, foo will be initialized at that point in A so you will get that initial value, which is 0 for Interegers:

trait Base {
  val foo: Int
}

trait A extends Base {
  val fooA: Int = foo
}

class Impl extends Base with A {
  override val foo: Int = 42
}

val a = new Impl
println(a.fooA) // prints 0 although we wanted 42

Note that lazy val has the same effect as def (it's also calculated lazy, it's just that it will be calculated only once, at the point of first usage), so modifying the above code to override lazy val foo: Int = 42 would result in printing 42 too.

slouc
  • 9,508
  • 3
  • 16
  • 41