¿Qué significan <: <, <% <, y =: = en Scala 2.8 y dónde están documentados?

Puedo ver en los documentos API para Predef que son subclases de un tipo de función genérica (De) => A, pero eso es todo lo que dice. ¿Um que? Tal vez haya documentación en alguna parte, pero los motores de búsqueda no manejan muy bien los “nombres” como “<: <", así que no he podido encontrarlo.

Pregunta de seguimiento: ¿cuándo debería usar estos símbolos / clases originales y por qué?

Estas se llaman restricciones de tipo generalizadas . Le permiten, dentro de una clase o característica parametrizada, 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 } 

La evidence argumento implícito es suministrada por el comstackdor, si A es String . Puedes pensar que es una prueba de que A es String argumento en sí mismo no es importante, solo sabiendo que existe. [edit: bueno, técnicamente es realmente importante porque representa una conversión implícita de A a String , que es lo que te permite llamar a.length y no tener el comstackdor gritándote]

Ahora puedo usarlo así:

 scala> Foo("blah").getStringLength res6: Int = 4 

Pero si intenté usarlo con un Foo contenga algo que no sea una String :

 scala> Foo(123).getStringLength :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 más restricciones en el tipo de A de lo que Foo en general requiere; es decir, solo puede invocar getStringLength en un Foo[String] . Esta restricción se aplica en tiempo de comstackció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áloga a la restricción de tipo simple <: :)
  • A <%< B significa que A debe poder visualizarse 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 solía realizar 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 muy artificial y no obviamente útil. Pero imagine usarlo para definir algo así como un método List.sumInts , que agrega una lista de enteros. No desea permitir que este método se invoque en ninguna List , solo una List[Int] . Sin embargo, el constructor de tipo List no puede ser tan limitado; aún desea poder tener listas de cadenas, foos, barras y elementos adicionales. Por lo tanto, al colocar una restricción de tipo generalizada en sumInts , puede asegurarse de que solo ese método tenga una restricción adicional que solo pueda usarse en una List[Int] . Básicamente, estás escribiendo un código de caso especial para ciertos tipos de listas.

No es una respuesta completa (otros ya han respondido esto), solo quería señalar lo siguiente, lo que quizás ayude a comprender mejor la syntax: La forma en que normalmente usa estos “operadores”, como por ejemplo en el ejemplo de pelotom:

 def getStringLength(implicit evidence: A =:= String) 

hace uso de la syntax de infijo alternativa de Scala para los operadores de tipo .

Entonces, A =:= String es lo mismo que =:=[A, String] (y =:= es solo una clase o rasgo con un nombre elegante). Tenga en cuenta que esta syntax también funciona con clases “normales”, por ejemplo, puede escribir:

 val a: Tuple2[Int, String] = (1, "one") 

Me gusta esto:

 val a: Int Tuple2 String = (1, "one") 

Es similar a las dos syntax para llamadas a métodos, la “normal” con . y () y la syntax del operador.

Lee las otras respuestas para entender qué son estos constructos. Aquí es cuando deberías usarlos. Los usa cuando necesita restringir un método solo para tipos específicos.

Aquí hay un ejemplo. Supongamos que quiere definir un par homogéneo, como este:

 class Pair[T](val first: T, val second: T) 

Ahora quiere agregar un método smaller , como este:

 def smaller = if (first < second) first else second 

Eso solo funciona si T es ordenado. Puedes restringir toda la clase:

 class Pair[T <: Ordered[T]](val first: T, val second: T) 

Pero eso parece una pena: podría haber usos para la clase cuando T no está ordenado. Con una restricción de tipo, aún puede definir el método smaller :

 def smaller(implicit ev: T <:< Ordered[T]) = if (first < second) first else second 

Está bien instanciar, digamos, un Pair[File] , siempre y cuando no llame smaller .

En el caso de la Option , los implementadores querían un método orNull , aunque no tiene sentido para la Option[Int] . Al usar una restricción de tipo, todo está bien. Puede usar orNull en una Option[String] , y puede formar una Option[Int] y usarla, siempre que no llame o orNull en ella. Si prueba Some(42).orNull , obtendrá el encantador mensaje

  error: Cannot prove that Null <:< Int 

Depende de dónde se utilizan. Muy a menudo, cuando se usan al declarar tipos de parámetros implícitos, son clases. También pueden ser objetos en casos excepcionales. Finalmente, pueden ser operadores en objetos Manifest . Se definen dentro de scala.Predef en los primeros dos casos, aunque no están particularmente bien documentados.

Están destinados a proporcionar una manera de probar la relación entre las clases, al igual que <: y <% do, en situaciones en las que este último no se puede utilizar.

En cuanto a la pregunta "¿cuándo debería usarlos?", La respuesta es que no debería hacerlo, a menos que sepa que debería hacerlo. :-) EDITAR : Ok, vale, aquí hay algunos ejemplos de la biblioteca. En Either , tienes:

 /** * Joins an Either through Right. */ def joinRight[A1 >: A, B1 >: B, C](implicit ev: B1 <:< Either[A1, C]): Either[A1, C] = this match { case Left(a) => Left(a) case Right(b) => b } /** * Joins an Either through Left. */ def joinLeft[A1 >: A, B1 >: B, C](implicit ev: A1 <:< Either[C, B1]): Either[C, B1] = this match { case Left(a) => a case Right(b) => Right(b) } 

En la Option , tiene:

 def orNull[A1 >: A](implicit ev: Null <:< A1): A1 = this getOrElse null 

Encontrará otros ejemplos en las colecciones.