Um tipo próprio para uma característica A
:
trait B
trait A { this: B => }
diz que " A
não pode ser misturado em uma classe concreta que também não se estende B
" .
Por outro lado, o seguinte:
trait B
trait A extends B
diz que "qualquer classe (concreta ou abstrata) que se misturar A
também se misturará em B" .
Essas duas afirmações não significam a mesma coisa? O tipo próprio parece servir apenas para criar a possibilidade de um simples erro em tempo de compilação.
o que estou perdendo?
trait A[Self] {this: Self => }
é legal,trait A[Self] extends Self
não é.Respostas:
É usado predominantemente para injeção de dependência , como no padrão de bolo. Existe um ótimo artigo que cobre muitas formas diferentes de injeção de dependência no Scala, incluindo o Cake Pattern. Se você pesquisar no Google "Cake Pattern and Scala", obterá muitos links, incluindo apresentações e vídeos. Por enquanto, aqui está um link para outra pergunta .
Agora, qual é a diferença entre um tipo de eu e a extensão de uma característica, isso é simples. Se você diz
B extends A
, entãoB
é umA
. Quando você usa auto-tipos,B
requer umA
. Existem dois requisitos específicos criados com auto-tipos:B
for estendido, você precisará misturar umA
.A
.Considere os seguintes exemplos:
Se
Tweeter
fosse uma subclasse deUser
, não haveria erro. No código acima, solicitamos umUser
sempre queTweeter
é usado, no entanto, umUser
não foi fornecidoWrong
, portanto, ocorreu um erro. Agora, com o código acima ainda no escopo, considere:Com
Right
, o requisito de misturar umUser
é atendido. No entanto, o segundo requisito mencionado acima não é atendido: o ônus da implementaçãoUser
ainda permanece para as classes / características que se estendemRight
.Com
RightAgain
ambos os requisitos são satisfeitos. AUser
e uma implementação deUser
são fornecidas.Para casos de uso mais práticos, consulte os links no início desta resposta! Mas espero que agora você entenda.
fonte
trait WarmerComponentImpl extends SensorDeviceComponent with OnOffDeviceComponent
? Isso causariaWarmerComponentImpl
ter essas interfaces. Eles estaria disponível para qualquer coisa que se estendeuWarmerComponentImpl
, o que é claramente errado, pois é não umSensorDeviceComponent
, nem umOnOffDeviceComponent
. Como um tipo próprio, essas dependências estão disponíveis exclusivamente paraWarmerComponentImpl
. AList
pode ser usado comoArray
e vice-versa. Mas eles simplesmente não são a mesma coisa.this
com tipos próprios é algo que desprezo, pois obscurece, sem uma boa razão, o originalthis
.self: Dep1 with Dep2 =>
.Os tipos próprios permitem definir dependências cíclicas. Por exemplo, você pode conseguir isso:
A herança usando
extends
não permite isso. Tentar:No livro Odersky, consulte a seção 33.5 (capítulo Criação da interface do usuário da planilha), onde ele menciona:
Espero que isto ajude.
fonte
Uma diferença adicional é que os tipos próprios podem especificar tipos que não são de classe. Por exemplo
O tipo de auto aqui é um tipo estrutural. O efeito é dizer que qualquer coisa que se misture no Foo deve implementar uma unidade de retorno de método sem argumento "fechar". Isso permite mixins seguros para digitação de patos.
fonte
abstract class A extends {def close:Unit}
é equivalente aabstract class A {def close:Unit}
. Portanto, não envolve tipos estruturais.A Seção 2.3 "Anotações de autotipo" do artigo Scala original de Martin Odersky, intitulado Scalable Component Abstractions, na verdade explica muito bem o propósito do autotipo além da composição da mixina: forneça uma maneira alternativa de associar uma classe a um tipo abstrato.
O exemplo dado no artigo foi o seguinte, e não parece ter um correspondente elegante da subclasse:
fonte
Outra coisa que não foi mencionada: como os tipos próprios não fazem parte da hierarquia da classe necessária, eles podem ser excluídos da correspondência de padrões, especialmente quando você está correspondendo exaustivamente a uma hierarquia selada. Isso é conveniente quando você deseja modelar comportamentos ortogonais, como:
fonte
TL; DR resumo das outras respostas:
Os tipos que você estende são expostos a tipos herdados, mas os tipos próprios não são
por exemplo:
class Cow { this: FourStomachs }
permite usar métodos disponíveis apenas para ruminantes, comodigestGrass
. As características que estendem Cow, no entanto, não terão esses privilégios. Por outro lado,class Cow extends FourStomachs
irá expordigestGrass
a quem quiserextends Cow
.auto-tipos permitem dependências cíclicas, estender outros tipos não
fonte
Vamos começar com a dependência cíclica.
No entanto, a modularidade dessa solução não é tão boa quanto parece à primeira vista, porque você pode substituir os tipos de self da seguinte maneira:
Embora, se você substituir um membro de um tipo próprio, perderá o acesso ao membro original, que ainda poderá ser acessado por meio do super uso da herança. Então, o que realmente é ganho com o uso de herança é:
Agora, não posso pretender entender todas as sutilezas do padrão de bolo, mas me parece que o principal método de impor a modularidade é através da composição, e não da herança ou tipos próprios.
A versão da herança é mais curta, mas a principal razão pela qual prefiro a herança sobre os tipos próprios é que acho muito mais complicado obter a ordem de inicialização correta com os tipos próprios. No entanto, existem algumas coisas que você pode fazer com tipos próprios que você não pode fazer com herança. Auto-tipos podem usar um tipo, enquanto a herança exige uma característica ou uma classe, como em:
Você pode até fazer:
Embora você nunca seja capaz de instanciar isso. Não vejo nenhuma razão absoluta para não ser capaz de herdar de um tipo, mas certamente acho que seria útil ter classes e características de construtor de caminho, como temos características / classes de construtor de tipo. Infelizmente
Nós temos isso:
Ou isto:
Um ponto que deve ser mais enfatizado é que os traços podem estender as classes. Agradecemos a David Maclver por apontar isso. Aqui está um exemplo do meu próprio código:
ScnBase
herda da classe Swing Frame, para que possa ser usada como um tipo próprio e depois misturada no final (na instanciação). No entanto,val geomR
precisa ser inicializado antes de ser usado pela herança de características. Portanto, precisamos de uma classe para impor a inicialização prévia degeomR
. A classeScnVista
pode ser herdada de várias características ortogonais das quais elas próprias podem ser herdadas. O uso de vários parâmetros de tipo (genéricos) oferece uma forma alternativa de modularidade.fonte
fonte
Um tipo próprio permite especificar quais tipos têm permissão para misturar uma característica. Por exemplo, se você tem uma característica com um tipo próprio
Closeable
, essa característica sabe que as únicas coisas que podem misturá-la devem implementar aCloseable
interface.fonte
trait A { self:B => ... }
, uma declaraçãoX with A
só será válida se X estender B. Sim, você pode dizerX with A with Q
, onde Q não estende B, mas acredito que o ponto de kikibobo era que X é tão restrito. Ou eu perdi alguma coisa?Atualização: A principal diferença é que os tipos próprios podem depender de várias classes (eu admito que isso seja um pouco esquecido). Por exemplo, você pode ter
Isso permite adicionar o
Employee
mixin a qualquer coisa que seja uma subclasse dePerson
eExpense
. Obviamente, isso só tem sentido se forExpense
estendidoPerson
ou vice-versa. O ponto é que o uso de tipos própriosEmployee
pode ser independente da hierarquia das classes das quais depende. Ele não se importa com o que estende o quê - Se você alternar a hierarquia deExpense
vsPerson
, não precisará modificarEmployee
.fonte
no primeiro caso, uma sub-característica ou sub-classe de B pode ser misturada a qualquer que seja o uso de A. Portanto, B pode ser uma característica abstrata.
fonte