Estes são chamados de restrições de tipo generalizado . Eles permitem que você, dentro de uma classe ou característica com parâmetros de tipo, restrinja ainda mais um de seus parâmetros de tipo. Aqui está um exemplo:
case class Foo[A](a:A) { // 'A' can be substituted with any type
// getStringLength can only be used if this is a Foo[String]
def getStringLength(implicit evidence: A =:= String) = a.length
}
O argumento implícito evidence
é fornecido pelo compilador, se A
for String
. Você pode pensar nisso como uma prova de que A
é String
--o argumento em si não é importante, sabendo apenas que ele existe. [editar: bem, tecnicamente é realmente importante porque representa uma conversão implícita de A
para String
, que é o que permite que você chame a.length
e não faça o compilador gritar com você]
Agora eu posso usá-lo assim:
scala> Foo("blah").getStringLength
res6: Int = 4
Mas se eu tentasse usá-lo com um Foo
contendo algo diferente de um String
:
scala> Foo(123).getStringLength
<console>:9: error: could not find implicit value for parameter evidence: =:=[Int,String]
Você pode ler esse erro como "não foi possível encontrar evidências de que Int == String" ... é como deveria ser! getStringLength
está impondo mais restrições ao tipo do A
que o que Foo
geralmente exige; ou seja, você só pode chamar getStringLength
em a Foo[String]
. Essa restrição é imposta em tempo de compilação, o que é legal!
<:<
e <%<
funcionam da mesma forma, mas com pequenas variações:
A =:= B
significa que A deve ser exatamente B
A <:< B
significa que A deve ser um subtipo de B (análogo à restrição de tipo simples<:
)
A <%< B
significa que A deve ser visualizado como B, possivelmente via conversão implícita (análoga à restrição de tipo simples <%
)
Esse trecho de @retronym é uma boa explicação de como esse tipo de coisa costumava ser realizado e como restrições de tipo generalizadas facilitam agora.
TERMO ADITIVO
Para responder à sua pergunta de acompanhamento, é certo que o exemplo que dei é bastante artificial e não é obviamente útil. Mas imagine usá-lo para definir algo como um List.sumInts
método, que adiciona uma lista de números inteiros. Você não deseja permitir que esse método seja chamado em nenhum antigo List
, apenas a List[Int]
. No entanto, o List
construtor de tipos não pode ser tão restrito; você ainda deseja ter listas de strings, foos, barras e outras coisas. Portanto, ao colocar uma restrição de tipo generalizada sumInts
, você pode garantir que apenas esse método tenha uma restrição adicional de que ele só pode ser usado em a List[Int]
. Essencialmente, você está escrevendo um código de caso especial para certos tipos de listas.