Artem Malinko Artem Malinko - 1 month ago 13
Scala Question

Scala. Is there a way to choose super method implementation with self-types?

When I extend traits I can choose which method implementation to use. Like here:

object Main {

def main(args: Array[String]): Unit = {
val c = new C
println(c.a)
println(c.b)
}

trait Parent {
def foo: String
}

trait A extends Parent {
override def foo = "from A"
}

trait B extends Parent {
override def foo = "from B"
}

class C extends A with B {
val b = super[A].foo
val a = super[B].foo
}

}


But if I want to do the same with self-types it's seems like it's not possible:

object Main {

def main(args: Array[String]): Unit = {
val c = new C with A with B
println(c.a)
println(c.b)
}

trait Parent {
def foo: String
}

trait A extends Parent {
override def foo = "from A"
}

trait B extends Parent {
override def foo = "from B"
}

class C {
self: A with B =>

val b = super[A].foo
val a = super[B].foo
}

}


This doesn't compile. Am I right and it's not possible? If I'm right, why is that and is there a workaround for it?

UPDATE:
Why do I needed in a first place? I was playing around with dependency injection using self-types instead of constructor injection. So I had a base trait Converter and child traits FooConverter and BarConverter. And I wanted to write it like that(which doesn't work of course):

object Main {

class Foo

class Bar

trait Converter[A] {
def convert(a: A): String
}

trait FooConverter extends Converter[Foo] {
override def convert(a: Foo): String = ???
}

trait BarConverter extends Converter[Bar] {
override def convert(a: Bar): String = ???
}

class Service {

this: Converter[Foo] with Converter[Bar] =>

def fooBar(f: Foo, b:Bar) = {
convert(f)
convert(b)
}
}

}


I thought it's because of generics, but it turned that it's not. So I was just wondering if it's possible to somehow invoke super method of chosen trait with self-types. Because with simple inheritance it's possible. As for my original problem I can write it like this and it will work:

object Main {

class Foo

class Bar

trait Converter[A] {
def convert(a: A): String
}

trait FooConverter extends Converter[Foo] {
override def convert(a: Foo): String = ???
}

trait BarConverter extends Converter[Bar] {
override def convert(a: Bar): String = ???
}

class Service {

this: FooConverter with BarConverter =>

def fooBar(f: Foo, b:Bar) = {
convert(f)
convert(b)
}
}

}


Probably tighter abstraction, but I'm not sure if it's bad for this kind of situation and if I need such broad abstraction like Converter[A] at all.

Answer

Calling super methods from already constructed type is impossible (you can do it only from the inside). In your example, you're trying to call foo on the instance self, which is constructed in runtime, so foo is virtual and could be overridden - compiler doesn't know which actual implementation is going to be called (formal vs real type problem). So technically - it's impossible to do what you want (call virtual method as a static one).

The naive hack is :

trait CC extends A with B {
  val b = super[A].foo
  val a = super[B].foo
}

class C {
  self: CC =>

}

It basically provides encapsulation you want - you might wanna redefine a and b in class C as they're not going to be available (in type C itself) till you mix C with CC.

Note that in every example you provide (including my naive solution) - resulting val c has access to foo anyway and which exact foo is going to be called depends on how do you mix A and B (A with B or B with A). So, the only encapsulation you get is that type C itself isn't going to have foo method. This means that self-type gives you kind of a way to temporary close (make private) a method in "subclass" without violating LSP - but it's not the only way (see below).


Besides all of that, cake-injection that you're trying to implement is considered impractical by some authors. You might want to have a look at Thin Cake Pattern - as a remark, I successfully used something like this in real project (in combination with constructor injection).

I would implement your converter services this way:

class Foo

class Bar

trait Converter[A] {
  def convert(a: A): String
}

object FooConverter1 extends Converter[Foo] {
  override def convert(a: Foo): String = ???
}

object BarConverter1 extends Converter[Bar] {
  override def convert(a: Bar): String = ???
}


trait FooBarConvertService {
  def fooConverter: Converter[Foo]
  def barConverter: Converter[Bar]

  def fooBar(f: Foo, b: Bar) = {
    fooConverter(f)
    barConverter(b)

  }

}

trait Converters {
  def fooConverter: Converter[Foo] = FooConverter1 
  def barConverter: Converter[Bar] = BarConverter1 
}

object App extends FooBarConvertService with Converters with ... 

This allows you to change/mock converter implementation when putting it all together.

I'd also notice that Converter[Bar] is nothing else but Function1[Bar, String] or just Bar => String, so actually you don't need separate interface for that:

sealed trait FooBar //introduced it just to make types stronger, you can omit it if you prefer
class Foo extends FooBar
class Bar extends FooBar

trait FooBarConvertService {

  type Converter[T <: FooBar] = T => String

  def fooConverter: Converter[Foo]
  def barConverter: Converter[Bar]
  def fooBar(f: Foo, b: Bar) = {
    fooConverter(f)
    barConverter(b)
  }

}

trait FooConverterProvider {
  def fooConverter: Foo => String = ??? 
}

trait BarConverterProvider {
  def barConverter: Bar => String = ??? 
}

object App 
  extends FooBarConvertService 
  with FooConverterProvider 
  with BarConverterProvider

You can also use def fooConverter(f: Foo): String = ??? instead def fooConverter: Foo => String = ???.

Talking about encapsulation - it's more weak here as you can access transitive dependencies, so if you really need it - use private[package] modifier.

Converters module:

package converters

trait FooBarConvertService {

  type Converter[T <: FooBar] = T => String

  private[converters] def fooConverter: Converter[Foo]
  private[converters] def barConverter: Converter[Bar]

  def fooBar(f: Foo, b: Bar) = {
    fooConverter(f)
    barConverter(b)
  }

}

trait FooConverterProvider {
  private[converters] def fooConverter: Foo => String = ??? 
}

trait BarConverterProvider {
  private[converters] def barConverter: Bar => String = ??? 
}

Core module:

package client 
import converters._  

object App 
  extends FooBarConvertService 
  with FooConverterProvider 
  with BarConverterProvider

You can use objects object converters {...}; object client {...} instead of packages if you prefer.

This encapsulation is even stronger than self-type based one, as you can't access fooConverter/barConverter from the App object (in your example foo is still accessable from val c = new C with A with B):

 client.App.fooBar(new Foo, new Bar) //OK
 client.App.fooConverter
 <console>:13: error: method fooConverter in trait FooConverterProvider cannot be accessed in object client.App
          client.App.fooConverter
                     ^
Comments