¿Cuál es la diferencia entre autotipos y subclases de rasgos?

Resuelto Dave asked hace 14 años • 11 respuestas

Un autotipo para un rasgo A:

trait B
trait A { this: B => }

Dice que " Ano se puede mezclar en una clase concreta que no se extienda también B" .

Por otro lado, lo siguiente:

trait B
trait A extends B

dice que "cualquier clase (concreta o abstracta) que se mezcle Atambién se mezclará en B" .

¿No significan lo mismo estas dos afirmaciones? El tipo propio parece servir sólo para crear la posibilidad de un simple error en tiempo de compilación.

¿Qué me estoy perdiendo?

Dave avatar Jan 02 '10 15:01 Dave
Aceptado

Se utiliza predominantemente para la inyección de dependencias , como en el patrón Cake. Existe un excelente artículo que cubre muchas formas diferentes de inyección de dependencia en Scala, incluido Cake Pattern. Si busca en Google "Cake Pattern and Scala", obtendrá muchos enlaces, incluidas presentaciones y videos. Por ahora, aquí hay un enlace a otra pregunta .

Ahora bien, en cuanto a cuál es la diferencia entre un tipo de yo y extender un rasgo, eso es simple. Si dices B extends A, entonces B es un A. Cuando usas tipos propios, B requiere un archivo A. Hay dos requisitos específicos que se crean con los tipos propios:

  1. Si Bestá extendido, entonces deberás mezclar un archivo A.
  2. Cuando una clase concreta finalmente extiende/mezcla estos rasgos, alguna clase/rasgo debe implementarse A.

Considere los siguientes ejemplos:

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
                       ^

Si Tweeterfuera una subclase de User, no habría ningún error. En el código anterior, requerimos que se use Usersiempre , sin embargo, no se proporcionó , por lo que recibimos un error. Ahora, con el código anterior todavía dentro del alcance, considere:TweeterUserWrong

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

Con , se cumple Rightel requisito de mezclar a . UserSin embargo, el segundo requisito mencionado anteriormente no se cumple: la carga de la implementación Useraún permanece para las clases/rasgos que se extienden Right.

Con RightAgainambos requisitos se cumplen. Se proporcionan A Usery una implementación de .User

Para casos de uso más prácticos, consulte los enlaces al comienzo de esta respuesta. Pero espero que ahora lo entiendas.

Daniel C. Sobral avatar Jan 02 '2010 14:01 Daniel C. Sobral

Los tipos propios le permiten definir dependencias cíclicas. Por ejemplo, puedes lograr esto:

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

El uso de herencia extendsno lo permite. Intentar:

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

En el libro de Odersky, consulte la sección 33.5 (capítulo Creación de interfaz de usuario de hoja de cálculo) donde menciona:

En el ejemplo de la hoja de cálculo, la clase Modelo hereda de Evaluador y, por lo tanto, obtiene acceso a su método de evaluación. Por el contrario, la clase Evaluador define su tipo propio como Modelo, así:

package org.stairwaybook.scells
trait Evaluator { this: Model => ...
Mushtaq Ahmed avatar Jan 02 '2010 11:01 Mushtaq Ahmed

Una diferencia adicional es que los tipos propios pueden especificar tipos que no son de clase. Por ejemplo

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

El tipo propio aquí es un tipo estructural. El efecto es decir que cualquier cosa que se mezcle en Foo debe implementar una unidad de devolución de método "cerrado" sin argumentos. Esto permite crear mixins seguros para escribir patos.

Dave Griffith avatar Jun 21 '2010 15:06 Dave Griffith

Otra cosa que no se ha mencionado: debido a que los tipos propios no son parte de la jerarquía de la clase requerida, pueden excluirse de la comparación de patrones, especialmente cuando se compara exhaustivamente con una jerarquía sellada. Esto resulta útil cuando se desea modelar comportamientos ortogonales 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
Bruno Bieth avatar Jun 03 '2015 06:06 Bruno Bieth