2010-12-07 13 views
15

Nota: Formulo esta pregunta para responderla yo mismo, pero se agradecen otras respuestas.¿Cómo obtengo una instancia de la clase de tipo asociada a un contexto enlazado?

Considere el siguiente método sencillo:

def add[T](x: T, y: T)(implicit num: Numeric[T]) = num.plus(x,y) 

puedo reescribir esta usando un context bound de la siguiente manera

def add[T: Numeric](x: T, y: T) = ??.plus(x,y) 

pero ¿cómo puedo obtener una instancia del tipo Numeric[T] de modo que pueda invocar el método plus?

Respuesta

23

Utilizando el método implícitamente

El enfoque más común y general es utilizar el implicitly method, definido en Predef:

def add[T: Numeric](x: T, y: T) = implicitly[Numeric[T]].plus(x,y) 

Obviamente, esto es un tanto detallado y requiere repitiendo el nombre de la clase de tipo.

Hacer referencia el parámetro de pruebas (no lo hacen!)

Otra alternativa es utilizar el nombre del parámetro evidencia implícita generada automáticamente por el compilador:

def add[T: Numeric](x: T, y: T) = evidence$1.plus(x,y) 

Es sorprendente que esta técnica es incluso legal, y no se debe confiar en ella en la práctica ya que el nombre del parámetro de evidencia podría cambiar.

Contexto de un tipo superior (introducir el método context)

su lugar, se puede usar una versión reforzada-up del método implicitly. Tenga en cuenta que el método implícitamente se define como

def implicitly[T](implicit e: T): T = e 

Este método simplemente se basa en el compilador para insertar un objeto implícito del tipo correcto del ámbito circundante en la llamada al método, y luego lo devuelve. Podemos hacer un poco mejor:

def context[C[_], T](implicit e: C[T]) = e 

Esto nos permite definir nuestro método add como

def add[T: Numeric](x: T, y: T) = context.plus(x,y) 

Los parámetros de tipo context método Numeric y T se infiere a partir del alcance! Desafortunadamente, hay circunstancias en las que este método context no funcionará. Cuando un parámetro de tipo tiene múltiples límites de contexto o hay múltiples parámetros con diferentes límites de contexto, por ejemplo.Podemos resolver este último problema con una versión ligeramente más complejo:

class Context[T] { def apply[C[_]]()(implicit e: C[T]) = e } 
def context[T] = new Context[T] 

Esta versión nos obliga a especificar el parámetro de tipo cada vez, pero maneja múltiples parámetros de tipo.

def add[T: Numeric](x: T, y: T) = context[T]().plus(x,y) 
+5

¡Hack inteligente con el método 'context'! –

+8

Chico, si pensara que alguien dependía del nombre del parámetro de evidencia de esa manera lo cambiaría una vez por semana más o menos ... también, la técnica no es legal en mi jurisdicción, pero tal vez las leyes son diferentes donde usted vivir. – extempore

+0

Muy inteligente de hecho. – pedrofurla

7

Al menos desde Scala 2.9 puede hacer lo siguiente:

import Numeric.Implicits._ 
def add[T: Numeric](x: T, y: T) = x + y 

add(2.8, 0.1) // res1: Double = 2.9 
add(1, 2) // res2: Int = 3 
+0

¡Gracias, nunca fue claro en los documentos cómo funcionó eso! – Lachlan

4

Esta respuesta describe otro enfoque que se traduce en código de cliente más legible, autodocumentado.

motivación

El context method that I described previously es una solución muy general que funciona con cualquier tipo de clase, sin ningún esfuerzo adicional. Sin embargo, puede ser indeseable por dos razones:

  • El método context no se puede utilizar cuando el parámetro de tipo tiene múltiples límites de contexto, ya que el compilador no tiene manera de determinar qué contexto ligado se pretende.

  • La referencia al método genérico context daña la legibilidad del código del cliente.

    métodos

de tipo específico de clase

usando un método que está ligada a la clase tipo deseado del código de cliente hace mucho más fácil de leer. Este es el enfoque utilizado en la biblioteca estándar para la clase de tipo Manifiesto:

// definition in Predef 
def manifest[T](implicit m: Manifest[T]) = m 

// example usage 
def getErasure[T: Manifest](x: T) = manifest[T].erasure 

Generalizando este enfoque

El principal inconveniente de la utilización de métodos de tipo específico de clase es que un método adicional se debe definir para cada clase de tipo. Podemos facilitar este proceso con las siguientes definiciones:

class Implicitly[TC[_]] { def apply[T]()(implicit e: TC[T]) = e } 
object Implicitly { def apply[TC[_]] = new Implicitly[TC] } 

A continuación, un nuevo método implícitamente al estilo tipo específico de clase se puede definir, para cualquier clase de tipo:

def numeric = Implicitly[Numeric] 
// or 
val numeric = Implicitly[Numeric] 

Por último, el código de cliente puede utilice el implícito de la siguiente manera:

def add[T: Numeric](x: T, y: T) = numeric[T].plus(x, y) 
Cuestiones relacionadas