Estos se denominan restricciones de tipo generalizadas . Le permiten, desde dentro de una clase o rasgo con parámetros de tipo, restringir aún más uno de sus parámetros de tipo. Aquí hay un ejemplo:
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
}
El evidence
compilador proporciona el argumento implícito , iff A
es String
. Se puede pensar en él como una prueba de que A
es String
--el argumento en sí mismo no es importante, sólo el saber que existe. [edit: bueno, técnicamente en realidad es importante porque representa una conversión implícita de A
a String
, que es lo que te permite llamar a.length
y no que el compilador te grite]
Ahora puedo usarlo así:
scala> Foo("blah").getStringLength
res6: Int = 4
Pero si intenté usarlo con un Foo
contenido que no sea un String
:
scala> Foo(123).getStringLength
<console>:9: error: could not find implicit value for parameter evidence: =:=[Int,String]
Puede leer ese error como "no se pudo encontrar evidencia de que Int == String" ... ¡así es como debería ser! getStringLength
está imponiendo restricciones en el tipo de A
lo que Foo
en general requiere; a saber, solo puede invocar getStringLength
en un Foo[String]
. Esta restricción se aplica en tiempo de compilación, ¡lo cual es genial!
<:<
y <%<
funcionan de manera similar, pero con ligeras variaciones:
A =:= B
significa que A debe ser exactamente B
A <:< B
significa que A debe ser un subtipo de B (análogo a la restricción de tipo simple<:
)
A <%< B
significa que A debe ser visible como B, posiblemente mediante conversión implícita (análoga a la restricción de tipo simple <%
)
Este fragmento de @retronym es una buena explicación de cómo se lograba este tipo de cosas y cómo las restricciones de tipo generalizadas lo hacen más fácil ahora.
APÉNDICE
Para responder a su pregunta de seguimiento, es cierto que el ejemplo que di es bastante artificial y obviamente no es útil. Pero imagine usarlo para definir algo así como un List.sumInts
método, que suma una lista de enteros. No desea permitir que este método se invoque en ningún antiguo List
, solo a List[Int]
. Sin embargo, el List
constructor de tipos no puede ser tan limitado; aún desea poder tener listas de cadenas, foos, barras y demás. Por lo tanto, al colocar una restricción de tipo generalizada sumInts
, puede asegurarse de que solo ese método tenga una restricción adicional que solo se pueda usar en un List[Int]
. Esencialmente, está escribiendo un código de caso especial para ciertos tipos de listas.