Тип личности для черты A
:
trait B
trait A { this: B => }
говорит, что « A
нельзя смешивать в конкретный класс, который также не расширяется B
» .
С другой стороны, следующее:
trait B
trait A extends B
говорит, что "любой (конкретный или абстрактный) класс, смешивающийся в, A
будет также смешиваться в B" .
Разве эти два утверждения не означают одно и то же? Кажется, что self-type служит только для того, чтобы создать возможность простой ошибки во время компиляции.
Что мне не хватает?
trait A[Self] {this: Self => }
законно,trait A[Self] extends Self
нет.Ответы:
Он преимущественно используется для инъекций зависимости , например, в Cake Pattern. В Scala есть отличная статья, рассказывающая о различных формах внедрения зависимостей, включая Cake Pattern. Если вы используете Google «Cake Pattern and Scala», вы получите много ссылок, включая презентации и видео. А пока вот ссылка на другой вопрос .
Теперь, что касается разницы между типом личности и расширением черты, это просто. Если вы говорите
B extends A
, тоB
этоA
. При использовании самого-типа,B
требуетA
. Существуют два конкретных требования, которые создаются с помощью собственных типов:B
это расширение, то вам необходимо добавитьA
.A
.Рассмотрим следующие примеры:
Если бы
Tweeter
был подклассUser
, не было бы ошибки. В приведенном выше коде нам требовалось использоватьUser
всякий раз, когдаTweeter
aUser
не было предоставленоWrong
, поэтому мы получили ошибку. Теперь, когда приведенный выше код все еще находится в области видимости, рассмотрим:При
Right
этом требование к смешиваниюUser
удовлетворяется. Однако второе требование, упомянутое выше, не выполняется: бремя реализацииUser
все еще остается для классов / признаков, которые расширяютсяRight
.С
RightAgain
удовлетворяются оба требования. АUser
и реализацияUser
предоставляются.Для более практического использования, пожалуйста, смотрите ссылки в начале этого ответа! Но, надеюсь, теперь вы получите это.
источник
trait WarmerComponentImpl extends SensorDeviceComponent with OnOffDeviceComponent
? Это должно было быWarmerComponentImpl
иметь эти интерфейсы. Они будут доступны для всего, что расширеноWarmerComponentImpl
, что явно неправильно, так как это не aSensorDeviceComponent
, ни aOnOffDeviceComponent
. Как тип self, эти зависимости доступны исключительно дляWarmerComponentImpl
. АList
можно использовать какArray
и наоборот. Но они просто не одно и то же.this
с типами self - это то, на что я смотрю свысока, так как это без тени причины скрывает оригиналthis
.self: Dep1 with Dep2 =>
.Самостоятельные типы позволяют вам определять циклические зависимости. Например, вы можете достичь этого:
Использование наследования
extends
не позволяет этого. Пытаться:В книге Одерского посмотрите раздел 33.5 (Глава «Создание пользовательского интерфейса электронной таблицы»), где упоминается:
Надеюсь это поможет.
источник
Еще одно отличие состоит в том, что self-типы могут указывать не классовые типы. Например
Тип self здесь является структурным типом. Эффект заключается в том, что все, что смешивается в Foo, должно реализовывать безошибочный метод «close», возвращающий единицу. Это позволяет использовать безопасные миксины для утки.
источник
abstract class A extends {def close:Unit}
эквивалентноabstract class A {def close:Unit}
. Так что это не касается структурных типов.Раздел 2.3 «Аннотации к самопечатанию» оригинальной Scala-статьи Мартина Одерского « Масштабируемые абстракции компонентов» выполненной Мартином Одерским, на самом деле очень хорошо объясняет цель автотипа помимо миксиновой композиции: предоставляет альтернативный способ связывания класса с абстрактным типом.
Пример, приведенный в статье, был похож на следующий, и у него, похоже, нет элегантного корреспондента подкласса:
источник
Еще одна вещь, которая не была упомянута: поскольку самотипы не являются частью иерархии требуемого класса, они могут быть исключены из сопоставления с образцом, особенно если вы исчерпывающе сопоставляете запечатанную иерархию. Это удобно, когда вы хотите моделировать ортогональные поведения, такие как:
источник
TL; DR резюме других ответов:
Типы, которые вы расширяете, подвержены унаследованным типам, но сами типы не
Например:
class Cow { this: FourStomachs }
позволяет использовать методы, доступные только жвачным, напримерdigestGrass
. Черты, которые расширяют корову, однако, не будут иметь таких привилегий. С другой стороны,class Cow extends FourStomachs
выставитdigestGrass
на все, ктоextends Cow
.self-типы допускают циклические зависимости, расширение других типов не допускает
источник
Давайте начнем с циклической зависимости.
Однако модульность этого решения не так велика, как может показаться на первый взгляд, потому что вы можете переопределить собственные типы следующим образом:
Хотя, если вы переопределите элемент собственного типа, вы потеряете доступ к исходному элементу, доступ к которому по-прежнему возможен через super с использованием наследования. Итак, что действительно получено за счет наследования:
Теперь я не могу утверждать, что понимаю все тонкости шаблона торта, но мне кажется, что основной метод обеспечения модульности - это составление, а не наследование или самопечатание.
Версия наследования короче, но главная причина, по которой я предпочитаю наследование по сравнению с типами self, заключается в том, что мне гораздо сложнее получить правильный порядок инициализации с типами self. Однако есть некоторые вещи, которые вы можете делать с типами себя, которые вы не можете делать с наследованием. Self-типы могут использовать тип, в то время как наследование требует черты или класса, как в:
Вы даже можете сделать:
Хотя ты никогда не сможешь создать его экземпляр. Я не вижу какой-либо абсолютной причины невозможности наследовать от типа, но я, конечно, чувствую, что было бы полезно иметь классы и признаки конструктора путей, поскольку у нас есть свойства / классы конструктора типов. Как к сожалению
У нас есть это:
Или это:
Еще один момент, на который следует обратить особое внимание, заключается в том, что черты могут расширять классы. Спасибо Дэвиду Маклверу за указание на это. Вот пример из моего собственного кода:
ScnBase
наследуется от класса Swing Frame, поэтому его можно использовать как собственный тип, а затем смешивать в конце (при создании экземпляра). Тем не менее, егоval geomR
необходимо инициализировать, прежде чем он будет использован для наследования черт. Таким образом, нам нужен класс для обеспечения предварительной инициализацииgeomR
. КлассScnVista
может быть унаследован от нескольких ортогональных признаков, которые сами могут наследоваться. Использование параметров нескольких типов (обобщений) предлагает альтернативную форму модульности.источник
источник
Self-тип позволяет вам указать, какие типы могут смешиваться в признаке. Например, если у вас есть черта с собственным типом
Closeable
, то эта черта знает, что единственные вещи, которые могут смешивать ее, должны реализовыватьCloseable
интерфейс.источник
trait A { self:B => ... }
то объявлениеX with A
действительно только в том случае, если X расширяет B. Да, вы можете сказатьX with A with Q
, где Q не расширяет B, но я считаю, что точка зрения Кикибобо заключалась в том, что X так ограничен. Или я что-то пропустил?Обновление: принципиальное отличие состоит в том, что самотипы могут зависеть от нескольких классов (я допускаю, что это немного угловой случай). Например, вы можете иметь
Это позволяет добавлять
Employee
миксин ко всему, что является подклассомPerson
иExpense
. Конечно, это имеет смысл, только еслиExpense
расширяетсяPerson
или наоборот. Дело в том, что использование self-типовEmployee
может быть независимым от иерархии классов, от которых она зависит. Это не заботится о том, что расширяет что - Если вы переключаете иерархиюExpense
vsPerson
, вам не нужно изменятьEmployee
.источник
в первом случае под-признак или подкласс B может быть смешан с тем, что использует A. Таким образом, B может быть абстрактным признаком.
источник