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 Afor 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 Apara String, que é o que permite que você chame a.lengthe 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 Foocontendo 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! getStringLengthestá impondo mais restrições ao tipo do Aque o que Foogeralmente exige; ou seja, você só pode chamar getStringLengthem 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 <:< Bsignifica que A deve ser um subtipo de B (análogo à restrição de tipo simples<: )
A <%< Bsignifica 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.sumIntsmé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 Listconstrutor 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.