开发者

Difference between trait inheritance and self type annotation

开发者 https://www.devze.com 2022-12-19 08:15 出处:网络
In Scala, I\'ve seen the const开发者_JAVA技巧ructs trait T extends S and trait T { this: S => used to achieve similar things (namely that the abstract methods in S must be defined before an

In Scala, I've seen the const开发者_JAVA技巧ructs

trait T extends S

and

trait T { this: S =>

used to achieve similar things (namely that the abstract methods in S must be defined before an instance may be created). What's the difference between them? Why would you use one over the other?


Self type annotations allow you to express cyclic dependencies. For instance:

trait A extends B
trait B { self: A => }

This is not possible with simple inheritance.


I'd use self-types for dependency-management: This trait requires another trait to be mixed in. And I'd use inheritance to refine another trait or interface.

Just as an example:

trait FooService

trait FooRemoting { this : FooService => }
trait FooPersistence { this : FooService => }

object Services extends FooService with FooRemoting with FooPersistence

Now, if FooRemoting and FooPersistence both would have inherited from FooService, and FooService has members and methods, how would Services look like?

Whereas for inheritance, we'd have something like:

trait Iterator[T] {
  def hasNext : boolean
  def next : T
}

trait InfiniteIterator[T] extends Iterator[T] {
  def hasNext = true
}


Since asking the question I came across these posts:

Spiros Tzavellas talks about using a trait as the public interface and the self type as a helper that must be mixed in by the implementation class.

In conclusion, if we want to move method implementations inside traits then we risk polluting the interface of those traits with abstract methods that support the implementation of the concrete methods and are unrelated with the main responsibility of the trait. A solution to this problem is to move those abstract methods in other traits and compose the traits together using self type annotations and multiple inheritance.

For example:

trait PublicInterface { this: HelperTrait =>
  // Uses helperMethod
}

trait HelperTrait {
  def helperMethod = // ...
}

class ImplementationClass extends PublicInterface with HelperTrait

A Tour of Scala discusses using self type annotations with abstract type members - presumably it's not possible to extend an abstract type member(?)


I know this question is old but I would like to add some clarification and examples.

There are three main differences between trait inheritance and self types.

Semantics

Inheritance is one of the relationships with the most coupling of the object paradigm, if A extends B, that means that A is a B.

Let's say we have the following code,

trait Animal {
  def stop():Unit = println("stop moving")
}

class Dog extends Animal {
  def bark:String = "Woof!"
}

val goodboy:Dog = new Dog

goodboy.bark
// Woof!

We are saying that a Dog is an Animal. We can send the messages bark and stop to goodboy because is a Dog, it understand both methods.

Now suppose we have a new trait,

trait Security {
  this: Animal =>
  def lookout:Unit = { stop(); println("looking out!") }
}

This time Security is NOT an Animal, and that is fine because would be semantically incorrect if we affirm that a Security is an Animal, they are different concepts, that can be used together.

So now we can create a new kind of dog,

val guardDog = new Dog with Security

guardDog.lookout
// stop moving
// looking out!

guardDog is a Dog, an Animal and Security. It understand stop, bark and lookout because is a Dog with Security.

But what happens if we create a new dog like this?

val guardDog2:Dog = new Dog with Security
guardDog2.lookout // no such method!

guardDog2 is just a Dog, so we can't call lookout method. (okok, it's a Dog with Security, but we just see a Dog)

Cyclic Dependencies

Self Types allow us to create cyclic dependencies between types.

trait Patient {
  this: Reader =>
  def isQuite:Boolean = isReading
  def isSlow:Boolean = true
}

trait Reader {
  this: Patient =>
  def read():Unit = if(isSlow) println("Reading Slow...") else println("Reading Fast...")
  def isReading = true
}

val person = new Patient with Reader

The following code doesn't compile.

trait Patient extends Reader { /** code **/}

trait Reader extends Patient { /** code **/ }

This kind of code is very common in dependency injection (cake pattern).

Versatility

Last but not least, who uses our traits can decide the order in which they are used, so thanks to Trait Linearization the final result can be different although the traits used are the same.

With normal inheritance we can't do that, the relations between traits and classes are fixed.

trait Human {
  def isGoodForSports:Boolean
}

trait Programmer extends Human {
  def readStackOverflow():Unit = println("Reading...")
  override def isGoodForSports: Boolean = false
}

trait Sportsman extends Human {
  def play():Unit = println("Playing something")
  override def isGoodForSports: Boolean = true
}

val foo = new Programmer with Sportsman
foo.isGoodForSports
// true

val bar = new Sportsman with Programmer
bar.isGoodForSports
// false

Hope this can be useful.


The answer is "circularity". But not only.

Self type annotation solves for me the fundamental problem of inheritance: what you inherit from cannot use what you are. With the self type, everything becomes easy.

My pattern is the following and can be considered as a degenerated cake:

trait A { self: X => def a = reuseme}
trait B { self: X => def b = a }
class X extends A with B { def reuseme=null }

You can explode your class in multiple behaviours which can be called from anywhere in the assembly, while staying cleanly typed. No need for the painful indirection too often (and wrongly) identified with the cake pattern.

Half (if not the totality) of the convoluted Java DI frameworks of the last ten years have been devoted to do this, of course without the typing. People still using JAVA in this domain are clearly loosing their time: "SCALA ouakbar".


Although it doesn't answer your question, I was trying to understand the self-type annotations and basically got lost in answers, and somehow ended up cycling through variations of your question, which focuses on usage of self-type annotations for stating dependencies.

So here I post a description of an use case where self-type annotations are well illustrated, namely something like a type-safe case of 'this' as a subtype:

http://programming-scala.labs.oreilly.com/ch13.html#SelfTypeAnnotationsAndAbstractTypeMembers

hoping that it would be helpful to those who end up on this question by chance (and, like me, didn't have time to read a scala book before starting to explore :-) )

0

精彩评论

暂无评论...
验证码 换一张
取 消