Un auto-tipo para un rasgo A
:
trait B
trait A { this: B => }
dice que " A
no se puede mezclar en una clase concreta que no se extienda B
" .
Por otro lado, lo siguiente:
trait B
trait A extends B
dice que "cualquier clase (concreta o abstracta) que se mezcle A
también se mezclará en B" .
¿Estas dos afirmaciones no significan lo mismo? El auto-tipo parece servir solo para crear la posibilidad de un simple error en tiempo de compilación.
¿Qué me estoy perdiendo?
trait A[Self] {this: Self => }
es legal,trait A[Self] extends Self
no lo es.Respuestas:
Se utiliza principalmente para la inyección de dependencia , como en el patrón de pastel. Existe un gran artículo que cubre muchas formas diferentes de inyección de dependencia en Scala, incluido el Patrón de pastel. Si buscas en Google "Cake Pattern and Scala", obtendrás muchos enlaces, incluidas presentaciones y videos. Por ahora, aquí hay un enlace a otra pregunta .
Ahora, en cuanto a cuál es la diferencia entre un auto tipo y extender un rasgo, eso es simple. Si dices
B extends A
, entoncesB
es unA
. Cuando usas auto-tipos,B
requiere unA
. Hay dos requisitos específicos que se crean con autotipos:B
se prolonga, entonces usted está obligado a mezclar en unaA
.A
.Considere los siguientes ejemplos:
Si
Tweeter
fuera una subclase deUser
, no habría error. En el código anterior, que requiere unaUser
cada vez queTweeter
se utiliza, sin embargo, unUser
no se proporcionó aWrong
, así que conseguimos un error. Ahora, con el código anterior todavía en el alcance, considere:Con
Right
,User
se cumple el requisito de mezclar a . Sin embargo, el segundo requisito mencionado anteriormente no se cumple: la carga de la implementaciónUser
sigue siendo para las clases / rasgos que se extiendenRight
.Con
RightAgain
ambos requisitos se cumplen. AUser
y una implementación deUser
se proporcionan.Para casos de uso más prácticos, consulte los enlaces al comienzo de esta respuesta. Pero, con suerte ahora lo entiendes.
fuente
trait WarmerComponentImpl extends SensorDeviceComponent with OnOffDeviceComponent
? Eso provocaríaWarmerComponentImpl
tener esas interfaces. Estarían disponibles para cualquier cosa que se extiendaWarmerComponentImpl
, lo cual es claramente incorrecto, ya que no es unSensorDeviceComponent
, ni unOnOffDeviceComponent
. Como tipo automático, estas dependencias están disponibles exclusivamente paraWarmerComponentImpl
. AList
podría usarse como unArray
y viceversa. Pero simplemente no son lo mismo.this
con tipos propios es algo que menosprecio, ya que no tiene ninguna razón para ocultar el originalthis
.self: Dep1 with Dep2 =>
.Los tipos propios le permiten definir dependencias cíclicas. Por ejemplo, puedes lograr esto:
El uso de la herencia
extends
no lo permite. Tratar:En el libro de Odersky, mire la sección 33.5 (Creación del capítulo de la UI de la hoja de cálculo) donde menciona:
Espero que esto ayude.
fuente
Una diferencia adicional es que los autotipos pueden especificar tipos que no son de clase. Por ejemplo
El auto tipo aquí es un tipo estructural. El efecto es decir que cualquier cosa que se mezcle en Foo debe implementar una unidad de retorno del método "cerrar" sin argumentos. Esto permite mezclas seguras para la tipificación de patos.
fuente
abstract class A extends {def close:Unit}
es equivalente aabstract class A {def close:Unit}
. Por lo tanto, no involucra tipos estructurales.La sección 2.3 "Anotaciones de autotipo" del papel Scala original de Martin Odersky. Las abstracciones de componentes escalables en realidad explican muy bien el propósito del autotipo más allá de la composición mixin: proporcionar una forma alternativa de asociar una clase con un tipo abstracto.
El ejemplo dado en el documento fue el siguiente, y no parece tener un corresponsal de subclase elegante:
fuente
Otra cosa que no se ha mencionado: debido a que los autotipos no son parte de la jerarquía de la clase requerida, se pueden excluir de la coincidencia de patrones, especialmente cuando se compara exhaustivamente con una jerarquía sellada. Esto es conveniente cuando desea modelar comportamientos ortogonales como:
fuente
TL; DR resumen de las otras respuestas:
Los tipos que extiende están expuestos a los tipos heredados, pero los autotipos no
Por ejemplo: le
class Cow { this: FourStomachs }
permite utilizar métodos solo disponibles para rumiantes, comodigestGrass
. Sin embargo, los rasgos que extienden Cow no tendrán tales privilegios. Por otro lado,class Cow extends FourStomachs
expondrádigestGrass
a quien seaextends Cow
.los auto-tipos permiten dependencias cíclicas, extender otros tipos no
fuente
Comencemos con la dependencia cíclica.
Sin embargo, la modularidad de esta solución no es tan buena como podría parecer en un principio, ya que puede anular los tipos propios de esta manera:
Sin embargo, si anula un miembro de tipo automático, pierde el acceso al miembro original, al que aún se puede acceder a través de super usando la herencia. Entonces, lo que realmente se gana con el uso de la herencia es:
Ahora no puedo afirmar que entiendo todas las sutilezas del patrón de pastel, pero me sorprende que el método principal para imponer la modularidad es a través de la composición en lugar de la herencia o los tipos propios.
La versión de herencia es más corta, pero la razón principal por la que prefiero la herencia sobre los tipos propios es que me resulta mucho más difícil obtener el orden de inicialización correcto con los tipos propios. Sin embargo, hay algunas cosas que puede hacer con los tipos propios que no puede hacer con la herencia. Los tipos propios pueden usar un tipo mientras que la herencia requiere un rasgo o una clase como en:
Incluso puedes hacer:
Aunque nunca podrás instanciarlo. No veo ninguna razón absoluta para no poder heredar de un tipo, pero ciertamente creo que sería útil tener clases y rasgos de constructor de ruta ya que tenemos rasgos / clases de constructor de tipo. Como lamentablemente
Tenemos esto:
O esto:
Un punto que debería empatizarse más es que los rasgos pueden extender las clases. Gracias a David Maclver por señalar esto. Aquí hay un ejemplo de mi propio código:
ScnBase
hereda de la clase Swing Frame, por lo que podría usarse como un tipo automático y luego mezclarse al final (en la instanciación). Sin embargo,val geomR
debe inicializarse antes de que se use heredando rasgos. Por lo tanto, necesitamos una clase para imponer la inicialización previa degeomR
. La claseScnVista
puede ser heredada de múltiples rasgos ortogonales de los cuales ellos mismos pueden ser heredados. El uso de múltiples parámetros de tipo (genéricos) ofrece una forma alternativa de modularidad.fuente
fuente
Un tipo automático le permite especificar qué tipos se pueden mezclar en un rasgo. Por ejemplo, si tiene un rasgo con un tipo automático
Closeable
, ese rasgo sabe que las únicas cosas que pueden mezclarlo deben implementar laCloseable
interfaz.fuente
trait A { self:B => ... }
, una declaraciónX with A
solo es válida si X extiende a B. Sí, puede decirX with A with Q
, donde Q no extiende a B, pero creo que el punto de kikibobo es que X está tan restringido. ¿O me perdí algo?Actualización: una diferencia principal es que los auto-tipos pueden depender de múltiples clases (admito que es un caso de esquina). Por ejemplo, puedes tener
Esto permite agregar el
Employee
mixin a cualquier cosa que sea una subclase dePerson
yExpense
. Por supuesto, esto solo tiene sentido si seExpense
extiendePerson
o viceversa. El punto es que el uso de auto-tiposEmployee
puede ser independiente de la jerarquía de las clases de las que depende. No le importa qué extiende qué: si cambia la jerarquía deExpense
vsPerson
, no tiene que modificarEmployee
.fuente
en el primer caso, una subcaracter o subclase de B se puede mezclar con cualquier uso de A. Entonces B puede ser una característica abstracta.
fuente