Gostei da resposta de Roland Ewald, pois ele descreveu com um caso de uso muito simples do alias de tipo e, para mais detalhes, apresentou um tutorial muito bom. No entanto, como outro caso de uso é introduzido nesta postagem com o nome de membros do tipo , gostaria de mencionar o caso de uso mais prático, do qual gostei muito: (esta parte é retirada daqui :)
Tipo de resumo:
type T
T acima diz que esse tipo que será usado ainda é desconhecido e, dependendo da subclasse de concreto, será definido. A melhor maneira sempre de entender os conceitos de programação é fornecer um exemplo: Suponha que você tenha o seguinte cenário:
Aqui você receberá um erro de compilação, porque o método eat nas classes Cow e Tiger não substitui o método eat na classe Animal, porque seus tipos de parâmetros são diferentes. É Grass na classe Cow e Meat na classe Tiger vs. Food na classe Animal, que é super classe e todas as subclasses devem estar em conformidade.
Agora, de volta ao tipo abstração, pelo diagrama a seguir e simplesmente adicionando um tipo abstração, você pode definir o tipo da entrada, de acordo com a própria subclasse.
Agora observe os seguintes códigos:
val cow1: Cow = new Cow
val cow2: Cow = new Cow
cow1 eat new cow1.SuitableFood
cow2 eat new cow1.SuitableFood
val tiger: Tiger = new Tiger
cow1 eat new tiger.SuitableFood // Compiler error
O compilador está feliz e melhoramos nosso design. Podemos alimentar nossa vaca com vaca. O alimento e o compilador nos impedem de alimentar a vaca com o alimento adequado para o Tiger. Mas e se quisermos fazer a diferença entre o tipo de vaca1 AdequadoFood e cow2 SuitabeFood. Em outra palavra, seria muito útil em alguns cenários se o caminho pelo qual chegamos ao tipo (é claro via objeto) realmente importa. Graças aos recursos avançados do scala, é possível:
Tipos dependentes de caminho: os
objetos Scala podem ter tipos como membros. O significado do tipo depende do caminho que você usa para acessá-lo. O caminho é determinado pela referência a um objeto (também conhecido como instância de uma classe). Para implementar esse cenário, você precisa definir a classe Grass dentro da Cow, ou seja, Cow é a classe externa e Grass é a classe interna. A estrutura será assim:
class Cow extends Animal {
class Grass extends Food
type SuitableFood = Grass
override def eat(food: this.SuitableFood): Unit = {}
}
class Tiger extends Animal {
class Meat extends Food
type SuitableFood = Meat
override def eat(food: this.SuitableFood): Unit = {}
}
Agora, se você tentar compilar este código:
1. val cow1: Cow = new Cow
2. val cow2: Cow = new Cow
3. cow1 eat new cow1.SuitableFood
4. cow2 eat new cow1.SuitableFood // compilation error
Na linha 4, você verá um erro porque o Grass agora é uma classe interna do Cow, portanto, para criar uma instância do Grass, precisamos de um objeto de vaca e esse objeto de vaca determina o caminho. Então, 2 objetos de vaca dão origem a 2 caminhos diferentes. Nesse cenário, o cow2 quer apenas comer alimentos especialmente criados para ele. Assim:
cow2 eat new cow2.SuitableFood
Agora todo mundo está feliz :-)