Puedo ver en los documentos de API para Predef que son subclases de un tipo de función genérica (From) => To, pero eso es todo lo que dice. ¿Um que? Tal vez hay documentación en alguna parte, pero los motores de búsqueda no manejan muy bien los "nombres" como "<: <", por lo que no he podido encontrarlo.
Pregunta de seguimiento: ¿cuándo debo usar estos símbolos / clases originales y por qué?
typeclass
es realiza el trabajo de estos operadores? Ejemplo:compare :: Ord a => a -> a -> Ordering
? Estoy tratando de entender este concepto de Scala con respecto a su contraparte de Haskell.Respuestas:
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:
El
evidence
compilador proporciona el argumento implícito , iffA
esString
. Se puede pensar en él como una prueba de queA
esString
--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 deA
aString
, que es lo que te permite llamara.length
y no que el compilador te grite]Ahora puedo usarlo así:
Pero si intenté usarlo con un
Foo
contenido que no sea unString
: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 deA
lo queFoo
en general requiere; a saber, solo puede invocargetStringLength
en unFoo[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 BA <:< 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 antiguoList
, solo aList[Int]
. Sin embargo, elList
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 generalizadasumInts
, puede asegurarse de que solo ese método tenga una restricción adicional que solo se pueda usar en unList[Int]
. Esencialmente, está escribiendo un código de caso especial para ciertos tipos de listas.fuente
Manifest
, que no mencionaste.Manifest
están en<:<
y>:>
solo ... ya que OP mencionó exactamente las 3 variedades de restricciones de tipo generalizadas, supongo que eso era lo que le interesaba.class =:=[From, To] extends From => To
, lo que significa que un valor implícito de tipoFrom =:= To
es en realidad una conversión implícita deFrom
aTo
. Entonces, al aceptar un parámetro implícito de tipoA =:= String
, estás diciendo queA
se puede convertir implícitamente aString
. Si cambia el orden y hace que el argumento implícito sea de tipoString =:= A
, no funcionaría, porque sería una conversión implícita deString
aA
.From =:= To
en alcance implica que tiene una conversión implícitaFrom => To
, pero la implicación no se ejecuta al revés; tener una conversión implícitaA => B
no no implica que tiene una instancia deA =:= B
.=:=
es una clase abstracta sellada definida enscala.Predef
, y tiene solo una instancia expuesta públicamente, que es implícita y es de tipoA =:= A
. Lo que está garantizado que un valor implícito del tipoA =:= B
testigos del hecho de queA
yB
son iguales.No es una respuesta completa (otros ya han respondido esto), solo quería señalar lo siguiente, que tal vez ayude a comprender mejor la sintaxis: la forma en que normalmente usa estos "operadores", como por ejemplo en el ejemplo de pelotom:
hace uso de la sintaxis infija alternativa de Scala para 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 sintaxis también funciona con clases "regulares", por ejemplo, puede escribir:Me gusta esto:
Es similar a las dos sintaxis para llamadas a métodos, la sintaxis "normal" con
.
y()
y el operador.fuente
makes use of Scala's alternative infix syntax for type operators.
falta totalmente esta explicación sin la cual todo esto no tiene sentidoLea las otras respuestas para comprender cuáles son estos constructos. Aquí es cuando debes usarlos. Los usa cuando necesita restringir un método solo para tipos específicos.
Aquí hay un ejemplo. Suponga que desea definir un par homogéneo, así:
Ahora desea agregar un método
smaller
, como este:Eso solo funciona si
T
se ordena. Puedes restringir toda la clase:Pero eso parece una pena, podría haber usos para la clase cuando
T
no se ordena. Con una restricción de tipo, aún puede definir elsmaller
método:Está bien instanciar, digamos, a
Pair[File]
, siempre y cuando no llamessmaller
.En el caso de
Option
, los implementadores querían unorNull
método, aunque no tiene sentidoOption[Int]
. Al usar una restricción de tipo, todo está bien. Puede usarloorNull
enOption[String]
, y puede formarloOption[Int]
y usarlo, siempre que no lo llameorNull
. Si lo intentasSome(42).orNull
, obtienes el mensaje encantadorfuente
<:<
, y creo que elOrdered
ejemplo ya no es tan convincente ya que ahora preferiría usar laOrdering
clase de tipos en lugar delOrdered
rasgo. Algo así como:def smaller(implicit ord: Ordering[T]) = if (ord.lt(first, second)) first else second
.Depende de dónde se estén utilizando. 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 de
Manifest
objetos. Se definen dentroscala.Predef
de los dos primeros casos, aunque no están particularmente bien documentados.Su objetivo es proporcionar una forma de probar la relación entre las clases, al igual que
<:
y<%
en las situaciones en que estas últimas no se pueden utilizar.En cuanto a la pregunta "¿cuándo debería usarlos?", La respuesta es que no debería, a menos que sepa que debería hacerlo. :-) EDITAR : Ok, ok, aquí hay algunos ejemplos de la biblioteca. En
Either
, tienes:En
Option
, tienes:Encontrarás otros ejemplos en las colecciones.
fuente
:-)
otro de estos? Y estaría de acuerdo en que su respuesta a "¿Cuándo debería usarlos?" se aplica a muchas cosas.