Qual é a diferença entre os auto-tipos e as subclasss de traços?

Um auto-tipo para uma característica A :

 trait B trait A { this: B => } 

diz que A não pode ser misturado em uma class concreta que também não estenda B .

Por outro lado, o seguinte:

 trait B trait A extends B 

diz que “qualquer class (concreta ou abstrata) misturando em A também estará misturando em B” .

Essas duas afirmações não significam a mesma coisa? O tipo auto parece servir apenas para criar a possibilidade de um erro simples em tempo de compilation.

o que estou perdendo?

    É predominantemente usado para Injeção de Dependência , como no Cake Pattern . Existe um ótimo artigo cobrindo muitas formas diferentes de injeção de dependência em Scala, incluindo o Cake Pattern. Se você usa o Google “Cake Pattern and Scala”, você terá muitos links, incluindo apresentações e vídeos. Por enquanto, aqui está um link para outra pergunta .

    Agora, quanto a qual é a diferença entre um tipo próprio e estender uma característica, isso é simples. Se você disser B extends A , então B é um A Quando você usa auto-tipos, B requer um A Existem dois requisitos específicos que são criados com tipos de auto:

    1. Se B for estendido, você precisará mixar um A
    2. Quando uma class concreta finalmente estende / mistura estas características, alguma class / característica deve implementar.

    Considere os seguintes exemplos:

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

    Se o Tweeter fosse uma subclass do User , não haveria erro. No código acima, exigimos um User sempre que o Tweeter é usado, no entanto, um User não foi fornecido para Wrong , então recebemos um erro. Agora, com o código acima ainda no escopo, considere:

     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 

    Com o Right , o requisito para misturar um User é satisfeito. No entanto, o segundo requisito mencionado acima não é satisfeito: o ônus de implementar o User ainda permanece para classs / características que se estendem para a Right .

    Com o RightAgain ambos os requisitos são satisfeitos. Um User e uma implementação do User são fornecidos.

    Para casos de uso mais práticos, por favor, veja os links no início desta resposta! Mas, espero que agora você consiga.

    Auto tipos permitem que você defina dependencies cíclicas. Por exemplo, você pode conseguir isso:

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

    Herança usando extensões não permite isso. Experimentar:

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

    No livro de Odersky, veja a seção 33.5 (Criando capítulo de UI de planilha) onde ele menciona:

    No exemplo de planilha, a class Modelo herda do Avaliador e, assim, obtém access ao seu método de avaliação. Para ir na outra direção, a class Evaluator define seu tipo self para ser Model, assim:

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

    Espero que isto ajude.

    Uma diferença adicional é que os auto-tipos podem especificar tipos que não são de class. Por exemplo

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

    O tipo self aqui é um tipo estrutural. O efeito é dizer que qualquer coisa que seja mixada no Foo deve implementar uma unidade de retorno do método no-arg “close”. Isso permite misturas seguras para a digitação de pato.

    A seção 2.3 “Anotações do Selftype” do original Scala de Martin Odersky, Abstract Scalable Component Abstractions na verdade explica o propósito do selftype além da composição do mixin muito bem: provê uma maneira alternativa de associar uma class com um tipo abstrato.

    O exemplo dado no artigo era como o seguinte, e não parece ter um correspondente elegante de subclass:

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

    Outra coisa que não foi mencionada: porque os auto-tipos não fazem parte da hierarquia da class requerida, eles podem ser excluídos da correspondência de padrões, especialmente quando você está combinando exaustivamente com uma hierarquia selada. Isso é conveniente quando você deseja modelar comportamentos ortogonais, como:

     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 

    Vamos começar com a dependência cíclica.

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

    No entanto, a modularidade dessa solução não é tão boa quanto parece, porque você pode replace os tipos de self da seguinte forma:

     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 

    Embora, se você replace um membro de um tipo próprio, você perderá o access ao membro original, que ainda pode ser acessado por super usando inheritance. Então, o que é realmente ganho com o uso de inheritance é:

     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 

    Agora não posso afirmar que compreendo todas as sutilezas do padrão do bolo, mas me parece que o principal método para impor a modularidade é através da composição do que da inheritance ou dos tipos de self.

    A versão de inheritance é mais curta, mas a principal razão pela qual eu prefiro a inheritance em relação aos tipos de self é que eu acho muito mais complicado obter a ordem de boot correta com tipos de self. No entanto, existem algumas coisas que você pode fazer com os tipos que você não pode fazer com inheritance. Tipos auto-suficientes podem usar um tipo, enquanto a inheritance requer um atributo ou uma class, como em:

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

    Você pode até fazer:

     trait TypeBuster { this: Int with String => } 

    Embora você nunca seja capaz de instanciá-lo. Eu não vejo nenhum motivo absoluto para não ser capaz de herdar de um tipo, mas eu certamente acho que seria útil ter classs e traços de construtores de caminho, já que temos classs / classs de construtor de tipo. Infelizmente

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

    Nós temos isso:

     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 } 

    Ou isto:

      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 } 

    Um ponto que deve ser mais empático é que as características podem ampliar as classs. Obrigado a David Maclver por apontar isso. Aqui está um exemplo do meu próprio código:

     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 herda da class Swing Frame, então ele pode ser usado como um tipo self e depois misturado no final (na instanciação). No entanto, val geomR precisa ser inicializado antes de ser usado herdando traços. Então, precisamos de uma class para impor a boot prévia do geomR . A class ScnVista pode então ser herdada por múltiplos traços ortogonais dos quais eles próprios podem ser herdados. A utilização de parâmetros de vários tipos (genéricos) oferece uma forma alternativa de modularidade.

    TL; DR resumo das outras respostas:

    • Os tipos estendidos são expostos a tipos herdados, mas os tipos não são

      ex.: class Cow { this: FourStomachs } permite que você use methods disponíveis apenas para ruminantes, como digestGrass . Os traços que estendem o Cow, entretanto, não terão tais privilégios. Por outro lado, a class Cow extends FourStomachs irá expor digestGrass para quem extends Cow .

    • auto-tipos permitem dependencies cíclicas, estendendo-se outros tipos não

     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 } 

    Um tipo self permite que você especifique quais tipos podem misturar um traço. Por exemplo, se você tiver uma característica com um tipo autônomo Closeable , essa característica saberá que as únicas coisas que podem ser misturadas, devem implementar a interface Closeable .

    Atualização: A principal diferença é que os tipos de auto podem depender de várias classs (admito que é um caso de canto). Por exemplo, você pode ter

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

    Isso permite adicionar o Employin mixin apenas a qualquer coisa que seja uma subclass de Person and Expense . Claro, isso só é significativo se Expense estender Person ou vice-versa. O ponto é que o uso de auto-tipos Employee pode ser independente da hierarquia das classs das quais depende. Não importa o que estende o que – Se você mudar a hierarquia de Expense vs Person , você não precisa modificar Employee .

    no primeiro caso, uma sub-característica ou subclass de B pode ser misturada a qualquer que seja o uso de A. Assim, B pode ser um traço abstrato.