[scala] 자기 유형과 특성 서브 클래스의 차이점은 무엇입니까?

형질의 자기 유형 A:

trait B
trait A { this: B => }

말한다 A또한 연장하지 않는 구체적인 클래스로 함께 사용할 수 없습니다 B .

반면에 다음은

trait B
trait A extends B

그 말한다 “어떤 (콘크리트 또는 추상) 클래스에 혼합 A또한 B에 혼합 될 것입니다” .

이 두 진술이 같은 의미가 아닙니까? self-type은 단순한 컴파일 타임 오류 가능성을 만드는 데만 사용됩니다.

내가 무엇을 놓치고 있습니까?



답변

케이크 패턴과 같이 의존성 주입에 주로 사용됩니다 . 케이크 패턴을 포함하여 스칼라에는 다양한 형태의 의존성 주입을 다루는 훌륭한 기사 가 있습니다 . Google “케이크 패턴 및 스칼라”를 사용하면 프리젠 테이션 및 비디오를 포함한 많은 링크가 제공됩니다. 지금은 다른 질문에 대한 링크 입니다.

자아 유형과 특성을 확장하는 것의 차이점은 간단합니다. 당신이 말한다면 B extends A, 다음 B 입니다A . 당신이 자기 유형을 사용하는 경우, B 필요A. 자체 유형으로 작성되는 두 가지 특정 요구 사항이 있습니다.

  1. 경우 B확장, 당신은하고 필요한 혼합 된 위해 A.
  2. 구체적 클래스가 이러한 특성을 확장 / 혼합 할 때 일부 클래스 / 특성은 구현해야합니다 A.

다음 예를 고려하십시오.

scala> trait User { def name: String }
defined trait User

scala> trait Tweeter {
     |   user: User =>
     |   def tweet(msg: String) = println(s"$name: $msg")
     | }
defined trait Tweeter

scala> trait Wrong extends Tweeter {
     |   def noCanDo = name
     | }
<console>:9: error: illegal inheritance;
 self-type Wrong does not conform to Tweeter's selftype Tweeter with User
       trait Wrong extends Tweeter {
                           ^
<console>:10: error: not found: value name
         def noCanDo = name
                       ^

Tweeter의 서브 클래스 인 경우 User오류가 없습니다. 위의 코드에서, 우리는 필요한User할 때마다 Tweeter사용된다 그러나이 User제공되지 않았습니다 Wrong우리가 오류를 가지고, 그래서. 이제 위의 코드가 여전히 범위 내에 있으면 다음을 고려하십시오.

scala> trait DummyUser extends User {
     |   override def name: String = "foo"
     | }
defined trait DummyUser

scala> trait Right extends Tweeter with User {
     |   val canDo = name
     | }
defined trait Right

scala> trait RightAgain extends Tweeter with DummyUser {
     |   val canDo = name
     | }
defined trait RightAgain

를 사용하면 Righta를 혼합해야하는 요구 사항 User이 충족됩니다. 그러나, 위에서 언급 한 두 번째 요구 사항이 충족되지 : 구현의 부담이 User여전히이 확장하는 클래스 / 형질 남아있다 Right.

함께 RightAgain두 요구 사항은 만족하고 있습니다. User및 구현 User이 제공된다.

보다 실용적인 사용 사례는이 답변의 시작 부분에있는 링크를 참조하십시오! 그러나, 이제 당신은 그것을 얻습니다.


답변

자체 유형을 사용하면 주기적 종속성을 정의 할 수 있습니다. 예를 들어 다음을 달성 할 수 있습니다.

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

상속 extends은 그것을 허용하지 않습니다. 시험:

trait A extends B
trait B extends A
error:  illegal cyclic reference involving trait A

Odersky 책에서 33.5 (스프레드 시트 UI 생성 장) 섹션에서 다음 내용을 참조하십시오.

스프레드 시트 예제에서 Model 클래스는 Evaluator에서 상속되므로 평가 방법에 액세스 할 수 있습니다. 반대로, Evaluator 클래스는 다음과 같이 자체 유형을 Model로 정의합니다.

package org.stairwaybook.scells
trait Evaluator { this: Model => ...

도움이 되었기를 바랍니다.


답변

또 다른 차이점은 자체 유형이 비 클래스 유형을 지정할 수 있다는 것입니다. 예를 들어

trait Foo{
   this: { def close:Unit} =>
   ...
}

여기서 자기 유형은 구조적 유형입니다. 그 결과 Foo에서 믹스되는 것은 arg가없는 “close”메소드 리턴 유닛을 구현해야한다는 것입니다. 이것은 오리 타이핑을위한 안전한 믹스 인을 허용합니다.


답변

Martin Odersky의 원본 Scala paper Scalable Component Abstractions 의 2.3 절 “Selftype Annotations”는 실제로 믹스 인 구성을 넘어서는 셀프 타이프의 목적을 잘 설명하고 있습니다.

논문에 제시된 예는 다음과 같으며, 우아한 서브 클래스 특파원이없는 것 같습니다.

abstract class Graph {
  type Node <: BaseNode;
  class BaseNode {
    self: Node =>
    def connectWith(n: Node): Edge =
      new Edge(self, n);
  }
  class Edge(from: Node, to: Node) {
    def source() = from;
    def target() = to;
  }
}

class LabeledGraph extends Graph {
  class Node(label: String) extends BaseNode {
    def getLabel: String = label;
    def self: Node = this;
  }
}


답변

언급되지 않은 또 다른 사항 : 자체 유형은 필수 클래스의 계층 구조의 일부가 아니므로 특히 봉인 된 계층 구조와 철저하게 일치하는 경우 패턴 일치에서 제외 될 수 있습니다. 다음과 같은 직교 동작을 모델링 할 때 편리합니다.

sealed trait Person
trait Student extends Person
trait Teacher extends Person
trait Adult { this : Person => } // orthogonal to its condition

val p : Person = new Student {}
p match {
  case s : Student => println("a student")
  case t : Teacher => println("a teacher")
} // that's it we're exhaustive


답변

TL; 다른 답변의 DR 요약 :

  • 확장 한 형식은 상속 된 형식에 노출되지만 자체 형식은 그렇지 않습니다

    예 : class Cow { this: FourStomachs }와 같은 반추 동물 만 사용할 수있는 방법을 사용할 수 있습니다 digestGrass. 그러나 Cow를 확장하는 특성은 그러한 권한이 없습니다. 한편, 누구에게나 class Cow extends FourStomachs노출 digestGrass됩니다 extends Cow .

  • 자체 유형은 주기적 종속성을 허용하지만 다른 유형은 확장하지 않습니다.


답변

주기적 의존성부터 시작하겠습니다.

trait A {
  selfA: B =>
  def fa: Int }

trait B {
  selfB: A =>
  def fb: String }

그러나이 솔루션의 모듈성은 처음 나타나는 것만 큼 좋지 않습니다. 자체 유형을 무시할 수 있기 때문입니다.

trait A1 extends A {
  selfA1: B =>
  override def fb = "B's String" }
trait B1 extends B {
  selfB1: A =>
  override def fa = "A's String" }
val myObj = new A1 with B1

자체 유형의 멤버를 대체하는 경우에도 상속을 사용하여 super를 통해 액세스 할 수있는 원래 멤버에 대한 액세스 권한이 손실됩니다. 따라서 상속을 통해 실제로 얻는 것은 다음과 같습니다.

trait AB {
  def fa: String
  def fb: String }
trait A1 extends AB
{ override def fa = "A's String" }
trait B1 extends AB
{ override def fb = "B's String" }
val myObj = new A1 with B1

이제 케이크 패턴의 모든 미묘함을 이해한다고 주장 할 수는 없지만, 모듈성을 적용하는 주된 방법은 상속이나 자기 유형보다는 구성을 통한 것입니다.

상속 버전은 짧지 만 자체 유형보다 상속을 선호하는 주된 이유는 자체 유형으로 초기화 순서를 올바르게 만드는 것이 훨씬 까다롭기 때문입니다. 그러나 상속으로 할 수없는 자기 유형으로 할 수있는 일이 있습니다. 상속에는 다음과 같이 특성이나 클래스가 필요하지만 자체 유형은 유형을 사용할 수 있습니다.

trait Outer
{ type T1 }
trait S1
{ selfS1: Outer#T1 => } //Not possible with inheritance.

당신은 할 수 있습니다 :

trait TypeBuster
{ this: Int with String => }

당신이 그것을 인스턴스화 할 수는 없지만. 형식에서 상속 할 수없는 절대적인 이유는 보이지 않지만 형식 생성자 특성 / 클래스가 있으므로 경로 생성자 클래스와 특성을 갖는 것이 유용 할 것입니다. 불행히도

trait InnerA extends Outer#Inner //Doesn't compile

우리는 이것을 가지고 있습니다 :

trait Outer
{ trait Inner }
trait OuterA extends Outer
{ trait InnerA extends Inner }
trait OuterB extends Outer
{ trait InnerB extends Inner }
trait OuterFinal extends OuterA with OuterB
{ val myV = new InnerA with InnerB }

아니면 이거:

  trait Outer
  { trait Inner }
  trait InnerA
  {this: Outer#Inner =>}
  trait InnerB
  {this: Outer#Inner =>}
  trait OuterFinal extends Outer
  { val myVal = new InnerA with InnerB with Inner }

더 공감해야 할 한 가지 점은 특성이 수업을 확장 할 수 있다는 것입니다. 이것을 지적 해 주신 David Maclver에게 감사드립니다. 내 코드의 예는 다음과 같습니다.

class ScnBase extends Frame
abstract class ScnVista[GT <: GeomBase[_ <: TypesD]](geomRI: GT) extends ScnBase with DescripHolder[GT] )
{ val geomR = geomRI }
trait EditScn[GT <: GeomBase[_ <: ScenTypes]] extends ScnVista[GT]
trait ScnVistaCyl[GT <: GeomBase[_ <: ScenTypes]] extends ScnVista[GT]

ScnBaseSwing 프레임 클래스 에서 상속 되므로 자체 유형으로 사용 된 다음 마지막에 인스턴스화 할 수 있습니다. 그러나 val geomR특성을 상속하여 사용하기 전에 초기화해야합니다. 따라서 사전 초기화를 시행하는 클래스가 필요합니다 geomR. ScnVista그런 다음 클래스 는 자체에서 상속받을 수있는 여러 직교 특성으로 상속 될 수 있습니다. 여러 유형의 매개 변수 (일반)를 사용하면 다른 형태의 모듈성이 제공됩니다.