Come ottengo un’istanza della class del tipo associata ad un contesto associato?

Nota: sto ponendo questa domanda per rispondere da solo, ma altre risposte sono benvenute.

Considera il seguente metodo semplice:

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

Posso riscriverlo usando un contesto associato come segue

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

ma come ottengo un’istanza del tipo Numeric[T] modo da poter invocare il metodo plus ?

Usando il metodo implicitamente

L’approccio più comune e generale è usare il metodo implicitamente , definito in Predef:

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

Ovviamente, questo è un po ‘prolisso e richiede di ripetere il nome della class del tipo.

Riferimento al parametro evidence ( no! )

Un’altra alternativa è usare il nome del parametro evidence implicito generato automaticamente dal compilatore:

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

È sorprendente che questa tecnica sia legale, e non dovrebbe essere utilizzata in pratica poiché il nome del parametro evidence potrebbe cambiare.

Contesto di un tipo più alto ( introducendo il metodo di context )

Invece, si può usare una versione potenziata del metodo implicitly . Si noti che il metodo implicitamente è definito come

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

Questo metodo si basa semplicemente sul compilatore per inserire un object implicito del tipo corretto dall’ambito circostante nella chiamata al metodo e quindi restituirlo. Possiamo fare un po ‘meglio:

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

Questo ci permette di definire il nostro metodo di add come

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

I parametri del tipo di metodo context Numeric e T sono dedotti dall’ambito! Sfortunatamente, ci sono circostanze in cui questo metodo di context non funzionerà. Quando un parametro di tipo ha più limiti di contesto o ci sono più parametri con limiti di contesto diversi, ad esempio. Possiamo risolvere quest’ultimo problema con una versione leggermente più complessa:

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

Questa versione ci richiede di specificare il parametro type ogni volta, ma gestisce più parametri di tipo.

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

Almeno dal momento che Scala 2.9 è ansible fare quanto segue:

 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 

Questa risposta descrive un altro approccio che si traduce in un codice client più leggibile e autoreferenziale.

Motivazione

Il metodo di context che ho descritto in precedenza è una soluzione molto generale che funziona con qualsiasi class di tipo, senza alcuno sforzo aggiuntivo. Tuttavia, potrebbe essere indesiderabile per due motivi:

  • Il metodo di context non può essere utilizzato quando il parametro type ha più limiti di contesto, poiché il compilatore non ha modo di determinare quale contesto è destinato.

  • Il riferimento al metodo di context generico danneggia la leggibilità del codice client.

Metodi specifici della class tipo

L’utilizzo di un metodo che è legato alla class del tipo desiderata rende il codice client molto più leggibile. Questo è l’approccio utilizzato nella libreria standard per la class di tipo Manifesto:

 // definition in Predef def manifest[T](implicit m: Manifest[T]) = m // example usage def getErasure[T: Manifest](x: T) = manifest[T].erasure 

Generalizzare questo approccio

Lo svantaggio principale dell’utilizzo di metodi specifici della class tipo è che deve essere definito un metodo addizionale per ogni class di tipi. Possiamo facilitare questo processo con le seguenti definizioni:

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

Quindi è ansible definire un nuovo metodo di stile implicito specifico per class tipo, per qualsiasi class di tipo:

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

Infine, il codice client può utilizzare Implicitamente come segue:

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