В чем разница между подклассами типа self-types и trait?
Автотип для признака A
:
trait B
trait A { this: B => }
говорит, что "A
не может быть смешано с конкретным классом, который также не распространяется на B
".
С другой стороны, следующее:
trait B
trait A extends B
говорит, что "любое (конкретное или абстрактное) смешивание классов в A
также будет смешиваться в B".
Разве эти два утверждения не означают одно и то же? Кажется, что тип self-type служит только для создания возможности простой ошибки времени компиляции.
Что мне не хватает?
Ответы
Ответ 1
Он преимущественно используется для инъекций зависимости, например, в Cake Pattern. В Scala есть отличная статья, охватывающая множество различных форм внедрения зависимостей, в том числе Cake Pattern. Если вы используете Google "Cake Pattern and Scala", вы получите много ссылок, включая презентации и видео. А пока вот ссылка на другой вопрос.
Теперь, что касается различия между типом личности и расширением черты, это просто. Если вы говорите, что B extends A
, то B
- это A
Когда вы используете собственные типы, B
требует A
Существуют два конкретных требования, которые создаются с помощью собственных типов:
- Если
B
расширен, то вы должны добавить A
- Когда конкретный класс наконец расширяет/смешивает эти черты, некоторый класс/черта должен реализовать
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 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
При использовании Right
требование о добавлении User
удовлетворяется. Тем не менее, второе требование, упомянутое выше, не выполняется: бремя реализации User
все еще остается для классов/признаков, которые расширяют Right
.
С RightAgain
оба требования удовлетворены. User
и реализация User
предоставляются.
Для более практического использования, пожалуйста, смотрите ссылки в начале этого ответа! Но, надеюсь, теперь вы получите это.
Ответ 2
Типы типов позволяют вам определять циклические зависимости. Например, вы можете достичь этого:
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 (Создание главы пользовательского интерфейса электронной таблицы), где он упоминает:
В примере электронной таблицы класс Model наследует от Evaluator и таким образом, получает доступ к методу оценки. Чтобы пойти другим путем, класс Оценщик определяет свой тип типа как Модель:
package org.stairwaybook.scells
trait Evaluator { this: Model => ...
Надеюсь, что это поможет.
Ответ 3
Еще одно отличие состоит в том, что типы self-types могут указывать типы неклассов. Например,
trait Foo{
this: { def close:Unit} =>
...
}
Тип типа здесь является структурным типом. Эффект состоит в том, чтобы сказать, что все, что смешивается в Foo, должно реализовать возвращаемый модуль метода no-arg "close". Это позволяет использовать безопасные микшины для утиного ввода.
Ответ 4
Раздел 2.3 "Аннотации самодельного изображения" оригинального макета Scala paper Масштабируемые абстракции компонентов на самом деле очень хорошо объясняет цель самолечения за пределами состава смеси: обеспечить альтернативный способ ассоциирования класса с абстрактным типом.
Пример, приведенный в документе, выглядит следующим образом: он, похоже, не имеет элегантного подкласса:
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;
}
}
Ответ 5
Еще одна вещь, о которой не упоминалось: поскольку сами типы не являются частью иерархии требуемого класса, они могут быть исключены из сопоставления шаблонов, особенно когда вы исчерпывающе сопоставляетесь с запечатанной иерархией. Это удобно, если вы хотите моделировать ортогональное поведение, например:
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 it we're exhaustive
Ответ 6
Начнем с циклической зависимости.
trait A {
selfA: B =>
def fa: Int }
trait B {
selfB: A =>
def fb: String }
Однако модульность этого решения не так велика, как может показаться вначале, потому что вы можете переопределить типы типов следующим образом:
trait A1 extends A {
selfA1: B =>
override def fb = "B String" }
trait B1 extends B {
selfB1: A =>
override def fa = "A String" }
val myObj = new A1 with B1
Хотя, если вы переопределяете член типа self, вы теряете доступ к исходному элементу, к которому все еще можно получить доступ через супер, используя наследование. Итак, что на самом деле достигается за счет использования наследования:
trait AB {
def fa: String
def fb: String }
trait A1 extends AB
{ override def fa = "A String" }
trait B1 extends AB
{ override def fb = "B String" }
val myObj = new A1 with B1
Теперь я не могу утверждать, что понимаю все тонкости шаблона пирога, но мне кажется, что основным методом обеспечения модульности является использование композиции, а не наследования или типов.
Версия наследования короче, но основная причина, по которой я предпочитаю наследование по типам self, заключается в том, что мне гораздо сложнее получить правильный порядок инициализации с собственными типами. Однако есть некоторые вещи, которые вы можете делать с самими типами, которые вы не можете сделать с наследованием. Типы типов могут использовать тип, тогда как для наследования требуется черта или класс, как в:
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 }
Один момент, который должен больше сопереживать, заключается в том, что черты могут расширять классы. Спасибо Дэвиду Маклверу за это. Вот пример из моего собственного кода:
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]
ScnBase
наследует Swing Класс кадра, поэтому его можно использовать как тип self, а затем смешивать в конце (при создании экземпляра). Тем не менее, val geomR
необходимо инициализировать, прежде чем использовать его, наследуя признаки. Поэтому нам нужен класс для принудительной инициализации geomR
. Класс ScnVista
может затем наследоваться из-за множества ортогональных признаков, которые сами могут быть унаследованы. Использование нескольких параметров типа (generics) предлагает альтернативную форму модульности.
Ответ 7
TL; DR сводка других ответов:
-
Типы, которые вы распространяете, подвергаются наследуемым типам, но само-типы не являются
например: class Cow { this: FourStomachs }
позволяет использовать методы, доступные только для жвачных животных, например digestGrass
. Однако черты, которые расширяют Корову, не будут иметь таких привилегий. С другой стороны, class Cow extends FourStomachs
будет выставлять digestGrass
всем, кто extends Cow
.
-
self-types позволяют циклические зависимости, расширяя другие типы, не
Ответ 8
trait A { def x = 1 }
trait B extends A { override def x = super.x * 5 }
trait C1 extends B { override def x = 2 }
trait C2 extends A { this: B => override def x = 2}
// 1.
println((new C1 with B).x) // 2
println((new C2 with B).x) // 10
// 2.
trait X {
type SomeA <: A
trait Inner1 { this: SomeA => } // compiles ok
trait Inner2 extends SomeA {} // doesn't compile
}
Ответ 9
Тип "Self" позволяет указать, какие типы разрешены для микширования признака. Например, если у вас есть черта с типом self Closeable
, то эта черта знает, что единственное, что разрешено смешивать, должно реализовать интерфейс Closeable
.
Ответ 10
Обновление: Основное отличие состоит в том, что самонастройки могут зависеть от нескольких классов (я допускаю, что бит-бит). Например, вы можете иметь
class Person {
//...
def name: String = "...";
}
class Expense {
def cost: Int = 123;
}
trait Employee {
this: Person with Expense =>
// ...
def roomNo: Int;
def officeLabel: String = name + "/" + roomNo;
}
Это позволяет добавить микс Employee
только к любому подклассу Person
и Expense
. Конечно, это имеет смысл только в том случае, если Expense
продолжается Person
или наоборот. Дело в том, что использование self-types Employee
может быть независимым от иерархии классов, от которых он зависит. Он не заботится о том, что расширяет то, что. Если вы переключите иерархию Expense
vs Person
, вам не нужно изменять Employee
.
Ответ 11
в первом случае подтип или подкласс класса B можно смешать с любым использованием A. Таким образом, B может быть абстрактным признаком.