En Scala, podemos usar al menos dos métodos para actualizar tipos nuevos o existentes. Supongamos que queremos expresar que algo se puede cuantificar usando un Int
. Podemos definir el siguiente rasgo.
Conversión implícita
trait Quantifiable{ def quantify: Int }
Y luego podemos usar conversiones implícitas para cuantificar, por ejemplo, cadenas y listas.
implicit def string2quant(s: String) = new Quantifiable{
def quantify = s.size
}
implicit def list2quantifiable[A](l: List[A]) = new Quantifiable{
val quantify = l.size
}
Después de importarlos, podemos llamar al método quantify
en cadenas y listas. Tenga en cuenta que la lista cuantificable almacena su longitud, por lo que evita el costoso recorrido de la lista en llamadas posteriores a quantify
.
Clases de tipo
Una alternativa es definir un "testigo" Quantified[A]
que declare que algún tipo A
puede cuantificarse.
trait Quantified[A] { def quantify(a: A): Int }
Luego proporcionamos instancias de esta clase de tipo para String
y en List
algún lugar.
implicit val stringQuantifiable = new Quantified[String] {
def quantify(s: String) = s.size
}
Y si luego escribimos un método que necesita cuantificar sus argumentos, escribimos:
def sumQuantities[A](as: List[A])(implicit ev: Quantified[A]) =
as.map(ev.quantify).sum
O usando la sintaxis ligada al contexto:
def sumQuantities[A: Quantified](as: List[A]) =
as.map(implicitly[Quantified[A]].quantify).sum
Pero, ¿cuándo usar qué método?
Ahora viene la pregunta. ¿Cómo puedo decidir entre esos dos conceptos?
Lo que he notado hasta ahora.
clases de tipo
- las clases de tipos permiten la agradable sintaxis vinculada al contexto
- con clases de tipo, no creo un nuevo objeto contenedor en cada uso
- la sintaxis ligada al contexto ya no funciona si la clase de tipo tiene varios parámetros de tipo; imagina que quiero cuantificar cosas no solo con números enteros sino con valores de algún tipo general
T
. Me gustaría crear una clase de tipoQuantified[A,T]
conversión implícita
- como creo un nuevo objeto, puedo almacenar valores en caché allí o calcular una mejor representación; pero ¿debo evitar esto, ya que podría suceder varias veces y una conversión explícita probablemente se invocaría solo una vez?
Lo que espero de una respuesta
Presente uno (o más) casos de uso en los que la diferencia entre ambos conceptos sea importante y explique por qué preferiría uno sobre el otro. También sería bueno explicar la esencia de los dos conceptos y su relación entre sí, incluso sin ejemplo.
fuente
size
de una lista en un valor y decir que evita el recorrido caro de la lista de llamadas posteriores a cuantificar, sino de toda su llamada a laquantify
dellist2quantifiable
se desencadena de nuevo restableciendoQuantifiable
y recalculando laquantify
propiedad. Lo que estoy diciendo es que en realidad no hay forma de almacenar en caché los resultados con conversiones implícitas.Respuestas:
Si bien no quiero duplicar mi material de Scala In Depth , creo que vale la pena señalar que las clases de tipo / rasgos de tipo son infinitamente más flexibles.
tiene la capacidad de buscar en su entorno local una clase de tipo predeterminada. Sin embargo, puedo anular el comportamiento predeterminado en cualquier momento de una de estas dos formas:
He aquí un ejemplo:
Esto hace que las clases de tipos sean infinitamente más flexibles. Otra cosa es que las clases / rasgos de tipo soportan mejor la búsqueda implícita .
En su primer ejemplo, si usa una vista implícita, el compilador hará una búsqueda implícita para:
Que mirará
Function1
el objeto compañero y elInt
objeto compañero.Observe que no
Quantifiable
está en ninguna parte de la búsqueda implícita. Esto significa que debe colocar la vista implícita en un objeto de paquete o importarla al alcance. Es más trabajo recordar lo que está pasando.Por otro lado, una clase de tipo es explícita . Ves lo que busca en la firma del método. También tiene una búsqueda implícita de
que buscará
Quantifiable
el objeto complementario de yInt
el objeto complementario de. Lo que significa que puede proporcionar valores predeterminados y nuevos tipos (como unaMyString
clase) pueden proporcionar un valor predeterminado en su objeto complementario y se buscará implícitamente.En general, utilizo clases de tipos. Son infinitamente más flexibles para el ejemplo inicial. El único lugar en el que utilizo conversiones implícitas es cuando uso una capa de API entre un contenedor de Scala y una biblioteca de Java, e incluso esto puede ser 'peligroso' si no tiene cuidado.
fuente
Un criterio que puede entrar en juego es cómo quiere que se "sienta" la nueva función; usando conversiones implícitas, puede hacer que parezca que es solo otro método:
... mientras usa clases de tipos, siempre parecerá que está llamando a una función externa:
Una cosa que puede lograr con clases de tipos y no con conversiones implícitas es agregar propiedades a un tipo , en lugar de a una instancia de un tipo. A continuación, puede acceder a estas propiedades incluso cuando no tiene una instancia del tipo disponible. Un ejemplo canónico sería:
Este ejemplo también muestra cómo los conceptos están estrechamente relacionados: las clases de tipos no serían tan útiles si no hubiera un mecanismo para producir un número infinito de sus instancias; sin el
implicit
método (no una conversión, es cierto), solo podría tener un número finito de tipos con laDefault
propiedad.fuente
default
para futuros lectores.Puede pensar en la diferencia entre las dos técnicas por analogía con la aplicación de la función, solo con un contenedor con nombre. Por ejemplo:
Una instancia del primero encapsula una función de tipo
A => Int
, mientras que una instancia del segundo ya se ha aplicado a unA
. Podrías continuar con el patrón ...por lo tanto, podría pensar en una
Foo1[B]
especie de aplicación parcial deFoo2[A, B]
a algunaA
instancia. Miles Sabin escribió un gran ejemplo de esto como "Dependencias funcionales en Scala" .Así que realmente mi punto es que, en principio:
fuente