В чем разница между типами self и подклассами черт?

347

Автотип для признака A:

trait B
trait A { this: B => }

говорит, что "A не может быть смешано с конкретным классом, который также не распространяется на B".

С другой стороны, следующее:

trait B
trait A extends B

говорит, что "любое (конкретное или абстрактное) смешивание классов в A также будет смешиваться в B".

Разве эти два утверждения не означают одно и то же? Кажется, что тип self-type служит только для создания возможности простой ошибки времени компиляции.

Что мне не хватает?

  • 0
    Я на самом деле интересуюсь здесь различиями между типами Я и подклассами в чертах. Я знаю некоторые из общих применений для самопознания; Я просто не могу найти причину, по которой их нельзя было бы сделать более четко с подтипами.
  • 30
    В типах self можно использовать параметры типа: trait A[Self] {this: Self => } допустима, trait A[Self] extends Self , а не.
Показать ещё 5 комментариев
Теги:
traits
self-type

11 ответов

241
Лучший ответ

Он преимущественно используется для инъекций зависимости, например, в Cake Pattern. В Scala есть отличная статья, охватывающая множество различных форм внедрения зависимостей, в том числе Cake Pattern. Если вы используете Google "Cake Pattern and Scala", вы получите много ссылок, включая презентации и видео. А пока вот ссылка на другой вопрос.

Теперь, что касается различия между типом личности и расширением черты, это просто. Если вы говорите, что 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 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 предоставляются.

Для более практического использования, пожалуйста, смотрите ссылки в начале этого ответа! Но, надеюсь, теперь вы получите это.

  • 3
    Благодарю. Шаблон Cake - это 90% того, что я имею в виду, почему я говорю о шумихе вокруг самоподобных типов ... именно там я впервые увидел тему. Пример Джонаса Бонера великолепен, потому что он подчеркивает суть моего вопроса. Если бы вы изменили типы самости в его примере с подогревателем, чтобы они стали субтитрами, тогда в чем будет разница (кроме ошибки, которую вы получаете при определении ComponentRegistry, если не смешиваете нужные вещи?
  • 28
    @Dave: Вы имеете в виду, что trait WarmerComponentImpl extends SensorDeviceComponent with OnOffDeviceComponent ? Это приведет к тому, что WarmerComponentImpl будет иметь эти интерфейсы. Они будут доступны всему, что расширило WarmerComponentImpl , что явно неверно, так как это не SensorDeviceComponent и не OnOffDeviceComponent . Как собственный тип, эти зависимости доступны исключительно для WarmerComponentImpl . List может быть использован в качестве Array , и наоборот. Но они просто не одно и то же.
Показать ещё 15 комментариев
142

Типы типов позволяют вам определять циклические зависимости. Например, вы можете достичь этого:

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
    Я не рассматривал этот сценарий. Это первый пример того, что я видел, и это не то же самое, что и самоподтип, как с подклассом. Тем не менее, это выглядит как крайний случай и, что более важно, кажется плохой идеей (я обычно стараюсь изо всех сил НЕ определять циклические зависимости!). Считаете ли вы, что это самое важное различие?
  • 4
    Я думаю так. Я не вижу никакой другой причины, по которой я бы предпочел, чтобы self-types расширял предложение. Self-типы являются многословными, они не наследуются (поэтому вы должны добавить self-типы ко всем подтипам как ритуал), и вы можете видеть только участника, но не можете его переопределить. Я хорошо знаком с паттерном Cake и многими постами, в которых упоминается самопечатание для DI. Но почему-то я не убежден. Я давно создал образец приложения ( bitbucket.org/mushtaq/scala-di ). Посмотрите конкретно на папку / src / configs. Я достиг DI, чтобы заменить сложные конфигурации Spring без самостоятельных типов.
Показать ещё 2 комментария
51

Еще одно отличие состоит в том, что типы self-types могут указывать типы неклассов. Например,

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

Тип типа здесь является структурным типом. Эффект состоит в том, чтобы сказать, что все, что смешивается в Foo, должно реализовать возвращаемый модуль метода no-arg "close". Это позволяет использовать безопасные микшины для утиного ввода.

  • 41
    На самом деле вы можете использовать наследование и со структурными типами: абстрактный класс A extends {def close: Unit}
  • 12
    Я думаю, что структурная типизация использует отражение, поэтому используйте только тогда, когда нет другого выбора ...
Показать ещё 3 комментария
11

Еще одна вещь, о которой не упоминалось: поскольку сами типы не являются частью иерархии требуемого класса, они могут быть исключены из сопоставления шаблонов, особенно когда вы исчерпывающе сопоставляетесь с запечатанной иерархией. Это удобно, если вы хотите моделировать ортогональное поведение, например:

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
11

Раздел 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;
  }
}
  • 0
    Для тех, кто задается вопросом, почему подклассы не решают эту проблему, в разделе 2.3 также говорится: «Каждый из операндов композиции mixin C_0 с ... с C_n должен ссылаться на класс. Механизм композиции mixin не позволяет любому C_i ссылаться на абстрактный тип. Это ограничение позволяет статически проверять наличие неоднозначностей и переопределять конфликты в момент составления класса ».
9

TL; DR сводка других ответов:

  • Типы, которые вы распространяете, подвергаются наследуемым типам, но само-типы не являются

    например: class Cow { this: FourStomachs } позволяет использовать методы, доступные только для жвачных животных, например digestGrass. Однако черты, которые расширяют Корову, не будут иметь таких привилегий. С другой стороны, class Cow extends FourStomachs будет выставлять digestGrass всем, кто extends Cow.

  • self-types позволяют циклические зависимости, расширяя другие типы, не

9

Начнем с циклической зависимости.

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
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
}
4

Тип "Self" позволяет указать, какие типы разрешены для микширования признака. Например, если у вас есть черта с типом self Closeable, то эта черта знает, что единственное, что разрешено смешивать, должно реализовать интерфейс Closeable.

  • 2
    Это неверно, другие черты также могут быть смешаны.
  • 3
    @Blaisorblade: Интересно, возможно, вы неправильно прочитали ответ Кикибобо - тип личности черты действительно позволяет вам ограничивать типы, которые могут его смешивать, и это является частью его полезности. Например, если мы определяем trait A { self:B => ... } то объявление X with A допустимо только в том случае, если X расширяет B. Да, вы можете сказать X with A with Q , где Q не расширяет B, но я считаю, что точка зрения Кикибобо заключалась в том, что Х так ограничен. Или я что-то пропустил?
Показать ещё 1 комментарий
1

Обновление: Основное отличие состоит в том, что самонастройки могут зависеть от нескольких классов (я допускаю, что бит-бит). Например, вы можете иметь

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.

  • 0
    Сотрудник не должен быть классом, чтобы спуститься с человека. Черты могут расширять классы. Если черта Employee расширила Person вместо использования собственного типа, пример все равно будет работать. Я нахожу ваш пример интересным, но, похоже, он не иллюстрирует сценарий использования для типов себя.
  • 0
    @MorganCreighton Справедливо, я не знал, что черты могут расширять классы. Я подумаю об этом, если смогу найти лучший пример.
Показать ещё 2 комментария
0

в первом случае подтип или подкласс класса B можно смешать с любым использованием A. Таким образом, B может быть абстрактным признаком.

  • 0
    Нет, B может быть (и действительно является) «абстрактной чертой» в обоих случаях. Так что нет никакой разницы с этой точки зрения.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню